/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, Open Source Geospatial Foundation (OSGeo)
* (C) 2001-2007 TOPP - www.openplans.org.
*
* 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.process.feature.gs;
import java.awt.geom.Point2D;
import javax.measure.converter.UnitConverter;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.LiteCoordinateSequence;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.process.gs.GSProcess;
import org.geotools.referencing.CRS;
import org.geotools.referencing.GeodeticCalculator;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.util.ProgressListener;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* Generates a set of polygons, each representing the set of points
* within a given distance from the central point The data layer must
* be a point layer, the reference layer must be a polygonal one"
*/
@DescribeProcess(title = "pointBuffers", description = "Generates a set of polygons, each representing the set of points " +
"within a given distance from the central point"
+ "The data layer must be a point layer, the reference layer must be a polygonal one")
public class PointBuffers implements GSProcess {
@DescribeResult(name = "buffers", description = "The buffers. Each feature has a 'geom' attribute and a 'radius' attribute")
public SimpleFeatureCollection execute(
@DescribeParameter(name = "center", description = "The buffers center") Point center,
@DescribeParameter(name = "crs", description = "The coordinate reference system "
+ "in which the point expressed and the points will be generated", min = 0) CoordinateReferenceSystem crs,
@DescribeParameter(name = "distances", description = "The buffer distances, in meters") double[] distances,
@DescribeParameter(name = "quadrantSegments", description = "Number of segments per quadrant "
+ "in the generated buffers (8 by default)", min = 0) Integer quadrantSegments,
ProgressListener listener) {
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.add("geom", Polygon.class, crs);
tb.add("radius", Double.class);
tb.setName("buffers");
SimpleFeatureType schema = tb.buildFeatureType();
if (quadrantSegments == null) {
quadrantSegments = 8;
}
// build the buffer geometry generator
BufferGenerator generator;
if (crs != null) {
CoordinateReferenceSystem hor = CRS.getHorizontalCRS(crs);
if (hor instanceof GeographicCRS) {
generator = new GeographicGenerator(center, quadrantSegments, crs);
} else {
Unit unit = hor.getCoordinateSystem().getAxis(0).getUnit();
UnitConverter converter = SI.METER.getConverterTo(unit);
generator = new MetricGenerator(center, quadrantSegments, converter);
}
} else {
generator = new MetricGenerator(center, quadrantSegments, UnitConverter.IDENTITY);
}
// we don't expect million of directions, so we use a simple in memory collection
SimpleFeatureCollection result = new ListFeatureCollection(schema);
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema);
for (int i = 0; i < distances.length; i++) {
fb.add(generator.getBuffer(distances[i]));
fb.add(distances[i]);
result.add(fb.buildFeature("buffers." + (i + 1)));
}
return result;
}
/**
* Generates a buffer
*
* @author Andrea Aime - GeoSolutions
*
*/
static abstract class BufferGenerator {
Point center;
int quadrantSegments;
public abstract Polygon getBuffer(double distance);
}
/**
* A generator that uses JTS buffer to create the buffer polygons
*/
public class MetricGenerator extends BufferGenerator {
UnitConverter converter;
public MetricGenerator(Point center, Integer quadrantSegments, UnitConverter converter) {
this.center = center;
this.quadrantSegments = quadrantSegments;
this.converter = converter;
}
@Override
public Polygon getBuffer(double distance) {
// convert to the target unit
distance = converter.convert(distance);
// buffer and return
return (Polygon) center.buffer(distance, quadrantSegments);
}
}
/**
* Builds the appropriate buffer polygons sampling the actual buffer shape with the
* GeodeticCalculator
*
* @author Andrea Aime - GeoSolutions
*/
public class GeographicGenerator extends BufferGenerator {
GeometryFactory gf = new GeometryFactory();
GeodeticCalculator calculator;
boolean latLon;
public GeographicGenerator(Point center, int quadrantSegments, CoordinateReferenceSystem crs) {
this.quadrantSegments = quadrantSegments;
this.center = center;
this.calculator = new GeodeticCalculator(crs);
latLon = isLatLonOrder(crs.getCoordinateSystem());
if (latLon) {
calculator.setStartingGeographicPoint(center.getY(), center.getX());
} else {
calculator.setStartingGeographicPoint(center.getX(), center.getY());
}
}
@Override
public Polygon getBuffer(double distance) {
CoordinateSequence cs = new LiteCoordinateSequence(quadrantSegments * 4 + 1, 2);
for (int i = 0; i < (cs.size() - 1); i++) {
double azimuth = 360.0 * i / cs.size() - 180;
calculator.setDirection(azimuth, distance);
Point2D dp = calculator.getDestinationGeographicPoint();
if (latLon) {
cs.setOrdinate(i, 0, dp.getY());
cs.setOrdinate(i, 1, dp.getX());
} else {
cs.setOrdinate(i, 0, dp.getX());
cs.setOrdinate(i, 1, dp.getY());
}
}
cs.setOrdinate(cs.size() - 1, 0, cs.getOrdinate(0, 0));
cs.setOrdinate(cs.size() - 1, 1, cs.getOrdinate(0, 1));
return gf.createPolygon(gf.createLinearRing(cs), null);
}
}
boolean isLatLonOrder(CoordinateSystem cs) {
int dimension = cs.getDimension();
int longitudeDim = -1;
int latitudeDim = -1;
for (int i = 0; i < dimension; i++) {
AxisDirection dir = cs.getAxis(i).getDirection().absolute();
if (dir.equals(AxisDirection.EAST)) {
longitudeDim = i;
}
if (dir.equals(AxisDirection.NORTH)) {
latitudeDim = i;
}
}
if ((longitudeDim >= 0) && (latitudeDim >= 0)) {
if (longitudeDim > latitudeDim) {
return true;
}
}
return false;
}
}