/*
* 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.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.process.spatialstatistics.enumeration.DistanceMethod;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
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.Point;
import com.vividsolutions.jts.index.strtree.ItemBoundable;
import com.vividsolutions.jts.index.strtree.ItemDistance;
import com.vividsolutions.jts.index.strtree.STRtree;
/**
* Help class for distance calculation
*
* @author Minpa Lee, MangoSystem
*
* @source $URL$
*/
public class DistanceFactory {
protected static final Logger LOGGER = Logging.getLogger(DistanceFactory.class);
static GeometryFactory gf = JTSFactoryFinder.getGeometryFactory(GeoTools.getDefaultHints());
private DistanceMethod distanceType = DistanceMethod.Euclidean;
public DistanceMethod getDistanceType() {
return distanceType;
}
public void setDistanceType(DistanceMethod distanceType) {
this.distanceType = distanceType;
}
public static DistanceFactory newInstance() {
return new DistanceFactory();
}
public double getMeanDistance(List<SpatialEvent> spatialEventSet, SpatialEvent curEvent) {
double sumDistance = 0.0;
for (SpatialEvent destEvent : spatialEventSet) {
if (destEvent.id.equals(curEvent.id)) {
continue;
}
sumDistance += getDistance(curEvent, destEvent, distanceType);
}
return sumDistance / spatialEventSet.size();
}
public double getThresholDistance(SimpleFeatureCollection features) {
// build spatial index
final List<SpatialEvent> events = new ArrayList<SpatialEvent>();
final STRtree spatialIndex = new STRtree();
SimpleFeatureIterator featureIter = features.features();
try {
while (featureIter.hasNext()) {
SimpleFeature feature = featureIter.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
Coordinate centroid = geometry.getCentroid().getCoordinate();
SpatialEvent event = new SpatialEvent(feature.getID(), centroid);
events.add(event);
spatialIndex.insert(new Envelope(centroid), event);
}
} finally {
featureIter.close();
}
// calculate nearest neighbor index
double threshold = Double.MIN_VALUE;
for (SpatialEvent source : events) {
SpatialEvent nearest = (SpatialEvent) spatialIndex.nearestNeighbour(new Envelope(
source.coordinate), source, new ItemDistance() {
@Override
public double distance(ItemBoundable item1, ItemBoundable item2) {
SpatialEvent s1 = (SpatialEvent) item1.getItem();
SpatialEvent s2 = (SpatialEvent) item2.getItem();
if (s1.id.equals(s2.id)) {
return Double.MAX_VALUE;
}
return s1.distance(s2);
}
});
threshold = Math.max(threshold, getDistance(source, nearest));
}
return threshold * 1.0001;
}
public double getMinimumDistance(List<SpatialEvent> srcEvents, SpatialEvent curEvent) {
double minDistance = Double.MAX_VALUE;
for (SpatialEvent destEvent : srcEvents) {
if (destEvent.id.equals(curEvent.id)) {
continue;
}
minDistance = Math.min(minDistance, getDistance(curEvent, destEvent, distanceType));
if (minDistance == 0d) {
return minDistance;
}
}
return minDistance;
}
public double getMaximumDistance(List<SpatialEvent> srcEvents, SpatialEvent curEvent) {
double maxDistance = Double.MIN_VALUE;
for (SpatialEvent destEvent : srcEvents) {
if (destEvent.id.equals(curEvent.id)) {
continue;
}
maxDistance = Math.max(maxDistance, getDistance(curEvent, destEvent, distanceType));
}
return maxDistance;
}
public SpatialEvent getMeanCenter(List<SpatialEvent> spatialEventSet, boolean useWeight) {
double sumX = 0.0, sumY = 0.0, sumWeight = 0.0;
double weight = 1;
for (SpatialEvent curEvent : spatialEventSet) {
weight = useWeight ? curEvent.xVal : 1;
sumWeight += weight;
sumX += (curEvent.getCoordinate().x * weight);
sumY += (curEvent.getCoordinate().y * weight);
}
double centerX = sumX / sumWeight;
double centerY = sumY / sumWeight;
return new SpatialEvent(0, new Coordinate(centerX, centerY), 0);
}
public List<SpatialEvent> getCentralFeature(List<SpatialEvent> spatialEventSet,
boolean useWeight) {
List<SpatialEvent> centralEvents = new ArrayList<SpatialEvent>();
if (spatialEventSet.size() <= 2) {
return spatialEventSet;
} else {
double minDistance = Double.MAX_VALUE;
SpatialEvent centralEvent = new SpatialEvent(0);
for (SpatialEvent curEvent : spatialEventSet) {
double curDistance = 0;
for (SpatialEvent destEvent : spatialEventSet) {
if (curEvent.id != destEvent.id) {
if (useWeight) {
curDistance += getDistance(curEvent, destEvent, distanceType)
* destEvent.xVal;
} else {
curDistance += getDistance(curEvent, destEvent, distanceType);
}
}
}
if (minDistance > curDistance) {
minDistance = curDistance;
centralEvent = curEvent;
}
}
centralEvents.add(centralEvent);
}
return centralEvents;
}
public SpatialEvent getStandardDistance(List<SpatialEvent> spatialEventSet,
double standardDeviation, boolean useWeight) {
double diffX = 0, diffY = 0, diffDist = 0;
double sumWeight = 0.0;
double weight = 1.0;
SpatialEvent centerPoint = getMeanCenter(spatialEventSet, useWeight);
for (SpatialEvent curEvent : spatialEventSet) {
weight = useWeight ? curEvent.xVal : 1.0;
sumWeight += weight;
diffX = curEvent.getCoordinate().x - centerPoint.getCoordinate().x;
diffY = curEvent.getCoordinate().y - centerPoint.getCoordinate().y;
diffDist += (diffX * diffX * weight) + (diffY * diffY * weight);
}
double stdDistance = Math.sqrt(diffDist / sumWeight) * standardDeviation;
return new SpatialEvent(0, centerPoint.getCoordinate(), stdDistance);
}
public Geometry getStandardDistanceAsCircle(List<SpatialEvent> spatialEventSet,
double standardDeviation, boolean useWeight) {
SpatialEvent sdtPoint = getStandardDistance(spatialEventSet, standardDeviation, useWeight);
Point centerPoint = gf.createPoint(sdtPoint.getCoordinate());
// for degree in NUM.arange(0, 360):
return centerPoint.buffer(sdtPoint.xVal * standardDeviation, 90, 1);
}
public double getDistance(SpatialEvent origEvent, SpatialEvent destEvent) {
switch (distanceType) {
case Euclidean:
return getEuclideanDistance(origEvent, destEvent);
case Manhattan:
return getManhattanDistance(origEvent, destEvent);
}
return getEuclideanDistance(origEvent, destEvent);
}
public double getDistance(SpatialEvent origEvent, SpatialEvent destEvent,
DistanceMethod distanceMethod) {
switch (distanceMethod) {
case Euclidean:
return getEuclideanDistance(origEvent, destEvent);
case Manhattan:
return getManhattanDistance(origEvent, destEvent);
}
return getEuclideanDistance(origEvent, destEvent);
}
public double getEuclideanDistance(SpatialEvent origEvent, SpatialEvent destEvent) {
return origEvent.distance(destEvent);
}
public double getEuclideanDistance(double x1, double y1, double x2, double y2) {
double dx = x1 - x2;
double dy = y1 - y2;
return Math.sqrt((dx * dx) + (dy * dy));
}
public double getManhattanDistance(SpatialEvent origEvent, SpatialEvent destEvent) {
// it is |x1 - x2| + |y1 - y2|.
double dx = Math.abs(origEvent.getCoordinate().x - destEvent.getCoordinate().x);
double dy = Math.abs(origEvent.getCoordinate().y - destEvent.getCoordinate().y);
return dx + dy;
}
public static List<SpatialEvent> loadEvents(SimpleFeatureSource featureSource, Filter filter,
String weightField) {
filter = filter == null ? Filter.INCLUDE : filter;
SimpleFeatureCollection features = null;
try {
features = featureSource.getFeatures(filter);
return loadEvents(features, weightField);
} catch (IOException e) {
LOGGER.log(Level.FINE, e.getMessage(), e);
}
return null;
}
public static List<SpatialEvent> loadEvents(SimpleFeatureCollection features, String weightField) {
List<SpatialEvent> events = new ArrayList<SpatialEvent>();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
weightField = FeatureTypes.validateProperty(features.getSchema(), weightField);
Expression expression = ff.property(weightField);
SimpleFeatureIterator featureIter = features.features();
try {
while (featureIter.hasNext()) {
SimpleFeature feature = featureIter.next();
Geometry origGeom = (Geometry) feature.getDefaultGeometry();
Coordinate coordinate = origGeom.getCentroid().getCoordinate();
Double weight = expression.evaluate(feature, Double.class);
SpatialEvent sEvent = new SpatialEvent(feature.getID(), coordinate);
sEvent.xVal = weight == null ? 1.0 : weight;
events.add(sEvent);
}
} finally {
featureIter.close();
}
return events;
}
}