All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.engine.visualization.geospace.GeoImageFactory Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *  Copyright (C) 2007, 2015:
 *  
 *    - Ferdinando Villa 
 *    - integratedmodelling.org
 *    - any other authors listed in @author annotations
 *
 *    All rights reserved. This file is part of the k.LAB software suite,
 *    meant to enable modular, collaborative, integrated 
 *    development of interoperable data and model components. For
 *    details, see http://integratedmodelling.org.
 *    
 *    This program is free software; you can redistribute it and/or
 *    modify it under the terms of the Affero General Public License 
 *    Version 3 or any later version.
 *
 *    This program 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
 *    Affero General Public License for more details.
 *  
 *     You should have received a copy of the Affero General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *     The license is also available at: https://www.gnu.org/licenses/agpl.html
 *******************************************************************************/
package org.integratedmodelling.engine.visualization.geospace;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;

import javax.imageio.ImageIO;

import org.apache.jcs.JCS;
import org.apache.jcs.access.exception.CacheException;
import org.geotools.data.ows.Layer;
import org.geotools.data.wms.WMSUtils;
import org.geotools.data.wms.WebMapServer;
import org.geotools.data.wms.request.GetMapRequest;
import org.geotools.data.wms.response.GetMapResponse;
import org.integratedmodelling.api.modelling.IExtent;
import org.integratedmodelling.api.modelling.visualization.IColormap;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.utils.MiscUtilities;
import org.integratedmodelling.common.utils.NetUtilities;
import org.integratedmodelling.common.utils.image.ImageUtil;
import org.integratedmodelling.common.visualization.Viewport;
import org.integratedmodelling.engine.geospace.extents.SpaceExtent;
import org.integratedmodelling.engine.geospace.literals.ShapeValue;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabIOException;
import org.integratedmodelling.exceptions.KlabResourceNotFoundException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;

public class GeoImageFactory {

    public enum HAlignment {
        LEFT,
        MIDDLE,
        RIGHT
    }

    public enum VAlignment {
        TOP,
        MIDDLE,
        BOTTOM
    }

    public static final int HOLLOW_SHAPES = 0x0000;
    public static final int FILLED_SHAPES = 0x0001;
    public static final int BORDER        = 0x0002;
    public static final int GREEN_SHAPES  = 0x004;

    public static final String WMS_IMAGERY_SERVER_PROPERTY = "imagery.wms";
    public static final String WMS_LAYER_PROPERTY          = "imagery.wms.layers";
    public static final String WORLD_IMAGE_PROPERTY        = "world.image";

    // TODO check - milliseconds?
    private static final int TIMEOUT = 2400;

    // private HashMap _cache = new HashMap();

    /*
     * yes, it's a singleton. It's also a simpleton.
     */
    private static GeoImageFactory _instance;

    // private static WebMapServer _wms = null;
    // private int _wms_index = -1;

    // FIXME the last two don't respond and the first shows the wrong part of the world
    private String[] wmsServers = new String[] {
            "http://disc1.gsfc.nasa.gov/daac-bin/wms_ogc?LAYER=AIRIBRAD_DAY&SERVICE=WMS&REQUEST=GetCapabilities"
            // "http://www.integratedmodelling.org/geoserver/ows?service=wms&version=1.1.1&request=GetCapabilities",
            // "http://terraservice.net/ogccapabilities.ashx",
            // "http://onearth.jpl.nasa.gov/wms.cgi?request=GetCapabilities"
    };

    private static HashMap _layers = new HashMap();

    static {
        _layers.put("http://www.integratedmodelling.org:80/geoserver/ows?SERVICE=WMS&", "global:imagery");
    }

    private HashMap worldImages = new HashMap();
    private JCS                  _imgCache;

    // /**
    // * Try out all the configured WMS servers (in imagery.properties) stopping at the first
    // * one that responds. The server will be in _wms after that; if none has responded, _wms
    // * will be null. It will only run the search once, so it can safely be called multiple
    // * times with no performance penalty, and should be called by each function that wants
    // * to use WMS imagery.
    // */
    // private void initializeWms() {
    //
    // if (_wms_index >= 0)
    // return;
    //
    // for (int i = 0; ; i++) {
    //
    // String url =
    // Thinklab.get().getProperties().getProperty(WMS_IMAGERY_SERVER_PROPERTY + "." + i);
    //
    // if (url == null)
    // break;
    //
    // try {
    // if (NetUtilities.urlResponds(url)) {
    // WebMapServer wms = new WebMapServer(new URL(url));
    // if (wms != null )
    // _wms = wms;
    // }
    // } catch (Exception e) {
    // /* just try the next */
    // }
    //
    // _wms_index = i;
    //
    // if (_wms != null) {
    // break;
    // }
    // }
    //
    // if (_wms == null) {
    // /*
    // * try defaults
    // */
    // for (String s : wmsServers) {
    // try {
    // if (NetUtilities.urlResponds(s)) {
    // WebMapServer wms = new WebMapServer(new URL(s));
    // if (wms != null) {
    // _wms = wms;
    // break;
    // }
    // }
    // } catch (Exception e) {
    // /* just try the next */
    // }
    // }
    // }
    //
    // }

    public URL getWorldImageURL(String worldImage, ShapeValue... shapes) throws KlabIOException {

        if (worldImage == null)
            return null;

        URL f;
        try {
            f = new URL(worldImage);
        } catch (MalformedURLException e1) {
            return null;
        }

        if (shapes == null) {
            return f;
        }

        /* open image, get graphics object to draw unto */
        BufferedImage img;
        try {
            img = ImageIO.read(f);
        } catch (IOException e) {
            throw new KlabIOException(e);
        }

        Graphics g = img.createGraphics();
        g.setColor(Color.RED);
        g.setPaintMode();

        for (ShapeValue s : shapes) {
            Polygon p = getPolygon(s, img.getWidth(), img.getHeight());
            g.fillPolygon(p.xpoints, p.ypoints, p.npoints);
        }

        File o = null;

        try {
            o = File.createTempFile("wim", ".gif");
            ImageIO.write(img, "gif", o);
            f = o.toURI().toURL();
        } catch (Exception e) {
            throw new KlabIOException(e);
        }

        return f;
    }

    private Polygon getPolygon(ShapeValue s, int w, int h) {

        Geometry g = s.getGeometry();
        Polygon ret = new Polygon();
        HashSet points = new HashSet();
        int sx = 0, sy = 0;
        for (Coordinate c : g.getBoundary().getCoordinates()) {

            int x = (int) (w * (c.x + 180.0) / 360.0);
            int y = h - (int) (h * (c.y + 90.0) / 180.0);

            if (!points.contains(x + "|" + y)) {

                if (ret.npoints == 0) {
                    sx = x;
                    sy = y;
                }

                ret.addPoint(x, y);
                points.add(x + "|" + y);
            }
        }

        /*
         * close polygon
         */
        if (ret.npoints > 0) {
            ret.addPoint(sx, sy);
        }

        return ret;
    }

    public String getWorldImageFile() throws KlabIOException {

        String ret = KLAB.CONFIG.getProperties().getProperty(WORLD_IMAGE_PROPERTY);
        if (ret == null) {
            File f = KLAB.CONFIG.getDataPath("imagery");
            f = new File(f + File.separator + "world_12000.jpg");
            if (f.exists()) {
                ret = f.toString();
            }
        }
        return ret;
    }

    public URL getSatelliteImage(Envelope envelope, int width, int height) throws KlabException {
        return getSatelliteImageURL(envelope, width, height, null, null, HAlignment.MIDDLE, VAlignment.MIDDLE);
    }

    public JCS getCache() throws KlabException {

        if (_imgCache == null) {

            try {
                _imgCache = JCS.getInstance("imagery");
            } catch (CacheException e) {
                throw new KlabIOException(e);
            }

        }
        return _imgCache;
    }

    public BufferedImage getImagery(Envelope envelope, int width, int height) throws KlabException {

        /*
         * HERE - first thing, try the cache.
         */
        String key = getKey(envelope, width, height);

        BufferedImage ret = null; //DataAssetResolver.getImage(key, getCache());

        if (ret != null)
            return ret;

        /*
         * then try the WMS
         */
        ret = getWMSImage(envelope, width, height);

        /*
         * then try the world file (unconfigured - just give it a name)
         */
        if (ret == null)
            ret = getSatelliteImage(envelope, width, height, getWorldImageFile(), null, HAlignment.MIDDLE, VAlignment.MIDDLE);

        if (ret != null) {
            // DataAssetResolver.loadImage(key, ret, getCache());
        }

        return ret;

    }

    private String getKey(Envelope envelope, int width, int height) {
        // TODO this should be more robust
        return envelope.toString() + "|" + width + "|" + height;
    }

    public BufferedImage getRasterImagery(Envelope envelope, int width, int height, int[] imageData, int rowWidth, IColormap cmap)
            throws KlabException {

        BufferedImage ret = getWMSImage(envelope, width, height);

        if (ret == null)
            ret = getSatelliteImage(envelope, width, height, getWorldImageFile(), null, HAlignment.MIDDLE, VAlignment.MIDDLE);

        /*
         * get unscaled image from pixels
         */
        Image image = ImageUtil.drawUnscaledRaster(ImageUtil.upsideDown(imageData, rowWidth), rowWidth, cmap);

        /*
         * paint scaled over the scenery
         */
        Graphics graphics = ret.getGraphics();
        graphics.drawImage(image, 0, 0, width, height, null); // /??? scaling
        graphics.dispose();

        return ret;
    }

    public BufferedImage paintOverImagery(Envelope envelope, int width, int height, Image image, int rowWidth, IColormap cmap)
            throws KlabException {

        BufferedImage ret = getWMSImage(envelope, width, height);

        if (ret == null)
            ret = getSatelliteImage(envelope, width, height, null, null, HAlignment.MIDDLE, VAlignment.MIDDLE);

        /*
         * paint scaled over the scenery
         */
        Graphics graphics = ret.getGraphics();
        graphics.drawImage(image, 0, 0, width, height, null); // /??? scaling
        graphics.dispose();

        return ret;
    }

    /**
     * Return an image of the world with a shape drawn on it. Flags control the
     * rendering mode. Default is a hollow shape in red outline, touching the borders
     * of the image. Uses WMS servers configured in, trying hard-coded defaults if not
     * configured or configured badly, and resorts to static image if one was provided
     * before declaring defeat.
     * 
     * @param shape
     * @param width
     * @param height
     * @param flags
     * @return the image
     * @throws KlabException
     */
    public BufferedImage getImagery(Envelope envelope, ShapeValue shape, int width, int height, int flags)
            throws KlabException {

        BufferedImage ret = getImagery(envelope, width, height);
        GeometryFactory geoFactory = new GeometryFactory();

        double edgeBuffer = 0.0;

        if (ret == null) {
            ret = getSatelliteImage(envelope, width, height, getWorldImageFile(), null, HAlignment.MIDDLE, VAlignment.MIDDLE);
        }

        if (ret == null)
            return null;

        /*
         * draw shape boundaries.
         */
        Geometry geometry = shape.getGeometry();
        double x = envelope.getMinX() - edgeBuffer;
        double y = envelope.getMinY() - edgeBuffer;
        double w = envelope.getWidth() + edgeBuffer * 2;
        double h = envelope.getHeight() + edgeBuffer * 2;

        java.awt.geom.Rectangle2D.Double bounds = new java.awt.geom.Rectangle2D.Double(x, y, w, h);

        Graphics graphics = ret.getGraphics();

        if ((flags & GREEN_SHAPES) != 0) {
            graphics.setColor(Color.green);
        } else {
            graphics.setColor(Color.red);
        }

        graphics.setPaintMode();

        if (geometry.getClass().equals(MultiPolygon.class) || geometry.getClass().equals(Polygon.class)) {

            for (int i = 0; i < geometry.getNumGeometries(); i++) {
                com.vividsolutions.jts.geom.Polygon poly = (com.vividsolutions.jts.geom.Polygon) geometry
                        .getGeometryN(i);
                LinearRing lr = geoFactory.createLinearRing(poly.getExteriorRing().getCoordinates());
                com.vividsolutions.jts.geom.Polygon part = geoFactory.createPolygon(lr, null);
                drawGeometry(part, bounds, graphics, width, height, flags);
                for (int j = 0; j < poly.getNumInteriorRing(); j++) {
                    lr = geoFactory.createLinearRing(poly.getInteriorRingN(j).getCoordinates());
                    part = geoFactory.createPolygon(lr, null);
                    drawGeometry(part, bounds, graphics, width, height, flags);
                }
            }
        } else if (geometry.getClass().equals(MultiLineString.class)) {
            MultiLineString mp = (MultiLineString) geometry;
            for (int n = 0; n < mp.getNumGeometries(); n++) {
                drawGeometry(mp.getGeometryN(n), bounds, graphics, width, height, flags);
            }
        } else if (geometry.getClass().equals(MultiPoint.class)) {
            MultiPoint mp = (MultiPoint) geometry;
            for (int n = 0; n < mp.getNumGeometries(); n++) {
                drawGeometry(mp.getGeometryN(n), bounds, graphics, width, height, flags);
            }
        } else {
            drawGeometry(geometry, bounds, graphics, width, height, flags);
        }

        return ret;
    }

    public BufferedImage getImagery(ShapeValue shape, int width, int height, int flags)
            throws KlabException {
        return getImagery(shape.getEnvelope(), shape, width, height, flags);
    }

    private void drawGeometry(Geometry geometry, java.awt.geom.Rectangle2D.Double bounds, Graphics graphics, int width, int height, int flags) {

        Coordinate[] coords = geometry.getCoordinates();

        double xInterval = bounds.width / width;
        double yInterval = bounds.height / height;

        // System.out.println("xInterval: " + xInterval + " yInterval: " + yInterval);

        if (xInterval > yInterval) {
            yInterval = xInterval;
        }
        if (yInterval > xInterval) {
            xInterval = yInterval;
        }

        // for later
        double cellsize = yInterval;

        // TODO fix this stupid legacy preallocation when it works
        int[] coordGridX = new int[coords.length];
        int[] coordGridY = new int[coords.length];

        // Go through coordinate array in order received (clockwise)
        for (int n = 0; n < coords.length; n++) {

            coordGridX[n] = (int) (((coords[n].x - bounds.x) / xInterval));
            coordGridY[n] = (int) (((coords[n].y - bounds.y) / yInterval));
            coordGridY[n] = height - coordGridY[n] - 1;

            // this may happen at the extremes, unless we use the pixel center as the coordinate
            if (coordGridX[n] < 0)
                coordGridX[n] = 0;
            if (coordGridY[n] < 0)
                coordGridY[n] = 0;
            if (coordGridX[n] >= width)
                coordGridX[n] = width - 1;
            if (coordGridY[n] >= height)
                coordGridY[n] = height - 1;
        }

        /*
         * ok, this if isn't really necessary, but it may become so in the
         * future.
         */
        if (geometry.getClass().equals(com.vividsolutions.jts.geom.Polygon.class)) {
            if ((flags & FILLED_SHAPES) != 0) {
                graphics.fillPolygon(coordGridX, coordGridY, coords.length);
            } else {
                graphics.drawPolyline(coordGridX, coordGridY, coords.length);
            }
        } else if (geometry.getClass().equals(LinearRing.class)) {
            graphics.drawPolyline(coordGridX, coordGridY, coords.length);
        } else if (geometry.getClass().equals(LineString.class)) {
            graphics.drawPolyline(coordGridX, coordGridY, coords.length);
        } else if (geometry.getClass().equals(Point.class)) {
            graphics.drawPolyline(coordGridX, coordGridY, coords.length);
        }
    }

    /**
     * @param envelope
     * @param width
     * @param height
     * @return
     * @throws KlabResourceNotFoundException
     */
    private BufferedImage getWMSImage(Envelope envelope, int width, int height)
            throws KlabResourceNotFoundException {

        BufferedImage ret = null;
        // initializeWms();

        // String sig = envelope.toString() + "," + width + "," + height;
        // if (_cache.containsKey(sig))
        // return ImageUtil.clone(_cache.get(sig));

        for (String s : wmsServers) {

            if (!NetUtilities.urlResponds(s))
                continue;

            WebMapServer wms = null;

            try {
                wms = new WebMapServer(new URL(s), TIMEOUT);
            } catch (Exception e1) {
                continue;
            }

            GetMapRequest request = wms.createGetMapRequest();
            request.setFormat("image/png");
            request.setDimensions("" + width, "" + height);
            request.setTransparent(true);

            // FIXME this assumes the envelope is in lat/lon
            request.setSRS("EPSG:4326");

            String bbox = (float) envelope.getMinX() + "," + (float) envelope.getMinY() + ","
                    + (float) envelope.getMaxX() + "," + (float) envelope.getMaxY();

            request.setBBox(bbox);

            for (Layer layer : getWMSLayers(wms)) {
                request.addLayer(layer);
            }

            GetMapResponse response = null;
            try {
                System.out.println(request.getFinalURL());
                response = wms.issueRequest(request);
                ret = ImageIO.read(response.getInputStream());
                if (ret != null) {
                    break;
                }
            } catch (Exception e) {
                KLAB.warn("cannot get WFS imagery: " + e.getLocalizedMessage());
                continue;
            }
        }

        return ret;
    }

    private Collection getWMSLayers(WebMapServer wms) {

        ArrayList layers = new ArrayList();
        String source = wms.getInfo().getSource().toString();
        String zp = _layers.get(source);

        //
        // String zp = Thinklab.get().getProperties().getProperty(WMS_LAYER_PROPERTY + "." + _wms_index);
        for (Layer l : WMSUtils.getNamedLayers(wms.getCapabilities())) {
            if (zp == null || (zp != null && zp.contains(l.getName()))) {
                layers.add(l);
            }
        }
        return layers;
    }

    public URL getSatelliteImage(Envelope envelope, URL other, int width, int height)
            throws KlabException {
        return getSatelliteImageURL(envelope, width, height, null, other, HAlignment.MIDDLE, VAlignment.MIDDLE);
    }

    /**
     * The all-configurable draw image engine
     * 
     * @param envelope
     * @param width
     * @param height
     * @param worldImage
     * @param otherImage
     * @param horAligment
     * @param verAlignment
     * @return satellite image
     * @throws KlabException
     */
    public BufferedImage getSatelliteImage(Envelope envelope, int width, int height, String worldImage, URL otherImage, HAlignment horAligment, VAlignment verAlignment)
            throws KlabException {

        URL f = null;

        if (worldImage == null) {
            throw new KlabIOException("image server not responding and no world image file specified");
        }

        try {
            f = new File(worldImage).toURI().toURL();
        } catch (MalformedURLException e1) {
            throw new KlabIOException(e1);
        }

        /* open image, get graphics object to draw unto */
        BufferedImage img;
        try {
            img = ImageIO.read(f);
        } catch (IOException e) {
            throw new KlabIOException(e);
        }

        int w = img.getWidth();
        int h = img.getHeight();

        int x1 = (int) (w * (envelope.getMinX() + 180.0) / 360.0);
        int y2 = h - (int) (h * (envelope.getMinY() + 90.0) / 180.0);

        int x2 = (int) (w * (envelope.getMaxX() + 180.0) / 360.0);
        int y1 = h - (int) (h * (envelope.getMaxY() + 90.0) / 180.0) - 1;

        int gw = x2 - x1;
        int gh = y2 - y1;

        if (gw < 1 || gh < 1)
            /*
             * image too small to display
             */
            return null;

        BufferedImage part = img.getSubimage(x1, y1, gw, gh);

        // Create rescaled picture as new buffered image
        AffineTransform tx = new AffineTransform();
        tx.scale((double) width / gw, (double) height / gh);

        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        BufferedImage newImage = op.filter(part, null);

        /* rescale to desired width */
        Graphics2D graphics2D = newImage.createGraphics();

        /* if we passed one, burn in the other image in specified alignment */
        if (otherImage != null) {

            BufferedImage other;
            try {
                other = ImageIO.read(otherImage);
            } catch (IOException e) {
                throw new KlabIOException(e);
            }

            int ow = other.getWidth();
            int oh = other.getHeight();

            // TODO check if this is ok: a larger image does not get drawn, without
            // any warning.
            if (width >= ow && height >= oh) {

                int nx = 0, ny = 0;

                if (horAligment == HAlignment.MIDDLE)
                    nx = (width - ow) / 2;
                else if (horAligment == HAlignment.RIGHT)
                    nx = width - ow;

                if (verAlignment == VAlignment.MIDDLE)
                    ny = (height - oh) / 2;
                else if (verAlignment == VAlignment.BOTTOM)
                    ny = (height - oh);

                graphics2D.drawImage(other, nx, ny, ow, oh, null);

            }
        }

        return newImage;

    }

    public URL getSatelliteImageURL(Envelope envelope, int width, int height, String worldImage, URL otherImage, HAlignment horAligment, VAlignment verAlignment)
            throws KlabException {

        BufferedImage newImage = getSatelliteImage(envelope, width, height, worldImage, otherImage, horAligment, verAlignment);

        URL f = null;
        File o = null;

        try {
            o = File.createTempFile("sim", ".png");
            ImageIO.write(newImage, "png", o);
            f = o.toURI().toURL();
        } catch (Exception e) {
            throw new KlabIOException(e);
        }

        return f;

    }

    public static GeoImageFactory get() {

        if (_instance == null) {
            _instance = new GeoImageFactory();
        }

        return _instance;
    }

    public void addWorldImage(URL url) {

        String wname = MiscUtilities.getURLBaseName(url.toString());
        worldImages.put(wname, url);

    }

    public static Pair getPlotSize(int viewportWidth, int viewportHeight, IExtent space) {

        if (space instanceof SpaceExtent && ((SpaceExtent) space).isGrid()) {
            return Viewport.getViewportSize(viewportWidth, viewportHeight, ((SpaceExtent) space).getGrid()
                    .getXCells(), ((SpaceExtent) space).getGrid().getYCells());
        } else if (space instanceof SpaceExtent) {
            return Viewport.getViewportSize(viewportWidth, viewportHeight, ((SpaceExtent) space)
                    .getEnvelope().getWidth(), ((SpaceExtent) space).getEnvelope().getWidth());
        }

        return null;

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy