/*
* The JCS 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.util.Assert;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureCollection;
import com.vividsolutions.jump.task.TaskMonitor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Enforces a one-to-one relationship between target features and
* matched candidate features, in the returned result set.
* "Conservative" because only top matches are allowed.
* <P>
* <b>Note:</b> DisambiguatingFCMatchFinder seems to give better results.
* <P>
* Example: OneToOneFCMatchFinder wraps a FCMatchFinder that returns the
* following matches (and scores): T1-C1 (0.8), T2-C1 (0.9), T2-C2 (0.8),
* T2-C3 (1.0), T3-C4 (0.5). T1 and T2 are from the target dataset, whereas
* C1, C2, and C3 are from the candidate dataset. OneToOneFCMatchFinder filters
* out all matches except the top ones, for each feature, leaving:
* T2-C3 (1.0), T3-C4 (0.5).
* * @see DisambiguatingFCMatchFinder
*/
public class TopMatchDisambiguatingFCMatchFinder implements FCMatchFinder {
private FCMatchFinder matchFinder;
public TopMatchDisambiguatingFCMatchFinder(FCMatchFinder matchFinder) {
this.matchFinder = matchFinder;
}
@Override
public Map match(
FeatureCollection targetFC,
FeatureCollection candidateFC,
TaskMonitor monitor) {
Map originalTargetToMatchesMap =
matchFinder.match(targetFC, candidateFC, monitor);
monitor.allowCancellationRequests();
monitor.report("Finding best forward matches");
Map bestForwardMatches = filterMatches(originalTargetToMatchesMap, monitor);
monitor.report("Finding best reverse matches");
Map bestReverseMatches =
filterMatches(invert(originalTargetToMatchesMap, monitor), monitor);
monitor.report("Finding common best matches");
//Want matches that are "best" regardless of whether forward or reverse.
//This is the only scheme I can think of right now that will satisfy
//the case described in the class comment. [Jon Aquino]
Map filteredTargetToMatchesMap =
commonMatches(
bestForwardMatches,
invert(bestReverseMatches, monitor),
monitor);
//Put back the targets that were filtered out (albeit with no matches). [Jon Aquino]
Map targetToMatchesMap =
AreaFilterFCMatchFinder.blankTargetToMatchesMap(
targetFC.getFeatures(),
candidateFC.getFeatureSchema());
targetToMatchesMap.putAll(filteredTargetToMatchesMap);
return targetToMatchesMap;
}
private Map commonMatches(
Map featureToMatchesMap1,
Map featureToMatchesMap2,
TaskMonitor monitor) {
int featuresProcessed = 0;
int totalFeatures = featureToMatchesMap1.size();
Map commonMatches = new HashMap();
for (Iterator i = featureToMatchesMap1.keySet().iterator();
i.hasNext() && !monitor.isCancelRequested();
) {
Feature key1 = (Feature) i.next();
featuresProcessed++;
monitor.report(featuresProcessed, totalFeatures, "features");
if (!featureToMatchesMap2.containsKey(key1)) {
continue;
}
Matches matches1 = (Matches) featureToMatchesMap1.get(key1);
Matches matches2 = (Matches) featureToMatchesMap2.get(key1);
if (matches1.getTopMatch() == matches2.getTopMatch()) {
Assert.isTrue(matches1.getTopScore() == matches2.getTopScore());
commonMatches.put(key1, matches1);
}
}
return commonMatches;
}
private Map filterMatches(Map featureToMatchesMap, TaskMonitor monitor) {
int featuresProcessed = 0;
int totalFeatures = featureToMatchesMap.size();
HashMap newMap = new HashMap();
if (featureToMatchesMap.isEmpty()) {
return newMap;
}
for (Iterator i = featureToMatchesMap.keySet().iterator();
i.hasNext() && !monitor.isCancelRequested();
) {
Feature feature = (Feature) i.next();
featuresProcessed++;
monitor.report(featuresProcessed, totalFeatures, "features filtered");
Matches oldMatches = (Matches) featureToMatchesMap.get(feature);
if (oldMatches.isEmpty()) {
continue;
}
Matches newMatches = new Matches(oldMatches.getFeatureSchema());
newMatches.add(oldMatches.getTopMatch(), oldMatches.getTopScore());
newMap.put(feature, newMatches);
}
return newMap;
}
protected Map invert(Map featureToMatchesMap, TaskMonitor monitor) {
int featuresProcessed = 0;
int totalFeatures = featureToMatchesMap.size();
HashMap newMap = new HashMap();
if (featureToMatchesMap.isEmpty()) {
return newMap;
}
for (Iterator i = featureToMatchesMap.keySet().iterator();
i.hasNext() && !monitor.isCancelRequested();
) {
Feature oldKey = (Feature) i.next();
featuresProcessed++;
monitor.report(featuresProcessed, totalFeatures, "features inverted");
Matches oldMatches = (Matches) featureToMatchesMap.get(oldKey);
for (int j = 0; j < oldMatches.size(); j++) {
Feature newKey = oldMatches.getFeature(j);
Matches newMatches = (Matches) newMap.get(newKey);
if (newMatches == null) {
newMatches = new Matches(oldKey.getSchema());
}
newMatches.add(oldKey, oldMatches.getScore(j));
newMap.put(newKey, newMatches);
}
}
return newMap;
}
}