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;
}
}