/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotools.resources.coverage;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.feature.type.FeatureTypeFactoryImpl;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureFactory;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureTypeFactory;
import org.opengis.filter.FilterFactory2;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
/**
* A set of utilities methods for interactions between {@link GridCoverage}
* and {@link Feature}. Those methods are not really rigorous; must of them
* should be seen as temporary implementations.
*
* @since 2.4
*
* @source $URL$
* @version $Id$
* @author Simone Giannecchini
*/
public final class FeatureUtilities {
static FeatureTypeFactory typeFactory;
static FeatureFactory featureFactory;
public static final FilterFactory2 DEFAULT_FILTER_FACTORY = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
/**
* Do not allows instantiation of this class.
*/
private FeatureUtilities() {
}
private static FeatureTypeFactory getTypeFactory() {
if(typeFactory == null)
typeFactory = new FeatureTypeFactoryImpl(); // CommonFactoryFinder.getFeatureTypeFactory(null);
return typeFactory;
}
private static FeatureFactory getFeatureFactory() {
if(featureFactory == null)
featureFactory = CommonFactoryFinder.getFeatureFactory(null);
return featureFactory;
}
/**
* Returns the polygon surrounding the specified rectangle.
* Code lifted from ArcGridDataSource (temporary).
*/
private static Polygon getPolygon(final Rectangle2D rect) {
return getPolygon(rect, 0);
}
/**
* Returns the polygon surrounding the specified rectangle.
* Code lifted from ArcGridDataSource (temporary).
*/
public static Polygon getPolygon(final Rectangle2D rect, final int srid) {
final PrecisionModel pm = new PrecisionModel();
final GeometryFactory gf = new GeometryFactory(pm, srid);
final Coordinate[] coord = new Coordinate[] {
new Coordinate(rect.getMinX(), rect.getMinY()),
new Coordinate(rect.getMaxX(), rect.getMinY()),
new Coordinate(rect.getMaxX(), rect.getMaxY()),
new Coordinate(rect.getMinX(), rect.getMaxY()),
new Coordinate(rect.getMinX(), rect.getMinY())
};
final LinearRing ring = gf.createLinearRing(coord);
return new Polygon(ring, null, gf);
}
/**
* Wraps a grid coverage into a Feature. Code lifted from ArcGridDataSource
* (temporary).
*
* @param coverage the grid coverage.
* @return a feature with the grid coverage envelope as the geometry and the
* grid coverage itself in the "grid" attribute.
*/
public static SimpleFeatureCollection wrapGridCoverage(final GridCoverage2D coverage)
throws TransformException, SchemaException {
final Polygon bounds = getPolygon(coverage.getEnvelope2D());
final CoordinateReferenceSystem sourceCRS = coverage.getCoordinateReferenceSystem2D();
SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder(getTypeFactory());
ftb.setName("GridCoverage");
ftb.add("geom", Polygon.class, sourceCRS);
ftb.add("grid", GridCoverage.class);
SimpleFeatureType schema = ftb.buildFeatureType();
// create the feature
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema, getFeatureFactory());
fb.add(bounds);
fb.add(coverage);
SimpleFeature feature = fb.buildFeature(null);
final SimpleFeatureCollection collection = FeatureCollections.newCollection();
collection.add(feature);
return collection;
}
/**
* Checks if the feature type specified is a GridCoverage wrapper
* @param featureType
* @return
*/
public static boolean isWrappedCoverage(SimpleFeatureType featureType) {
if(!"GridCoverage".equals(featureType.getName().getLocalPart()))
return false;
if(featureType.getAttributeCount() != 2)
return false;
AttributeDescriptor polyDescriptor = featureType.getDescriptor("geom");
if(polyDescriptor == null || !Polygon.class.equals(polyDescriptor.getType().getBinding()))
return false;
AttributeDescriptor gridDescriptor = featureType.getDescriptor("grid");
if(gridDescriptor == null || !GridCoverage.class.equals(gridDescriptor.getType().getBinding()))
return false;
return true;
}
/**
* Wraps a grid coverage into a Feature. Code lifted from ArcGridDataSource
* (temporary).
*
* @param reader the grid coverage reader.
* @return a feature with the grid coverage envelope as the geometry and the
* grid coverage itself in the "grid" attribute.
*/
public static SimpleFeatureCollection wrapGridCoverageReader(final AbstractGridCoverage2DReader gridCoverageReader,
GeneralParameterValue[] params) throws TransformException,
FactoryRegistryException, SchemaException {
// create surrounding polygon
final PrecisionModel pm = new PrecisionModel();
final GeometryFactory gf = new GeometryFactory(pm, 0);
final Rectangle2D rect = gridCoverageReader.getOriginalEnvelope()
.toRectangle2D();
final CoordinateReferenceSystem sourceCrs = CRS
.getHorizontalCRS(gridCoverageReader.getCrs());
if(sourceCrs==null)
throw new UnsupportedOperationException(
Errors.format(
ErrorKeys.CANT_SEPARATE_CRS_$1,gridCoverageReader.getCrs()));
final Coordinate[] coord = new Coordinate[5];
coord[0] = new Coordinate(rect.getMinX(), rect.getMinY());
coord[1] = new Coordinate(rect.getMaxX(), rect.getMinY());
coord[2] = new Coordinate(rect.getMaxX(), rect.getMaxY());
coord[3] = new Coordinate(rect.getMinX(), rect.getMaxY());
coord[4] = new Coordinate(rect.getMinX(), rect.getMinY());
// }
final LinearRing ring = gf.createLinearRing(coord);
final Polygon bounds = new Polygon(ring, null, gf);
SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder(getTypeFactory());
ftb.setName("GridCoverage");
ftb.add("geom", Polygon.class, sourceCrs);
ftb.add("grid", AbstractGridCoverage2DReader.class);
ftb.add("params", GeneralParameterValue[].class);
SimpleFeatureType schema = ftb.buildFeatureType();
// create the feature
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema, getFeatureFactory());
fb.add(bounds);
fb.add(gridCoverageReader);
fb.add(params);
SimpleFeature feature = fb.buildFeature(null);
final SimpleFeatureCollection collection = FeatureCollections.newCollection();
collection.add(feature);
return collection;
}
/**
* Checks if the feature type specified is a AbstractGridCoverage2DReader wrapper
* @param featureType
* @return
*/
public static boolean isWrappedCoverageReader(SimpleFeatureType featureType) {
if(!"GridCoverage".equals(featureType.getName().getLocalPart()))
return false;
if(featureType.getAttributeCount() != 3)
return false;
AttributeDescriptor polyDescriptor = featureType.getDescriptor("geom");
if(polyDescriptor == null || !Polygon.class.equals(polyDescriptor.getType().getBinding()))
return false;
AttributeDescriptor gridDescriptor = featureType.getDescriptor("grid");
if(gridDescriptor == null || !AbstractGridCoverage2DReader.class.equals(gridDescriptor.getType().getBinding()))
return false;
AttributeDescriptor paramDescriptor = featureType.getDescriptor("params");
if(paramDescriptor == null || !GeneralParameterValue[].class.equals(paramDescriptor.getType().getBinding()))
return false;
return true;
}
/**
* Converts a JTS {@link Polygon}, which represents a ROI, int an AWT
* {@link java.awt.Polygon} by means of the provided {@link MathTransform}.
*
* @param roiInput
* the input ROI as a JTS {@link Polygon}.
* @param worldToGridTransform
* the {@link MathTransform} to apply to the input ROI.
* @return an AWT {@link java.awt.Polygon}.
* @throws TransformException
* in case the provided {@link MathTransform} chokes.
*/
public static java.awt.Polygon convertPolygon(final Polygon roiInput,
MathTransform worldToGridTransform) throws TransformException {
return convertPolygonToPointArray(roiInput, worldToGridTransform, null);
}
/**
* Converts a JTS {@link Polygon}, which represents a ROI, int an AWT
* {@link java.awt.Polygon} by means of the provided {@link MathTransform}.
*
* <p>
* It also stores the points for this polygon into the provided {@link List}.
*
* @param roiInput
* the input ROI as a JTS {@link Polygon}.
* @param worldToGridTransform
* the {@link MathTransform} to apply to the input ROI.
* @param points
* a {@link List} that should hold the transformed points.
* @return an AWT {@link java.awt.Polygon}.
* @throws TransformException
* in case the provided {@link MathTransform} chokes.
*/
public static java.awt.Polygon convertPolygonToPointArray(final Polygon roiInput,
MathTransform worldToGridTransform, List<Point2D> points)
throws TransformException {
final boolean isIdentity = worldToGridTransform.isIdentity();
final double coords[] = new double[2];
final LineString exteriorRing = roiInput.getExteriorRing();
final CoordinateSequence exteriorRingCS = exteriorRing
.getCoordinateSequence();
final int numCoords = exteriorRingCS.size();
final java.awt.Polygon retValue = new java.awt.Polygon();
for (int i = 0; i < numCoords; i++) {
// get the actual coord
coords[0] = exteriorRingCS.getX(i);
coords[1] = exteriorRingCS.getY(i);
// transform it
if (!isIdentity)
worldToGridTransform.transform(coords, 0, coords, 0, 1);
// send it back to the returned polygon
final int x = (int) (coords[0] + 0.5d);
final int y = (int) (coords[1] + 0.5d);
if (points != null)
points.add(new Point2D.Double(coords[0],coords[1]));
// send it back to the returned polygon
retValue.addPoint(x, y);
}
// return the created polygon.
return retValue;
}
/**
* Convert the crop envelope into a polygon and the use the
* world-to-grid transform to get a ROI for the source coverage.
*/
public static Polygon getPolygon(final GeneralEnvelope env, final GeometryFactory gf) throws IllegalStateException, MismatchedDimensionException {
final Rectangle2D rect = env.toRectangle2D();
final Coordinate[] coord = new Coordinate[]{
new Coordinate(rect.getMinX(), rect.getMinY()),
new Coordinate(rect.getMinX(), rect.getMaxY()),
new Coordinate(rect.getMaxX(), rect.getMaxY()),
new Coordinate(rect.getMaxX(), rect.getMinY()),
new Coordinate(rect.getMinX(), rect.getMinY())};
final LinearRing ring = gf.createLinearRing(coord);
final Polygon modelSpaceROI = new Polygon(ring, null, gf);
// check that we have the same thing here
assert modelSpaceROI.getEnvelopeInternal().equals(new ReferencedEnvelope(rect, env.getCoordinateReferenceSystem()));
return modelSpaceROI;
}
/**
* Function to calculate the area of a polygon, according to the algorithm
* defined at http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
*
* @param polyPoints
* array of points in the polygon
* @return area of the polygon defined by pgPoints
*/
public static double area(Point2D[] polyPoints) {
Utilities.ensureNonNull("polyPoints", polyPoints);
int i, j, n = polyPoints.length;
double area = 0;
for (i = 0; i < n; i++) {
j = (i + 1) % n;
area += polyPoints[i].getX() * polyPoints[j].getY();
area -= polyPoints[j].getX() * polyPoints[i].getY();
}
area /= 2.0;
return (area);
}
}