/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2014, 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.process.spatialstatistics.operations;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.process.spatialstatistics.core.FeatureTypes;
import org.geotools.process.spatialstatistics.storage.IFeatureInserter;
import org.geotools.process.spatialstatistics.transformation.ReprojectFeatureCollection;
import org.geotools.referencing.CRS;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
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;
import com.vividsolutions.jts.index.strtree.ItemBoundable;
import com.vividsolutions.jts.index.strtree.ItemDistance;
import com.vividsolutions.jts.index.strtree.STRtree;
import com.vividsolutions.jts.linearref.LengthIndexedLine;
/**
* Creates a line features representing the shortest distance between hub and spoke features by nearest distance.
*
* @author Minpa Lee, MangoSystem
*
* @source $URL$
*
*/
public class HubLinesByDistanceOperation extends AbstractHubLinesOperation {
protected static final Logger LOGGER = Logging.getLogger(HubLinesByDistanceOperation.class);
public SimpleFeatureCollection execute(SimpleFeatureCollection hubFeatures, String hubIdField,
SimpleFeatureCollection spokeFeatures, boolean useCentroid, boolean preserveAttributes,
double maximumDistance) throws IOException {
this.setPreserveAttributes(preserveAttributes);
this.setMaximumDistance(maximumDistance);
this.setUseCentroid(useCentroid);
return execute(hubFeatures, hubIdField, spokeFeatures);
}
public SimpleFeatureCollection execute(SimpleFeatureCollection hubFeatures, String hubIdField,
SimpleFeatureCollection spokeFeatures) throws IOException {
SimpleFeatureType spokeSchema = spokeFeatures.getSchema();
CoordinateReferenceSystem crs = spokeSchema.getCoordinateReferenceSystem();
SimpleFeatureType featureType = null;
if (preserveAttributes) {
featureType = FeatureTypes.build(spokeSchema, TYPE_NAME, LineString.class);
} else {
String geomName = spokeSchema.getGeometryDescriptor().getLocalName();
featureType = FeatureTypes.getDefaultType(TYPE_NAME, geomName, LineString.class, crs);
}
boolean hasHubID = hubIdField != null && hubFeatures.getSchema().indexOf(hubIdField) != -1;
if (hasHubID) {
AttributeDescriptor hubIdDesc = hubFeatures.getSchema().getDescriptor(hubIdField);
featureType = FeatureTypes.add(featureType, hubIdDesc);
}
featureType = FeatureTypes.add(featureType, HUB_DIST, Double.class, 38);
// check coordinate reference system
CoordinateReferenceSystem crsT = spokeFeatures.getSchema().getCoordinateReferenceSystem();
CoordinateReferenceSystem crsS = hubFeatures.getSchema().getCoordinateReferenceSystem();
if (crsT != null && crsS != null && !CRS.equalsIgnoreMetadata(crsT, crsS)) {
hubFeatures = new ReprojectFeatureCollection(hubFeatures, crsS, crsT, true);
LOGGER.log(Level.WARNING, "reprojecting features");
}
// build spatial index
STRtree spatialIndex = loadHubs(hubFeatures, hubIdField);
// prepare transactional feature store
IFeatureInserter featureWriter = getFeatureWriter(featureType);
SimpleFeatureIterator spokeIter = spokeFeatures.features();
try {
while (spokeIter.hasNext()) {
SimpleFeature feature = spokeIter.next();
Geometry spokeGeom = (Geometry) feature.getDefaultGeometry();
Object id = hasHubID ? feature.getAttribute(hubIdField) : feature.getID();
if (useCentroid) {
spokeGeom = getCentroid(spokeGeom);
}
// find nearest hub
Hub nearest = (Hub) spatialIndex.nearestNeighbour(spokeGeom.getEnvelopeInternal(),
new Hub(spokeGeom, id), new ItemDistance() {
@Override
public double distance(ItemBoundable item1, ItemBoundable item2) {
Hub s1 = (Hub) item1.getItem();
Hub s2 = (Hub) item2.getItem();
return s1.location.distance(s2.location);
}
});
// create line: direction = hub --> spoke
Geometry hubLine = getShortestLine(nearest.location, spokeGeom, false);
double distance = hubLine.getLength();
if (distance == 0 || this.maximumDistance < distance) {
continue;
}
// create & insert feature
SimpleFeature newFeature = featureWriter.buildFeature();
if (preserveAttributes) {
featureWriter.copyAttributes(feature, newFeature, false);
}
newFeature.setDefaultGeometry(hubLine);
if (hasHubID) {
newFeature.setAttribute(hubIdField, nearest.id);
}
newFeature.setAttribute(HUB_DIST, distance);
featureWriter.write(newFeature);
}
} catch (IOException e) {
featureWriter.rollback(e);
} finally {
featureWriter.close(spokeIter);
}
return featureWriter.getFeatureCollection();
}
private STRtree loadHubs(SimpleFeatureCollection features, String idField) {
STRtree spatialIndex = new STRtree();
boolean hasID = idField != null && features.getSchema().indexOf(idField) != -1;
SimpleFeatureIterator featureIter = features.features();
try {
while (featureIter.hasNext()) {
SimpleFeature feature = featureIter.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
if (useCentroid) {
geometry = getCentroid(geometry);
}
Object id = hasID ? feature.getAttribute(idField) : feature.getID();
Hub hub = new Hub(geometry, id);
spatialIndex.insert(geometry.getEnvelopeInternal(), hub);
}
} finally {
featureIter.close();
}
return spatialIndex;
}
private Geometry getCentroid(Geometry input) {
Class<?> geomBinding = input.getClass();
if (MultiPolygon.class.equals(geomBinding)) {
if (input.getNumGeometries() == 1) {
return getCentroid(input.getGeometryN(0));
}
// largest area
return getCentroid(getLargestGeometry(input, false));
} else if (Polygon.class.equals(geomBinding)) {
// point in polygon
Point center = input.getCentroid();
if (!input.contains(center)) {
center = input.getInteriorPoint();
}
return center;
} else if (MultiLineString.class.equals(geomBinding)) {
if (input.getNumGeometries() == 1) {
return getCentroid(input.getGeometryN(0));
}
// largest length
return getCentroid(getLargestGeometry(input, true));
} else if (LineString.class.equals(geomBinding)) {
// point on linestring
LengthIndexedLine lil = new LengthIndexedLine(input);
double index = input.getLength() * 0.5;
Coordinate midpoint = lil.extractPoint(index);
return input.getFactory().createPoint(midpoint);
} else if (MultiPoint.class.equals(geomBinding)) {
// central point
return getCentralPoint((MultiPoint) input);
} else if (Point.class.equals(geomBinding)) {
return input;
}
return input.getCentroid();
}
private Geometry getLargestGeometry(Geometry multi, boolean useLength) {
Geometry largest = null;
double max = Double.MIN_VALUE;
for (int i = 0; i < multi.getNumGeometries(); i++) {
Geometry part = multi.getGeometryN(i);
double cur = useLength ? part.getLength() : part.getArea();
if (max < cur) {
max = cur;
largest = part;
}
}
return largest;
}
private Point getCentralPoint(MultiPoint multiPoint) {
int numPoints = multiPoint.getNumPoints();
if (numPoints == 1) {
return (Point) multiPoint.getGeometryN(0);
}
double minDistance = Double.MAX_VALUE;
Point centralPoint = null;
for (int i = 0; i < numPoints; i++) {
Point ce = (Point) multiPoint.getGeometryN(i);
double curDistance = 0d;
for (int j = 0; j < numPoints; j++) {
Point de = (Point) multiPoint.getGeometryN(j);
if (i != j) {
curDistance += ce.distance(de);
}
}
if (minDistance > curDistance) {
minDistance = curDistance;
centralPoint = ce;
}
}
return centralPoint;
}
static final class Hub {
public Geometry location;
public Object id;
public Hub(Geometry location, Object id) {
this.location = location;
this.id = id;
}
}
}