/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004, Refractions Research Inc.
*
* 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 net.refractions.linecleaner;
import java.io.IOException;
import java.util.Iterator;
import net.refractions.linecleaner.GeometryUtil.CoordinateFunction;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.memory.MemoryFeatureCollection;
import org.geotools.factory.FactoryConfigurationError;
import org.geotools.feature.AttributeType;
import org.geotools.feature.AttributeTypeFactory;
import org.geotools.feature.Feature;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureType;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.SchemaException;
import org.geotools.filter.IllegalFilterException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
/**
* <p>
* Utility class dealing with the calculation of similarity between LineStrings.
* Similarity is defined as the average distance between two lines.
* </p>
*/
public class SimilarityMetric {
/**
*
* @param source
* @return All the LineString/MultiLineString features with similarity added as an
* attribute named "Similarity".
* @throws IOException
* @throws SchemaException
* @throws FactoryConfigurationError
* @throws IllegalFilterException
* @throws IllegalAttributeException
*/
public static FeatureCollection addSimilarityMetric(FeatureSource source, double intervalLength)
throws IOException, SchemaException, FactoryConfigurationError,
IllegalFilterException, IllegalAttributeException {
return addSimilarityMetric(source, "Similarity", intervalLength);
}
/**
*
* @param source
* @param attributeName Name of the attribute in which the similarity metric will be
* written.
* @return All the LineString/MultiLineString features with similarity added as an
* attribute named "Similarity".
* @throws IOException
* @throws SchemaException
* @throws FactoryConfigurationError
* @throws IllegalFilterException
* @throws IllegalAttributeException
*/
public static FeatureCollection addSimilarityMetric(FeatureSource source, String attributeName, double intervalLength)
throws IOException, SchemaException, FactoryConfigurationError,
IllegalFilterException, IllegalAttributeException {
FeatureCollection fc = source.getFeatures();
AttributeType similarityType = AttributeTypeFactory.newAttributeType(attributeName,
Double.class, true, Double.SIZE, 0.0);
FeatureType ft = FeatureUtil.addAttribute(fc.getSchema(), similarityType);
FeatureCollection out = new MemoryFeatureCollection(ft);
int count = fc.size();
int n = 1;
for (Iterator i = fc.iterator(); i.hasNext();) {
System.out.println("on feature # " + n++ + " of " + count);
Feature f = (Feature)i.next();
Feature similarFeature = DataUtilities.reType(ft, f);
double sim = similarityMetric(source.getFeatures(), f, intervalLength);
similarFeature.setAttribute(similarityType.getName(), sim);
out.add(similarFeature);
}
return out;
}
/**
*
* @param source
* @param f
* @return Similarity metric for f from source.
* @throws FactoryConfigurationError
*/
private static double similarityMetric( FeatureCollection features, Feature f, double intervalLength )
throws FactoryConfigurationError {
FeatureCollection nearbyFeatures = FeatureUtil.nearbyFeatures(features, f);
double sim = 0;
for (Iterator j = nearbyFeatures.iterator(); j.hasNext();) {
Feature g = (Feature)j.next();
double currentSim = similarity(f.getDefaultGeometry(),
g.getDefaultGeometry(), intervalLength);
if (sim == 0) {
sim = currentSim;
} else {
sim = Math.min(sim, currentSim);
}
}
return sim;
}
/**
*
* @param g
* @param h
* @param d subdivision distance
* @return similarity between g and h
*/
public static double similarity(Geometry g, Geometry h, double d) {
LineString line = GeometryUtil.extractLine(g);
if (line == null) {
return 0.0;
}
Coordinate[] gPoints = GeometryUtil.subdivide(line, d);
double sumDifferences = 0;
for (int i = 0; i < gPoints.length; i++) {
Point p = g.getFactory().createPoint(gPoints[i]);
sumDifferences += p.distance(h);
}
return sumDifferences / (gPoints.length - 1);
}
/**
*
* @param g
* @param h
* @param d subdivision distance
* @return similarity between g and h
*/
public double similarityF(Geometry g, Geometry h, double d) {
LineString line = GeometryUtil.extractLine(g);
if (line == null) {
return 0.0;
}
SimilarityClosure similarityClosure = new SimilarityClosure(h);
GeometryUtil.subdivide(line, d, similarityClosure);
return similarityClosure.getSimilarity();
}
private class SimilarityClosure implements CoordinateFunction {
double sumDifferences = 0.0;
Geometry target;
int numPointsVisited = 0;
public SimilarityClosure(Geometry g) {
this.target = g;
}
public void run(Coordinate c) {
Point p = this.target.getFactory().createPoint(c);
this.sumDifferences += p.distance(this.target);
this.numPointsVisited++;
}
public double getSimilarity() {
return this.sumDifferences / (numPointsVisited-1);
}
}
}