/*
* The Java Conflation Suite (JCS) is a library of Java classes that
* can be used to build automated or semi-automated conflation solutions.
*
* Copyright (C) 2003 Vivid Solutions
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jcs.conflate.polygonmatch;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.feature.*;
import com.vividsolutions.jump.task.TaskMonitor;
import com.vividsolutions.jump.util.CollectionMap;
import com.vividsolutions.jump.util.CollectionUtil;
import com.vividsolutions.jump.util.CoordinateArrays;
import java.util.*;
/**
* An FCMatchFinder wrapper that also treats pairs of adjacent target features
* as themselves target features. Such pairs are formed into composite target
* features. These composites are temporary -- before the results are returned,
* each composite is split into its constituent features. <P>
*
* The result returned is a one-to-one mapping of target feature to matched
* candidate feature; the one-to-one mapping is achieved by discarding all
* matches except for those with the highest scores, for each feature (target
* and matched candidate). <P>
*
* Note on composites: if a composite's top score is higher than the top score
* of each of its constituents, the composite match is retained and constituent
* matches are discarded; otherwise, the composite match is discarded and
* constituent matches are retained.
*/
public class CombinatorialFCMatchFinder implements FCMatchFinder {
private FCMatchFinder matchFinder;
private int maxCompositeSize;
/**
*@param maxCompositeSize the maximum number of adjacent target features to
* try combining
*@param matchFinder the FCMatchFinder to wrap
*/
public CombinatorialFCMatchFinder(int maxCompositeSize, FCMatchFinder matchFinder) {
this.maxCompositeSize = maxCompositeSize;
this.matchFinder = new OneToOneFCMatchFinder(matchFinder);
}
public Map match(IndexedFeatureCollection targetFC, IndexedFeatureCollection candidateFC,
TaskMonitor monitor) {
monitor.allowCancellationRequests();
FeatureCollection compositeTargetFC = new FeatureDataset(targetFC.getFeatureSchema());
CollectionMap constituentToCompositesMap = new CollectionMap();
createComposites(targetFC, constituentToCompositesMap, compositeTargetFC, monitor);
Map targetFeatureToMatchesMap = matchFinder.match(
new IndexedFeatureCollection(compositeTargetFC),
candidateFC, monitor);
deleteInferiorComposites(targetFeatureToMatchesMap, constituentToCompositesMap, monitor);
return splitComposites(targetFeatureToMatchesMap, monitor);
}
protected void createComposites(FeatureCollection fc, CollectionMap constituentToCompositesMap, FeatureCollection compositeFC, TaskMonitor monitor) {
Assert.isTrue(constituentToCompositesMap.isEmpty());
Assert.isTrue(compositeFC.isEmpty());
Set composites = createCompositeSet(fc, monitor);
add(composites, constituentToCompositesMap, monitor);
add(composites, compositeFC, monitor);
}
/**
* Removes from the compositeToMatchesMap any composites sharing constituents
* with other composites but having a lower match score than any of the other
* composites.
*/
protected void deleteInferiorComposites(Map compositeToMatchesMap, CollectionMap constituentToCompositesMap, TaskMonitor monitor) {
monitor.report("Discarding inferior composites");
int featuresProcessed = 0;
int totalFeatures = constituentToCompositesMap.size();
for (Iterator i = constituentToCompositesMap.keySet().iterator(); i.hasNext() && ! monitor.isCancelRequested(); ) {
Feature constituent = (Feature) i.next();
featuresProcessed++;
monitor.report(featuresProcessed, totalFeatures, "features");
Collection composites = constituentToCompositesMap.getItems(constituent);
Assert.isTrue(!composites.isEmpty());
double bestScore = -1;
CompositeFeature bestComposite = null;
Matches bestMatches = null;
for (Iterator j = composites.iterator(); j.hasNext(); ) {
CompositeFeature composite = (CompositeFeature) j.next();
Matches matches = (Matches) compositeToMatchesMap.get(composite);
if (matches == null) {
continue;
}
if (matches.getTopScore() > bestScore) {
bestScore = matches.getTopScore();
bestComposite = composite;
bestMatches = matches;
}
}
CollectionUtil.removeKeys(composites, compositeToMatchesMap);
if (bestMatches == null) {
continue;
}
compositeToMatchesMap.put(bestComposite, bestMatches);
}
}
protected List featuresWithCommonEdge(Feature feature, FeatureCollection fc) {
ArrayList featuresWithCommonEdge = new ArrayList();
List candidates = fc.query(feature.getGeometry().getEnvelopeInternal());
for (Iterator i = candidates.iterator(); i.hasNext(); ) {
Feature candidate = (Feature) i.next();
if (feature == candidate || shareEdge(feature.getGeometry(), candidate.getGeometry())) {
featuresWithCommonEdge.add(candidate);
}
}
return featuresWithCommonEdge;
}
protected boolean shareEdge(Geometry a, Geometry b) {
Set aEdges = edges(a);
Set bEdges = edges(b);
for (Iterator i = bEdges.iterator(); i.hasNext(); ) {
Edge bEdge = (Edge) i.next();
if (aEdges.contains(bEdge)) { return true; }
}
return false;
}
@Override
public Map match(FeatureCollection targetFC, FeatureCollection candidateFC, TaskMonitor monitor) {
throw new UnsupportedOperationException("Not supported yet.");
}
private static class Edge implements Comparable {
private Coordinate p0, p1;
public Edge(Coordinate a, Coordinate b) {
if (a.compareTo(b) < 1) {
p0 = a;
p1 = b;
}
else {
p0 = b;
p1 = a;
}
}
@Override
public int compareTo(Object o) {
Edge other = (Edge) o;
int result = p0.compareTo(other.p0);
if (result != 0) return result;
return p1.compareTo(other.p1);
}
}
private Set edges(Geometry g) {
TreeSet edges = new TreeSet();
for (Iterator i = CoordinateArrays.toCoordinateArrays(g, false).iterator(); i.hasNext(); ) {
Coordinate[] coordinates = (Coordinate[]) i.next();
for (int j = 1; j < coordinates.length; j++) { //1
edges.add(new Edge(coordinates[j], coordinates[j-1]));
}
}
return edges;
}
/**
* Splits each composite target into its constituent features.
*/
protected Map splitComposites(Map compositeToMatchesMap, TaskMonitor monitor) {
monitor.report("Splitting composites");
int compositesProcessed = 0;
int totalComposites = compositeToMatchesMap.size();
Map newMap = new HashMap();
for (Iterator i = compositeToMatchesMap.keySet().iterator(); i.hasNext() && ! monitor.isCancelRequested(); ) {
CompositeFeature composite = (CompositeFeature) i.next();
compositesProcessed++;
monitor.report(compositesProcessed, totalComposites, "composites");
Matches matches = (Matches) compositeToMatchesMap.get(composite);
//Because we use OneToOneFCMatchFinder, all targets will be associated
//with one and only one match.
Assert.isTrue(1 == matches.size());
for (Iterator j = composite.getFeatures().iterator(); j.hasNext(); ) {
Feature constituent = (Feature) j.next();
Assert.isTrue(!newMap.containsKey(constituent));
Matches matchesCopy = new Matches(matches.getFeatureSchema());
matchesCopy.add(matches.getTopMatch(), matches.getTopScore());
newMap.put(constituent, matchesCopy);
}
}
return newMap;
}
private Set createCompositeSet(FeatureCollection fc, TaskMonitor monitor) {
monitor.report("Creating composites of adjacent features");
int featuresProcessed = 0;
int totalFeatures = fc.getFeatures().size();
//Use a Set to prevent duplicate composites [Jon Aquino]
HashSet composites = new HashSet();
for (Iterator i = fc.getFeatures().iterator(); i.hasNext() && !monitor.isCancelRequested(); ) {
Feature feature = (Feature) i.next();
featuresProcessed++;
monitor.report(featuresProcessed, totalFeatures, "features");
List featuresWithCommonEdge = featuresWithCommonEdge(feature, fc);
for (Iterator j = CollectionUtil.combinations(
featuresWithCommonEdge, maxCompositeSize, feature).iterator(); j.hasNext() && !monitor.isCancelRequested(); ) {
List combination = (List) j.next();
composites.add(new CompositeFeature(fc.getFeatureSchema(), combination));
}
}
return composites;
}
private void add(Set composites, CollectionMap constituentToCompositesMap, TaskMonitor monitor) {
monitor.report("Creating feature-to-composite map");
int compositesProcessed = 0;
int totalComposites = composites.size();
for (Iterator i = composites.iterator(); i.hasNext() && !monitor.isCancelRequested(); ) {
CompositeFeature composite = (CompositeFeature) i.next();
compositesProcessed++;
monitor.report(compositesProcessed, totalComposites, "composites");
for (Iterator j = composite.getFeatures().iterator(); j.hasNext() && !monitor.isCancelRequested(); ) {
Feature constituent = (Feature) j.next();
constituentToCompositesMap.addItem(constituent, composite);
}
}
}
public static class CompositeFeature extends BasicFeature {
private List features;
private int hashCode;
public CompositeFeature(FeatureSchema schema, List features) {
super(schema);
this.features = features;
Geometry union = ((Feature) features.get(0)).getGeometry();
hashCode = ((Feature) features.get(0)).hashCode();
for (int i = 1; i < features.size(); i++) {
Feature feature = (Feature) features.get(i);
union = union.union(feature.getGeometry());
hashCode = Math.min(hashCode, feature.hashCode());
}
setGeometry(union);
}
public List getFeatures() {
return features;
}
@Override
public boolean equals(Object obj) {
Assert.isTrue(obj instanceof CompositeFeature, obj.getClass().toString());
CompositeFeature other = (CompositeFeature) obj;
if (features.size() != other.features.size()) {
return false;
}
for (Iterator i = features.iterator(); i.hasNext(); ) {
Feature myFeature = (Feature) i.next();
if (!other.features.contains(myFeature)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashCode;
}
}
private void add(Collection features, FeatureCollection fc, TaskMonitor monitor) {
monitor.report("Building feature-collection");
fc.addAll(features);
}
}