diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/ChainMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/ChainMatcher.java index 605c4dd..ab4c8f3 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/ChainMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/ChainMatcher.java @@ -36,6 +36,7 @@ import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; +import com.vividsolutions.jump.feature.FeatureDataset; /** * Composes several FeatureMatchers into one. Candidate features are whittled @@ -68,8 +69,7 @@ public ChainMatcher(FeatureMatcher[] matchers) { */ @Override public Matches match(Feature target, FeatureCollection candidates) { - Matches survivors = new Matches( - candidates.getFeatureSchema(), candidates.getFeatures()); + Matches survivors = new Matches(new FeatureDataset(candidates.getFeatures(), candidates.getFeatureSchema(), candidates.getEnvelope())); for (FeatureMatcher matcher : matchers) { survivors = matcher.match(target, survivors); } diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java index 71288a5..4f72dd4 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java @@ -1,7 +1,10 @@ package com.vividsolutions.jcs.conflate.polygonmatch; + import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedSet; import com.vividsolutions.jump.feature.Feature; @@ -24,20 +27,26 @@ public Map match( FeatureCollection targetFC, FeatureCollection candidateFC, TaskMonitor monitor) { - List targets = new ArrayList<>(); - List candidates = new ArrayList<>(); - List scores = new ArrayList<>(); - SortedSet matchSet = DisambiguationMatch.createDisambiguationMatches(matchFinder.match(targetFC, candidateFC, monitor), monitor); + final SortedSet matchSet = + DisambiguationMatch.createDisambiguationMatches(matchFinder.match(targetFC, candidateFC, monitor), monitor); + final List targets = new ArrayList<>(matchSet.size()); + final List candidates = new ArrayList<>(matchSet.size()); + // These sets are here to avoid expensive ArrayList#contains calls + final Set candidatesSet = new HashSet<>(matchSet.size()); + final Set targetsSet = new HashSet<>(matchSet.size()); + final List scores = new ArrayList<>(matchSet.size()); monitor.report("Discarding inferior matches"); int j = 0; for (DisambiguationMatch match : matchSet) { monitor.report(++j, matchSet.size(), "matches"); - if (targets.contains(match.getTarget()) || candidates.contains(match.getCandidate())) { + if (targetsSet.contains(match.getTarget()) || candidatesSet.contains(match.getCandidate())) { continue; } targets.add(match.getTarget()); + targetsSet.add(match.getTarget()); candidates.add(match.getCandidate()); - scores.add(Double.valueOf(match.getScore())); + candidatesSet.add(match.getCandidate()); + scores.add(match.getScore()); } //Re-add filtered-out targets, but with zero-score matches [Jon Aquino] Map targetToMatchesMap = @@ -46,7 +55,7 @@ public Map match( candidateFC.getFeatureSchema()); for (int i = 0; i < targets.size(); i++) { Matches matches = new Matches(candidateFC.getFeatureSchema()); - matches.add(candidates.get(i), scores.get(i).doubleValue()); + matches.add(candidates.get(i), scores.get(i)); targetToMatchesMap.put(targets.get(i), matches); } return targetToMatchesMap; diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/FeatureMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/FeatureMatcher.java index 79626fe..b9d5c63 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/FeatureMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/FeatureMatcher.java @@ -58,6 +58,6 @@ public interface FeatureMatcher { * @return the matching features, and a score for each. (Implementors should * document how they do their scoring). */ - public Matches match(Feature target, FeatureCollection candidates); + Matches match(Feature target, FeatureCollection candidates); } diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/IndependentCandidateMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/IndependentCandidateMatcher.java index 80b8d8b..f8137d7 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/IndependentCandidateMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/IndependentCandidateMatcher.java @@ -31,9 +31,12 @@ */ package com.vividsolutions.jcs.conflate.polygonmatch; +import java.util.Collections; + import org.locationtech.jts.geom.Geometry; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; +import com.vividsolutions.jump.feature.FeatureDataset; /** * Base class of FeatureMatchers that compare the target to each candidate @@ -46,7 +49,9 @@ public IndependentCandidateMatcher() { @Override public Matches match(Feature target, FeatureCollection candidates) { - Matches matches = new Matches(candidates.getFeatureSchema()); + final FeatureDataset fds = new FeatureDataset(Collections.emptySet(), candidates.getFeatureSchema(), + candidates.getEnvelope()); + final Matches matches = new Matches(fds); for (Feature candidate : candidates) { double score = match(target.getGeometry(), candidate.getGeometry()); if (score > 0) { matches.add(candidate, score); } diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java index 5575fbb..ba60095 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java @@ -32,8 +32,9 @@ package com.vividsolutions.jcs.conflate.polygonmatch; import java.util.AbstractMap; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -41,7 +42,8 @@ import java.util.Set; import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.util.Assert; +import org.locationtech.jts.util.AssertionFailedException; + import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; import com.vividsolutions.jump.feature.FeatureDataset; @@ -53,13 +55,14 @@ */ public class Matches extends AbstractMap implements FeatureCollection, Cloneable { private final Set> entrySet = new HashSet<>(); + private int size; /** * Creates a Matches object. * @param schema metadata applicable to the features that will be stored in * this Matches object */ public Matches(FeatureSchema schema) { - dataset = new FeatureDataset(schema); + this(schema, Collections.emptyList()); } @Override @@ -78,14 +81,28 @@ protected Matches clone() { * @param features added to the Matches, each with the max score (1.0) */ public Matches(FeatureSchema schema, List features) { - this(schema); - for (Feature match : features) { - add(match, 1); - } + // We want to ensure that the dataset won't have a ton of ArrayList#grow calls + // So we initialize the dataset with all the data + this.dataset = new FeatureDataset(features.size(), schema, null); + this.scores = new double[features.size()]; + addAll(features, 1, true); + } + + /** + * Creates a Matches object, initialized with the given Dataset. + * @param featureDataset The dataset to use for initialization + */ + public Matches(FeatureDataset featureDataset) { + // We want to ensure that the dataset won't have a ton of ArrayList#grow calls + // So we initialize the dataset with all the data + this.scores = new double[featureDataset.size()]; + this.dataset = new FeatureDataset(featureDataset); + + addAll(featureDataset, 1, false); } private final FeatureDataset dataset; - private final List scores = new ArrayList<>(); + private double[] scores; /** * This method is not supported, because added features need to be associated @@ -94,7 +111,7 @@ public Matches(FeatureSchema schema, List features) { * @see #add(Feature, double) */ @Override - public void add(Feature feature) { + public void add(Feature feature) { throw new UnsupportedOperationException("Use #add(feature, score) instead"); } @@ -103,10 +120,16 @@ public void add(Feature feature) { * with a score. Use #add(Feature, double) instead. */ @Override - public void addAll(Collection features) { + public void addAll(Collection features) { throw new UnsupportedOperationException("Use #add(feature, score) instead"); } + private void addAll(Iterable features, double score, boolean addToDataset) { + for (Feature feature : features) { + add(feature, score, addToDataset); + } + } + /** * This method is not supported, because added features need to be associated * with a score. Use #add(Feature, double) instead. @@ -122,7 +145,7 @@ public void add(int index, Feature feature) { * have matches removed. */ @Override - public Collection remove(Envelope envelope) { + public Collection remove(Envelope envelope) { //If we decide to implement this, remember to remove the corresponding //score. [Jon Aquino] throw new UnsupportedOperationException(); @@ -133,7 +156,7 @@ public Collection remove(Envelope envelope) { * have matches removed. */ @Override - public void clear() { + public void clear() { //If we decide to implement this, remember to remove the corresponding //score. [Jon Aquino] throw new UnsupportedOperationException(); @@ -162,7 +185,7 @@ private synchronized Set> updateEntrySet() { * have matches removed. */ @Override - public void removeAll(Collection features) { + public void removeAll(Collection features) { //If we decide to implement this, remember to remove the corresponding //score. [Jon Aquino] throw new UnsupportedOperationException(); @@ -174,7 +197,7 @@ public void removeAll(Collection features) { * @param feature a feature to remove */ @Override - public void remove(Feature feature) { + public void remove(Feature feature) { //If we decide to implement this, remember to remove the corresponding //score. [Jon Aquino] throw new UnsupportedOperationException(); @@ -185,20 +208,36 @@ public void remove(Feature feature) { * @param score the confidence of the match, ranging from 0 to 1 */ public void add(Feature feature, double score) { - Assert.isTrue(0 <= score && score <= 1, "Score = " + score); + add(feature, score, true); + } + + private void add(Feature feature, double score, boolean addToDataset) { + // We want to avoid the string concatenation here, if we don't need it. + // It is *very* expensive when run with large datasets. + // This used to be an Assert.isTrue statement + if (0 > score || score > 1) { + throw new AssertionFailedException("Score = " + score); + } if (score == 0) { return; } - scores.add(score); - dataset.add(feature); + scoreAdd(size++, score); + if (addToDataset) dataset.add(feature); if (score > topScore) { topScore = score; topMatch = feature; } } + private void scoreAdd(int index, double score) { + if (this.scores.length < index + 1) { + this.scores = Arrays.copyOf(this.scores, index + 1); + } + this.scores[index] = score; + } + private Feature topMatch; - private double topScore = 0; + private double topScore; public double getTopScore() { return topScore; @@ -217,26 +256,26 @@ public Feature getTopMatch() { * @return the confidence of the ith match */ public double getScore(int i) { - return scores.get(i); + return scores[i]; } @Override - public FeatureSchema getFeatureSchema() { + public FeatureSchema getFeatureSchema() { return dataset.getFeatureSchema(); } @Override - public Envelope getEnvelope() { + public Envelope getEnvelope() { return dataset.getEnvelope(); } @Override - public int size() { + public int size() { return dataset.size(); } @Override - public boolean isEmpty() { + public boolean isEmpty() { return dataset.isEmpty(); } @@ -245,17 +284,17 @@ public Feature getFeature(int index) { } @Override - public List getFeatures() { + public List getFeatures() { return dataset.getFeatures(); } @Override - public Iterator iterator() { + public Iterator iterator() { return dataset.iterator(); } @Override - public List query(Envelope envelope) { + public List query(Envelope envelope) { return dataset.query(envelope); } } diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/WindowMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/WindowMatcher.java index 1cd76cc..626919c 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/WindowMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/WindowMatcher.java @@ -37,6 +37,7 @@ import org.locationtech.jts.geom.Envelope; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; +import com.vividsolutions.jump.feature.FeatureDataset; import com.vividsolutions.jump.geom.EnvelopeUtil; /** @@ -79,6 +80,6 @@ public WindowMatcher() {} public Matches match(Feature target, FeatureCollection candidates) { Envelope window = new Envelope(target.getGeometry().getEnvelopeInternal()); window = EnvelopeUtil.expand(window, buffer); - return new Matches(candidates.getFeatureSchema(), candidates.query(window)); + return new Matches(new FeatureDataset(candidates.query(window), candidates.getFeatureSchema(), window)); } } diff --git a/src/com/vividsolutions/jump/feature/FeatureDataset.java b/src/com/vividsolutions/jump/feature/FeatureDataset.java index cf99299..e0742bd 100644 --- a/src/com/vividsolutions/jump/feature/FeatureDataset.java +++ b/src/com/vividsolutions/jump/feature/FeatureDataset.java @@ -55,8 +55,31 @@ public class FeatureDataset implements FeatureCollection { * @param featureSchema the types of the attributes of the features in this collection */ public FeatureDataset(Collection newFeatures, FeatureSchema featureSchema) { - features = new ArrayList<>(newFeatures); + this(newFeatures, featureSchema, null); + } + + /** + * Creates a FeatureDataset, initialized with a group of Features. + * @param newFeatures an initial group of features to add to this FeatureDataset + * @param featureSchema the types of the attributes of the features in this collection + * @param envelope The expected envelope for the features + */ + public FeatureDataset(Collection newFeatures, FeatureSchema featureSchema, Envelope envelope) { + this.features = new ArrayList<>(newFeatures); + this.featureSchema = featureSchema; + this.envelope = envelope != null ? envelope.copy() : null; + } + + /** + * Creates a FeatureDataset, with an initial list size for features + * @param newFeatures The expected size of the features + * @param featureSchema the types of the attributes of the features in this collection + * @param envelope The expected envelope for this dataset + */ + public FeatureDataset(int newFeatures, FeatureSchema featureSchema, Envelope envelope) { + this.features = new ArrayList<>(newFeatures); this.featureSchema = featureSchema; + this.envelope = envelope == null ? null : envelope.copy(); } /** @@ -64,7 +87,18 @@ public FeatureDataset(Collection newFeatures, FeatureSchema featureSche * @param featureSchema the types of the attributes of the features in this collection */ public FeatureDataset(FeatureSchema featureSchema) { - this(new ArrayList(), featureSchema); + this(new ArrayList<>(), featureSchema); + } + + /** + * Clone another dataset + * @param otherDataset The dataset to clone + */ + public FeatureDataset(FeatureDataset otherDataset) { + this(otherDataset.getFeatures(), otherDataset.getFeatureSchema()); + if (otherDataset.envelope != null) { + this.envelope = otherDataset.envelope.copy(); + } } public Feature getFeature(int index) { @@ -72,7 +106,7 @@ public Feature getFeature(int index) { } @Override - public FeatureSchema getFeatureSchema() { + public FeatureSchema getFeatureSchema() { return featureSchema; } @@ -81,7 +115,7 @@ public FeatureSchema getFeatureSchema() { * later change a Feature's geometry using Feature#setGeometry. */ @Override - public Envelope getEnvelope() { + public Envelope getEnvelope() { if (envelope == null) { envelope = new Envelope(); @@ -95,12 +129,12 @@ public Envelope getEnvelope() { } @Override - public List getFeatures() { + public List getFeatures() { return Collections.unmodifiableList(features); } @Override - public boolean isEmpty() { + public boolean isEmpty() { return size() == 0; } @@ -112,7 +146,7 @@ public boolean isEmpty() { //<> Perhaps return value should be a Set, not a List, because order //doesn't matter. [Jon Aquino] @Override - public List query(Envelope envelope) { + public List query(Envelope envelope) { if (!envelope.intersects(getEnvelope())) { return new ArrayList<>(); } @@ -131,7 +165,7 @@ public List query(Envelope envelope) { } @Override - public void add(Feature feature) { + public void add(Feature feature) { features.add(feature); if (envelope != null) { envelope.expandToInclude(feature.getGeometry().getEnvelopeInternal()); @@ -152,7 +186,7 @@ public boolean contains(Feature feature) { * @param env */ @Override - public Collection remove(Envelope env) { + public Collection remove(Envelope env) { Collection features = query(env); removeAll(features); @@ -160,7 +194,7 @@ public Collection remove(Envelope env) { } @Override - public void remove(Feature feature) { + public void remove(Feature feature) { features.remove(feature); invalidateEnvelope(); } @@ -169,18 +203,18 @@ public void remove(Feature feature) { * Removes all features from this collection. */ @Override - public void clear() { + public void clear() { invalidateEnvelope(); features.clear(); } @Override - public int size() { + public int size() { return features.size(); } @Override - public Iterator iterator() { + public Iterator iterator() { return features.iterator(); } @@ -189,7 +223,7 @@ public void invalidateEnvelope() { } @Override - public void addAll(Collection features) { + public void addAll(Collection features) { this.features.addAll(features); if (envelope != null) { for (Feature feature : features) { @@ -199,7 +233,7 @@ public void addAll(Collection features) { } @Override - public void removeAll(Collection features) { + public void removeAll(Collection features) { this.features.removeAll(features); invalidateEnvelope(); } diff --git a/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java b/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java index 3fbf110..60a2d1a 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java +++ b/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java @@ -38,6 +38,7 @@ import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryException; import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryUtils; import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.UserCancelException; import org.openstreetmap.josm.tools.Utils; @@ -195,7 +196,8 @@ public static Command buildReplaceGeometryCommand(OsmPrimitive subjectObject, Os TagMap savedSubjectTags = saveAndRemoveTagsNotInCollection(subjectObject, tagCollection); Command command = null; try { - command = ReplaceGeometryUtils.buildReplaceCommand(subjectObject, referenceObject); + command = ReplaceGeometryUtils.buildReplaceCommand(subjectObject, referenceObject, + CombinePrimitiveResolverDialog.Strategy.KEEP_NON_CONFLICTING); } catch (ReplaceGeometryException ex) { AutoScaleAction.zoomTo(Arrays.asList(subjectObject, referenceObject)); JOptionPane.showMessageDialog(MainApplication.getMainFrame(), @@ -230,8 +232,10 @@ public static List buildTagMergingCommand(SimpleMatch match, SimpleMatc return CombinePrimitiveResolverDialog.launchIfNecessary( match.getMergingTagCollection(settings), Arrays.asList(match.getReferenceObject(), match.getSubjectObject()), - Collections.singleton(match.getSubjectObject())); + Collections.singleton(match.getSubjectObject()), + CombinePrimitiveResolverDialog.Strategy.KEEP_NON_CONFLICTING); } catch (UserCancelException e) { + Logging.trace(e); return null; } finally { restoreRelationsData(savedRelationsData);