org.geolatte.maprenderer.sld.graphics.ExternalGraphicsRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.geolatte.maprenderer.sld.graphics.ExternalGraphicsRepository.java

Source

/*
 * This file is part of the GeoLatte project.
 *
 *     GeoLatte is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     GeoLatte is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with GeoLatte.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Copyright (C) 2010 - 2011 and Ownership of code is shared by:
 *  Qmino bvba - Esperantolaan 4 - 3001 Heverlee  (http://www.qmino.com)
 *  Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
 */

package org.geolatte.maprenderer.sld.graphics;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.geolatte.maprenderer.util.SVGDocumentIO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A Repository for ExternalGraphic images.
 *
 * <p>Clients can get {@link ExternalGraphic}s from this repository by URL. The ExternalGraphic is first search in the internal cache,
 * then it is search in local symbol packages (see below), finally it is searched on the internet.</p>
 *
 * <p>An <code>ExternalGraphicsRepository</code> can be provided with a set of local symbol packages. These packages (jars)
 * must reside on the class path and contain a properties file 'graphics.index' listing the URL's and image filenames.</p>
 *
 * @author Karel Maesen, Geovise BVBA
 *         creation-date: 9/14/11
 */
public class ExternalGraphicsRepository {

    public final static float DEFAULT_SIZE = 16f;
    public final static float DEFAULT_ROTATION = 0f;

    private final static Logger LOGGER = LoggerFactory.getLogger(ExternalGraphicsRepository.class);

    private final Map<ImageKey, BufferedImage> cache = new ConcurrentHashMap<ImageKey, BufferedImage>();
    private final Map<String, SVGDocument> svgCache = new ConcurrentHashMap<String, SVGDocument>();

    //TODO -- verify the exception-handling scenario's
    //TODO -- replace the concurrentHashMaps with ehcache (in order to control growth of the cache). Note that ehcache is already a dependency

    public ExternalGraphicsRepository(String[] localGraphicsPackage) {
        for (String packageName : localGraphicsPackage) {
            readGraphicsFromPackage(packageName);
        }
    }

    public BufferedImage get(String url) throws IOException {
        return get(url, DEFAULT_SIZE, DEFAULT_ROTATION, false);
    }

    public BufferedImage get(String url, float size, float rotation, boolean sizeSet) throws IOException {
        ImageKey key = new ImageKey(url, size, rotation);
        BufferedImage image = getFromCache(key);
        if (image == null) {
            image = retrieve(url, size, sizeSet);
            if (rotation != 0) {
                image = rotate(image, rotation);
            }
            storeInCache(key, image);
        }
        return image;
    }

    public void storeInCache(ImageKey url, BufferedImage source) {
        if (url == null || source == null)
            throw new IllegalArgumentException();
        this.cache.put(url, source);
    }

    public void storeInSvgCache(String url, SVGDocument svgDoc) {
        if (url == null || svgDoc == null)
            throw new IllegalArgumentException();
        this.svgCache.put(url, svgDoc);
    }

    public BufferedImage getFromCache(ImageKey url) {
        return this.cache.get(url);
    }

    public SVGDocument getSVGFromCache(String url) {
        return this.svgCache.get(url);
    }

    /**
     * Reads all the graphics from a package and stores the images in the image cache and svg's
     * in the svg cache.
     *
     * @param packageName
     */
    private void readGraphicsFromPackage(String packageName) {
        InputStream graphicsIndex = getGraphicsIndex(packageName);
        if (graphicsIndex == null) {
            LOGGER.warn("Can't find package " + packageName + ", or package doesn't have a graphics.index file.");
            return;
        }
        try {
            Properties index = readGraphicsIndex(graphicsIndex);
            readGraphicsFromPackage(packageName, index);
        } catch (IOException e) {
            LOGGER.warn("Error reading from package " + packageName, e);
        }
    }

    private void readGraphicsFromPackage(String packageName, Properties index) throws IOException {
        Enumeration<String> enumeration = (Enumeration<String>) index.propertyNames();
        while (enumeration.hasMoreElements()) {
            String url = enumeration.nextElement();
            String path = packageName + "/" + index.getProperty(url).trim();
            retrieveAndStore(url, path);
        }
    }

    private Properties readGraphicsIndex(InputStream graphicsIndexFile) throws IOException {
        Properties index = new Properties();
        index.loadFromXML(graphicsIndexFile);
        return index;
    }

    /**
     * Retrieves image or SVG from path and stores it in the cache (svg or image cache)
     * @param uri
     * @param path
     * @throws IOException
     */
    private void retrieveAndStore(String uri, String path) throws IOException {
        InputStream inputStream = getResourceAsInputStream(path);
        if (inputStream == null) {
            throw new IOException(String.format("Graphics file %s not found on classpath.", path));
        }

        //try to read it as an image (png, jpeg,..)
        BufferedImage img = ImageIO.read(inputStream);
        if (img != null) {
            storeInCache(new ImageKey(uri), img);
            return;
        }
        //ImageIO.read() removes the first 8 characters from the input stream, so we need to reset it.
        inputStream = getResourceAsInputStream(path);

        //if unsuccesfull, try to read it as an SVG
        SVGDocument svg = SVGDocumentIO.read(uri, inputStream);
        if (svg != null) {
            storeInSvgCache(uri, svg);
            return;
        }
        throw new IOException("File " + path + " is neither image nor svg.");
    }

    /**
     * Retrieves the image from specified URL.
     *
     * If the url points to an SVG, the SVG is rendered at the specified size. If the
     * url points to an bitmap image, then the image is scaled to size only if sizeSet is true.
     *
     * @param url URL to the graphic source.
     * @param size size at which to render the image (for SVG) or to which to scale (if sizeSet)
     * @param sizeSet whether or not a specific size is explicitly requested for the image
     * @return
     * @throws IOException
     */
    private BufferedImage retrieve(String url, float size, boolean sizeSet) throws IOException {
        //check if we have the image in default_size of
        BufferedImage unscaledImage = getFromCache(new ImageKey(url));
        if (unscaledImage != null) {
            return sizeSet ? scale(unscaledImage, size) : unscaledImage;
        }
        //check if we have it in the SVG Cache
        SVGDocument cachedSVG = this.svgCache.get(url);
        if (cachedSVG != null) {
            return transCodeSVG(cachedSVG, size);
        }
        //if not, retrieve from URL.
        HttpEntity entity = retrieveGraphicFromUrl(url);
        if (contentTypeIsSVG(entity)) {
            return SVGFromURLResponse(url, size, entity);
        } else {
            return scale(ImageFromURLResponse(url, entity), size);
        }
    }

    private BufferedImage ImageFromURLResponse(String url, HttpEntity entity) throws IOException {
        BufferedImage img = ImageIO.read(entity.getContent());
        if (img == null)
            throw new IOException("Response from " + url + " is not recognized as an image.");
        //remember the default image (before rotating and scaling)
        storeInCache(new ImageKey(url), img);
        return img;
    }

    private BufferedImage SVGFromURLResponse(String url, float size, HttpEntity entity) throws IOException {
        SVGDocument svg = SVGDocumentIO.read(url, entity.getContent());
        if (svg == null)
            throw new IOException("Response from " + url + " is not recognized as SVG document.");
        storeInSvgCache(url, svg);
        return transCodeSVG(svg, size);
    }

    private HttpEntity retrieveGraphicFromUrl(String url) throws IOException {
        HttpGet httpGet = new HttpGet(url);
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpResponse response = httpClient.execute(httpGet);
        if (response.getStatusLine().getStatusCode() != 200) {
            throw new IOException("Can't retrieve image from " + url);
        }

        return response.getEntity();
    }

    private BufferedImage rotate(BufferedImage img, float rotation) {
        AffineTransform tx = new AffineTransform();
        //determine center point of image
        int sx = img.getMinX() + img.getWidth() / 2;
        int sy = img.getMinY() + img.getHeight() / 2;
        double theta = Math.toRadians(rotation);
        tx.rotate(theta, sx, sy);
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
        return op.filter(img, null);
    }

    private BufferedImage transCodeSVG(SVGDocument svg, float size) {
        if (size < 0) {
            size = DEFAULT_SIZE;
        }
        SVGTranscoder transcoder = new SVGTranscoder();
        SVGSVGElement svgRootElement = svg.getRootElement();
        float svgWidth = svgRootElement.getWidth().getBaseVal().getValue();
        float svgHeight = svgRootElement.getHeight().getBaseVal().getValue();
        //        float aspectRatio = svgWidth/svgHeight;
        //        int height = Math.round(size);
        //        int width = (int)(aspectRatio * height);
        Dimension dim = getWidthAndHeight(svgWidth, svgHeight, size);
        return transcoder.transcode(svg, dim.width, dim.height);
    }

    private BufferedImage scale(BufferedImage unscaledImage, float size) {
        Dimension dim = getWidthAndHeight(unscaledImage.getWidth(), unscaledImage.getHeight(), size);
        AffineTransform tx = new AffineTransform();
        tx.scale(((double) dim.width / unscaledImage.getWidth()),
                ((double) dim.height) / unscaledImage.getHeight());
        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
        return op.filter(unscaledImage, null);
    }

    private Dimension getWidthAndHeight(float originalWidth, float originalHeight, float newSize) {
        float aspectRatio = originalWidth / originalHeight;
        Dimension dim;
        int height = Math.round(newSize);
        int width = (int) (aspectRatio * height);
        return new Dimension(width, height);
    }

    private boolean contentTypeIsSVG(HttpEntity entity) {
        return "image/svg+xml".equalsIgnoreCase(entity.getContentType().getValue());
    }

    private InputStream getGraphicsIndex(String packageName) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(packageName + "/graphics.index");
    }

    private InputStream getResourceAsInputStream(String resource) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
    }

    public static class ImageKey {
        private final String url;
        private final float size;
        private final float rotation;

        private ImageKey(String url, float size, float rotation) {
            this.url = url;
            this.size = size;
            this.rotation = rotation;
        }

        public ImageKey(String url) {
            this(url, DEFAULT_SIZE, DEFAULT_ROTATION);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            ImageKey imageKey = (ImageKey) o;

            if (Float.compare(imageKey.rotation, rotation) != 0)
                return false;
            if (Float.compare(imageKey.size, size) != 0)
                return false;
            if (url != null ? !url.equals(imageKey.url) : imageKey.url != null)
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = url != null ? url.hashCode() : 0;
            result = 31 * result + (size != +0.0f ? Float.floatToIntBits(size) : 0);
            result = 31 * result + (rotation != +0.0f ? Float.floatToIntBits(rotation) : 0);
            return result;
        }
    }
}