
org.integratedmodelling.engine.geospace.gis.FeatureRasterizer Maven / Gradle / Ivy
/*******************************************************************************
* 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.geospace.gis;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.util.HashMap;
import javax.media.jai.RasterFactory;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.integratedmodelling.api.modelling.IClassifyingObserver;
import org.integratedmodelling.api.modelling.IObserver;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.monitoring.Messages;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.utils.NameGenerator;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.extents.Grid;
import org.integratedmodelling.engine.geospace.interfaces.IGridMask;
import org.integratedmodelling.engine.geospace.literals.ShapeValue;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
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;
import com.vividsolutions.jts.geom.Polygon;
/**
* Rasterize features onto a WritableRaster object using Java 2D Graphics/BufferedImage.
*
* @author steve.ansari, NOAA (original code)
* @author Ferdinando Villa (extensive modifications)
* @created March 20, 2008
*/
public class FeatureRasterizer {
private int height;
private int width;
private float noDataValue = Float.NaN;
private WritableRaster raster = null;
private BufferedImage bimage = null;
private Graphics2D graphics = null;
private double minAttValue = 999999999;
private double maxAttValue = -999999999;
private int[] coordGridX = new int[3500];
private int[] coordGridY = new int[3500];
private float value = 0.0f;
private boolean emptyGrid = false;
private GeometryFactory geoFactory = new GeometryFactory();
private String attributeName = "value";
public static GridCoverageFactory rasterFactory = new GridCoverageFactory();
private double xInterval;
private double yInterval;
// Any change in height, width or no_data values will cause
// the raster to 'reset' at the next call to .rasterize(...)
private boolean resetRaster = true;
private AttributeDescriptor attributeDescriptor;
/*
* if this is not null, we have rasterized a string attribute, and the map defines the
* values encountered and the corresponding short integer in the resulting coverage.
*/
private HashMap classification = null;
private String valueDefault;
private Geometry hullShape = null;
private Grid extent;
IMonitor monitor;
private boolean attributeAlertDone;
/**
* Constructor for the FeatureRasterizer object
*/
public FeatureRasterizer() {
this(800, 800, Float.NaN);
}
/**
* Returns values of the rasterized attribute that correspond to consecutive numeric
* IDs, ranging from 1 to the number of classes (inclusive), in the raster.
*
* @return classification
*/
public String[] getClassification() {
if (classification == null)
return null;
String[] ret = new String[classification.size()];
for (String s : classification.keySet()) {
ret[classification.get(s) - 1] = s;
}
return ret;
}
/**
* Constructor for the FeatureRasterizer object
*
* @param height Height of raster (number of grid cells)
* @param width Width of raster (number of grid cells)
* @param noData No Data value for raster
*/
public FeatureRasterizer(int height, int width, float noData) {
this.height = height;
this.width = width;
this.noDataValue = noData;
raster = /*
* RasterFactory.createBandedRaster(DataBuffer.TYPE_FLOAT, width, height,
* 1, null)
*/null;
bimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bimage.setAccelerationPriority(1.0f);
// GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
graphics = bimage.createGraphics();
graphics.setPaintMode();
graphics.setComposite(AlphaComposite.Src);
}
/**
* Call this one when you need to analyze the attribute for rasterization and behave
* accordingly.
*
* @param extent
*
* @param noData
* @param attributeDescriptor
* @param monitor
*/
public FeatureRasterizer(Grid extent, float noData, AttributeDescriptor attributeDescriptor,
IMonitor monitor) {
this(extent.getYCells(), extent.getXCells(), noData);
this.attributeDescriptor = attributeDescriptor;
this.extent = extent;
this.monitor = monitor;
}
public GridCoverage2D rasterize(String name, FeatureCollection fc, String attributeName, IObserver observer, ReferencedEnvelope env)
throws KlabException {
if (raster == null) {
WritableRaster raster = RasterFactory
.createBandedRaster(getRasterType(observer), this.width, this.height, 1, null);
setWritableRaster(raster);
}
clearRaster(null);
if (env == null) {
rasterize(fc, extent, attributeName);
/*
* Use full envelope from feature collection TODO check if we need to use a
* buffer like in Steve's code above
*/
env = fc.getBounds();
} else {
// /*
// * TODO check if we need to use a buffer like in Steve's code above
// */
// java.awt.geom.Rectangle2D.Double box =
// new java.awt.geom.Rectangle2D.Double(
// env.getMinX(),
// env.getMinY(),
// env.getWidth(),
// env.getHeight());
rasterize(fc, extent, attributeName);
}
GridCoverage2D coverage = rasterFactory.create(name, raster, env);
return coverage;
}
private int getRasterType(IObserver observer) {
int ret = DataBuffer.TYPE_FLOAT;
if (observer instanceof IClassifyingObserver) {
if (classification == null) {
classification = new HashMap();
}
} /*
* else if (attributeDescriptor != null) {
*
* if ( !(attributeDescriptor.getType() instanceof NumericAttributeType)) { if
* (classification == null) { classification = new HashMap(); }
* } }
*/
return ret;
}
/**
* Rasterize a single shape. It is assumed that the grid and the shape agree with each
* other, and that the axes don't need swapping.
*
* @param shape
* @param value
* @return rasterized coverage
* @throws KlabException
*
*/
public GridCoverage2D rasterize(ShapeValue shape, int value) throws KlabException {
if (raster == null) {
WritableRaster raster = RasterFactory
.createBandedRaster(DataBuffer.TYPE_SHORT, this.width, this.height, 1, null);
setWritableRaster(raster);
}
clearRaster(extent);
setBounds(extent, false);
this.value = value;
checkReset(DataBuffer.TYPE_SHORT);
addShape(shape, false);
close();
GridCoverage2D coverage = rasterFactory
.create(NameGenerator.newName("mask"), raster, extent.getEnvelope());
return coverage;
}
private void checkReset(int type) {
if (resetRaster) {
raster = RasterFactory.createBandedRaster(type, width, height, 1, null);
bimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
bimage.setAccelerationPriority(1.0f);
graphics = bimage.createGraphics();
graphics.setPaintMode();
graphics.setComposite(AlphaComposite.Src);
resetRaster = false;
}
}
public GridCoverage2D rasterize(String name, FeatureIterator fc, String attributeName, IObserver observer, String valueDefault, ReferencedEnvelope dataEnvelope, boolean swapAxis)
throws KlabException {
/**
* 1. pass the DATA envelope that the data will come in; 2. swapAxis is IMPOSSIBLE
* to understand: from WFS it should always be true for 4326. For files, I have no
* idea. Maybe the datasources should pass it as a parameter.
*
*
*/
if (raster == null) {
WritableRaster raster = RasterFactory
.createBandedRaster(getRasterType(observer), this.width, this.height, 1, null);
setWritableRaster(raster);
}
if (dataEnvelope == null) {
throw new KlabValidationException("rasterizer: data envelope must be passed");
}
/*
* TODO check if we need to use a buffer like in Steve's code above
*/
// java.awt.geom.Rectangle2D.Double box =
// new java.awt.geom.Rectangle2D.Double(
// dataEnvelope.getMinX(),
// dataEnvelope.getMinY(),
// dataEnvelope.getWidth(),
// dataEnvelope.getHeight());
rasterize(fc, extent, attributeName, observer, valueDefault, dataEnvelope, swapAxis, monitor);
GridCoverage2D coverage = rasterFactory.create(name, raster, dataEnvelope);
if (KLAB.CONFIG.isDebug()) {
coverage.show();
}
return coverage;
}
// /**
// * Gets the raster attribute of the FeatureRasterizer object
// * Processes data from the FeatureCollection and approximates onto a Raster Grid.
// *
// * @param fc Feature Collection with features to rasterize.
// * @param attributeName Name of attribute from feature collection to provide as the
// cell value.
// * @exception FeatureRasterizerException An error when rasterizing the data
// */
// public void rasterize(FeatureCollection fc,
// String attributeName)
// throws ThinklabException {
//
// double edgeBuffer = 0.001;
// double x = fc.getBounds().getMinX() - edgeBuffer;
// double y = fc.getBounds().getMinY() - edgeBuffer;
// double width = fc.getBounds().getWidth() + edgeBuffer * 2;
// double height = fc.getBounds().getHeight() + edgeBuffer * 2;
// java.awt.geom.Rectangle2D.Double bounds = new java.awt.geom.Rectangle2D.Double(x,
// y, width, height);
// rasterize(fc, bounds, attributeName);
// }
/**
* Gets the raster attribute of the FeatureRasterizer object Processes data from the
* FeatureCollection and approximates onto a Raster Grid.
*
* @param fc Description of the Parameter
* @param bounds Description of the Parameter
* @param attributeName Name of attribute from feature collection to provide as the
* cell value.
* @exception KlabException An error when rasterizing the data
*/
public void rasterize(FeatureCollection fc, Grid bounds, String attributeName)
throws KlabException {
this.attributeName = attributeName;
checkReset(DataBuffer.TYPE_FLOAT);
clearRaster(null);
setBounds(bounds, false);
FeatureIterator fci = fc.features();
SimpleFeature feature;
while (fci.hasNext()) {
feature = fci.next();
addFeature(feature, null, false);
}
close();
}
/**
* Gets the raster attribute of the FeatureRasterizer object Processes data from the
* FeatureCollection and approximates onto a Raster Grid.
*
* @param fc Description of the Parameter
* @param bounds Description of the Parameter
* @param attributeName Name of attribute from feature collection to provide as the
* cell value.
* @param observer
* @param valueDefault
* @param dataEnvelope
* @param swapAxis
* @exception KlabException An error when rasterizing the data
*/
public void rasterize(FeatureIterator fc, Grid bounds, String attributeName, IObserver observer, String valueDefault, ReferencedEnvelope dataEnvelope, boolean swapAxis, IMonitor monitor)
throws KlabException {
this.attributeName = attributeName;
this.valueDefault = valueDefault;
checkReset(getRasterType(observer));
if (attributeName == null) {
// no attribute means use 1.0 for presence of feature
value = 1.0f;
noDataValue = 0.0f;
}
// initialize raster to NoData value
clearRaster(bounds);
setBounds(bounds, swapAxis);
SimpleFeature feature;
int n = 0;
while (fc.hasNext()) {
try {
feature = fc.next();
addFeature(feature, dataEnvelope, swapAxis);
n++;
} catch (Exception e) {
// timeouts are easy to get into here, we don't want them to kill the
// rasterization
KLAB.warn("problem reading feature #" + n + ": " + e.getMessage());
}
// log when we have to rasterize lots of features, so we know we should take
// action otherwise
if (n > 0 && (n % 5000) == 0) {
if (monitor != null) {
monitor.info("rasterized " + n
+ "-th feature. Consider providing pre-rasterized data", Messages.INFOCLASS_MODEL);
}
KLAB.info("rasterized " + n + "-th feature... that's a lot to rasterize");
}
}
close();
if (monitor != null) {
monitor.info("rasterized " + n + " features", Messages.INFOCLASS_MODEL);
}
KLAB.info("rasterized " + n + " features");
}
public void addShape(ShapeValue shape, boolean swapAxis) {
try {
shape = shape.transform(Geospace.get().getDefaultCRS());
} catch (KlabException e) {
return;
}
int rgbVal = floatBitsToInt(value);
graphics.setColor(new Color(rgbVal, true));
Geometry geometry = shape.getGeometry();
if (geometry.intersects(extent.getBoundary())) {
if (geometry.getClass().equals(MultiPolygon.class) || geometry.getClass().equals(Polygon.class)) {
for (int i = 0; i < geometry.getNumGeometries(); i++) {
Polygon poly = (Polygon) geometry.getGeometryN(i);
LinearRing lr = geoFactory.createLinearRing(poly.getExteriorRing().getCoordinates());
Polygon part = geoFactory.createPolygon(lr, null);
drawGeometry(part, false, swapAxis);
for (int j = 0; j < poly.getNumInteriorRing(); j++) {
lr = geoFactory.createLinearRing(poly.getInteriorRingN(j).getCoordinates());
part = geoFactory.createPolygon(lr, null);
drawGeometry(part, true, swapAxis);
}
}
} else if (geometry.getClass().equals(MultiLineString.class)) {
MultiLineString mp = (MultiLineString) geometry;
for (int n = 0; n < mp.getNumGeometries(); n++) {
drawGeometry(mp.getGeometryN(n), false, swapAxis);
}
} else if (geometry.getClass().equals(MultiPoint.class)) {
MultiPoint mp = (MultiPoint) geometry;
for (int n = 0; n < mp.getNumGeometries(); n++) {
drawGeometry(mp.getGeometryN(n), false, swapAxis);
}
} else {
drawGeometry(geometry, false, swapAxis);
}
}
}
/**
* Implementation the StreamingProcess interface. Rasterize a single feature and
* update current WriteableRaster using the current settings.
*
* @param feature The feature to rasterize and add to current WritableRaster
*/
public void addFeature(SimpleFeature feature, ReferencedEnvelope dataEnvelope, boolean swapAxis) {
try {
// if (this.attributeTable == null) {
if (attributeName != null) {
Object attr = feature.getAttribute(attributeName);
if (attr == null) {
if (this.valueDefault != null) {
attr = this.valueDefault;
} else {
/*
* alert that the attribute is not available (once). Need the monitor to be
* passed.
*/
if (!this.attributeAlertDone) {
this.attributeAlertDone = true;
if (monitor != null) {
monitor.error("rasterization attribute " + attributeName + " not found");
}
}
return;
}
}
if (classification != null) {
value = getClassifiedValue(attr.toString());
} else {
try {
value = Float.parseFloat(attr.toString());
} catch (Throwable e) {
// OK, definitely not a number, so let's encode strings and
// we deal with that externally.
classification = new HashMap();
value = getClassifiedValue(attr.toString());
}
}
}
if (value > maxAttValue) {
maxAttValue = value;
}
if (value < minAttValue) {
minAttValue = value;
}
// } else {
//
// // TODO account for value being used as key into a hash. If so, the value
// may be null or empty,
// // and nodata should be left undisturbed with no error (warning maybe).
// // value =
// Float.parseFloat(attributeTable.getIndexedValue(feature.getAttribute(attributeName),
// attributeHandle));
// // attributeTable.getIndexedValue(feature.getAttribute(attributeName),
// attributeHandle)
// }
} catch (Exception e) {
e.printStackTrace();
KLAB
.error("THE FEATURE COULD NOT BE RASTERIZED BASED ON THE '" + attributeName
+ "' ATTRIBUTE VALUE OF '" + feature.getAttribute(attributeName).toString()
+ "'");
return;
}
// Extract polygon and rasterize
Geometry geometry = (Geometry) feature.getDefaultGeometry();
if (dataEnvelope != null) {
try {
geometry = JTS.transform(geometry, CRS
.findMathTransform(dataEnvelope.getCoordinateReferenceSystem(), extent.getCRS()));
} catch (Exception e) {
return;
}
}
if (geometry.getClass().equals(MultiPolygon.class) || geometry.getClass().equals(Polygon.class)) {
for (int i = 0; i < geometry.getNumGeometries(); i++) {
Polygon poly = (Polygon) geometry.getGeometryN(i);
LinearRing lr = geoFactory.createLinearRing(poly.getExteriorRing().getCoordinates());
Polygon part = geoFactory.createPolygon(lr, null);
drawGeometry(part, false, swapAxis);
for (int j = 0; j < poly.getNumInteriorRing(); j++) {
lr = geoFactory.createLinearRing(poly.getInteriorRingN(j).getCoordinates());
part = geoFactory.createPolygon(lr, null);
drawGeometry(part, true, swapAxis);
}
}
} else if (geometry.getClass().equals(MultiLineString.class)) {
MultiLineString mp = (MultiLineString) geometry;
for (int n = 0; n < mp.getNumGeometries(); n++) {
drawGeometry(mp.getGeometryN(n), false, swapAxis);
}
} else if (geometry.getClass().equals(MultiPoint.class)) {
MultiPoint mp = (MultiPoint) geometry;
for (int n = 0; n < mp.getNumGeometries(); n++) {
drawGeometry(mp.getGeometryN(n), false, swapAxis);
}
} else {
drawGeometry(geometry, false, swapAxis);
}
// }
}
private float getClassifiedValue(String string) {
Integer ret = classification.get(string);
if (ret == null) {
ret = classification.size() + 1;
classification.put(string, ret);
}
return ret;
}
/**
* this copies values from BufferedImage RGB to WritableRaster of floats or integers.
*
* @throws KlabException
*/
public void close() throws KlabException {
IGridMask mask = null;
if (extent != null && hullShape != null) {
mask = ThinklabRasterizer.createMask(new ShapeValue(hullShape
.buffer(Math.max(extent.getEWExtent(), extent.getNSExtent())), extent.getCRS()), extent);
}
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
float fval = Float.NaN;
if (mask == null || mask.isActive(i, j)) {
fval = Float.intBitsToFloat(bimage.getRGB(i, j));
}
raster.setSample(i, j, 0, fval);
}
}
}
private void drawGeometry(Geometry geometry, boolean hole, boolean swapAxis) {
Coordinate[] coords = geometry.getCoordinates();
int rgbVal = floatBitsToInt(hole ? Float.NaN : value);
graphics.setColor(new Color(rgbVal, true));
// enlarge if needed
if (coords.length > coordGridX.length) {
coordGridX = new int[coords.length];
coordGridY = new int[coords.length];
}
// Clear Array
for (int i = 0; i < coords.length; i++) {
coordGridX[i] = -1;
}
for (int i = 0; i < coords.length; i++) {
coordGridY[i] = -1;
}
// Go through coordinate array in order received (clockwise)
// if (swapAxis) {
// for (int n = 0; n < coords.length; n++) {
// coordGridX[n] = (int) (((coords[n].y - extent.getWest()) / xInterval));
// coordGridY[n] = (int) (((coords[n].x - extent.getSouth()) / yInterval));
// coordGridY[n] = bimage.getHeight() - coordGridY[n];
// }
// } else {
for (int n = 0; n < coords.length; n++) {
coordGridX[n] = (int) (((coords[n].x - extent.getWest()) / xInterval));
coordGridY[n] = (int) (((coords[n].y - extent.getSouth()) / yInterval));
coordGridY[n] = bimage.getHeight() - coordGridY[n];
}
// }
if (geometry.getClass().equals(Polygon.class)) {
graphics.fillPolygon(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.drawLine(coordGridX[0], coordGridY[0], coordGridX[0], coordGridY[0]);
}
}
/**
* Gets the emptyGrid attribute of the FeatureRasterizer object
*
* @return The emptyGrid value
*/
public boolean isEmptyGrid() {
return emptyGrid;
}
/**
* Gets the writableRaster attribute of the FeatureRasterizer object
*
* @return The writableRaster value
*/
public WritableRaster getWritableRaster() {
return raster;
}
/**
* Sets the writableRaster attribute of the FeatureRasterizer object
*
* @param raster The new writableRaster value
*/
public void setWritableRaster(WritableRaster raster) {
this.raster = raster;
}
public Geometry setBounds(Grid grid, boolean swapAxis) {
xInterval = grid.getEWExtent() / width;
yInterval = grid.getNSExtent() / height;
Envelope env = swapAxis
? new Envelope(grid.getSouth(), grid.getNorth(), grid.getWest(), grid.getEast())
: new Envelope(grid.getWest(), grid.getEast(), grid.getSouth(), grid.getNorth());
return geoFactory.toGeometry(env);
}
/**
* Sets the entire raster to NoData
*
* @param bounds
*/
public void clearRaster(Grid bounds) {
minAttValue = 999999999;
maxAttValue = -999999999;
// initialize raster to NoData value inside covered area, NaN outside
for (int i = 0; i < bounds.getXCells(); i++) {
for (int j = 0; j < bounds.getYCells(); j++) {
boolean inRegion = true;
float clear = inRegion ? noDataValue : Float.NaN;
raster.setSample(i, j, 0, clear);
bimage.setRGB(i, j, floatBitsToInt(clear));
}
}
/*
* reset collection of coordinates or geometry to compute convex hull for
* activation
*/
this.hullShape = null;
}
/**
* Get the current attribute to use as the grid cell values.
*/
public String getAttName() {
return attributeName;
}
/**
* Sets the current attribute to use as the grid cell values.
*/
public void setAttName(String attName) {
this.attributeName = attName;
}
public float getNoDataValue() {
return noDataValue;
}
public void setNoDataValue(float noData) {
if (noData != noDataValue) {
resetRaster = true;
}
this.noDataValue = noData;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
if (this.height != height) {
resetRaster = true;
}
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
if (this.width != width) {
resetRaster = true;
}
this.width = width;
}
public double getMinAttValue() {
return minAttValue;
}
public double getMaxAttValue() {
return maxAttValue;
}
private int floatBitsToInt(float f) {
ByteBuffer conv = ByteBuffer.allocate(4);
conv.putFloat(0, f);
return conv.getInt(0);
}
@Override
public String toString() {
return "FEATURE RASTERIZER: WIDTH=" + width + " , HEIGHT=" + height + " , NODATA=" + noDataValue;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy