diff --git a/.classpath b/.classpath index 4ca8290..54c9cc7 100644 --- a/.classpath +++ b/.classpath @@ -1,7 +1,9 @@ + + diff --git a/.gitignore b/.gitignore index 9d32708..0c6a528 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /build.properties /build -/dist \ No newline at end of file +/test/build +/test/report +/dist diff --git a/README.md b/README similarity index 100% rename from README.md rename to README diff --git a/build.properties b/build.properties index 6c4106d..7e1df37 100644 --- a/build.properties +++ b/build.properties @@ -9,7 +9,7 @@ # the current plugin version. Increment if you create a new build to be rolled out # via the OSM subversion repository # -plugin.version=0.5.0 +plugin.version=0.5.1 # the lowest JOSM version the curent plugin version is compatible with # diff --git a/build.xml b/build.xml index 54c5354..67fd2e4 100644 --- a/build.xml +++ b/build.xml @@ -1,100 +1,28 @@ - + - - + - - - - - - - + + + + - - - ** - ** Property file 'build.properties' doesn't exist. - ** Create a copy from 'build.properties.template' and update the properties - ** according to your local environment. - - + + + + + + + + - - - - - + + - - + + + + - - - Classpath includes ${josm} - Classpath includes ${jts.plugin.jar} - Classpath includes ${utilsplugin2.plugin.jar} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Installing the plugin in ${local.osm.svn.path} - - - - - - - - - Installing the plugin in ${local.install.path} - - - - - - diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidAligner.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidAligner.java index 7095be6..19cd788 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidAligner.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidAligner.java @@ -3,13 +3,18 @@ import com.vividsolutions.jts.geom.Geometry; public class CentroidAligner extends IndependentCandidateMatcher { + private IndependentCandidateMatcher matcher; + public CentroidAligner(IndependentCandidateMatcher matcher) { this.matcher = matcher; } + + @Override public double match(Geometry target, Geometry candidate) { return matcher.match(align(target), align(candidate)); } + private Geometry align(Geometry original) { Geometry aligned = (Geometry) original.clone(); MatcherUtil.align(aligned, aligned.getCentroid().getCoordinate()); diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidDistanceMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidDistanceMatcher.java index 11d3820..3f60eab 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidDistanceMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/CentroidDistanceMatcher.java @@ -15,6 +15,7 @@ public CentroidDistanceMatcher(double maxDistance) { setMaxDistance(maxDistance); } + @Override protected double distance(Geometry target, Geometry candidate) { return target.getCentroid().distance( candidate.getCentroid()); diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/CompactnessMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/CompactnessMatcher.java index 3f37548..a3ad19a 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/CompactnessMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/CompactnessMatcher.java @@ -50,6 +50,7 @@ public CompactnessMatcher() { * @return 1 - the difference between the values of the shape * characteristic, defined above. */ + @Override public double match(Geometry target, Geometry candidate) { double score = 1 - Math.abs(characteristic(target) - characteristic(candidate)); diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java index 7a98f0c..71288a5 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/DisambiguatingFCMatchFinder.java @@ -37,7 +37,7 @@ public Map match( } targets.add(match.getTarget()); candidates.add(match.getCandidate()); - scores.add(new Double(match.getScore())); + scores.add(Double.valueOf(match.getScore())); } //Re-add filtered-out targets, but with zero-score matches [Jon Aquino] Map targetToMatchesMap = @@ -51,4 +51,4 @@ public Map match( } return targetToMatchesMap; } -} \ No newline at end of file +} diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/HausdorffDistanceMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/HausdorffDistanceMatcher.java index d50755a..892af4b 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/HausdorffDistanceMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/HausdorffDistanceMatcher.java @@ -20,6 +20,7 @@ public HausdorffDistanceMatcher(double maxDistance) { setMaxDistance(maxDistance); } + @Override protected double distance(Geometry target, Geometry candidate) { return new VertexHausdorffDistance(target, candidate).distance(); } diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java index f90e1f1..ea1dc6f 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/Matches.java @@ -167,7 +167,7 @@ public void add(Feature feature, double score) { if (score == 0) { return; } - scores.add(new Double(score)); + scores.add(Double.valueOf(score)); dataset.add(feature); if (score > topScore) { topScore = score; diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/OverlapMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/OverlapMatcher.java index 91d29bf..e1e103a 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/OverlapMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/OverlapMatcher.java @@ -4,6 +4,7 @@ public class OverlapMatcher extends IndependentCandidateMatcher { + @Override public double match(Geometry target, Geometry candidate) { //Impose the min to curb roundoff error in exact matches (a situation which //arose during testing (identical datasets)) [Jon Aquino] diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/SymDiffMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/SymDiffMatcher.java index b0c3f24..b9e21bb 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/SymDiffMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/SymDiffMatcher.java @@ -51,6 +51,7 @@ public SymDiffMatcher() { * @param candidate the feature to compare with the target * @return candidates with a score greater than 0 (typically all the candidates). */ + @Override public double match(Geometry target, Geometry candidate) { Geometry targetGeom = (Geometry) target.clone(); Geometry candidateGeom = (Geometry) candidate.clone(); diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/TargetUnioningFCMatchFinder.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/TargetUnioningFCMatchFinder.java index 1d5a993..aa6be5b 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/TargetUnioningFCMatchFinder.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/TargetUnioningFCMatchFinder.java @@ -134,7 +134,7 @@ public int compare(Feature o1, Feature o2) { unionID++; for (Feature targetConstituent : compositeTarget.getFeatures()) { lastTargetConstituents.add(targetConstituent); - lastUnionIDs.add(new Integer(unionID)); + lastUnionIDs.add(Integer.valueOf(unionID)); } } } @@ -173,7 +173,7 @@ protected Map disambiguateCompositeTargetConstituents( } compositeTargets.add(match.getTarget()); candidates.add(match.getCandidate()); - scores.add(new Double(match.getScore())); + scores.add(Double.valueOf(match.getScore())); targetConstituentsEncountered.addAll(((CompositeFeature) match.getTarget()).getFeatures()); } Map newMap = new HashMap<>(); diff --git a/src/com/vividsolutions/jcs/conflate/polygonmatch/WeightedMatcher.java b/src/com/vividsolutions/jcs/conflate/polygonmatch/WeightedMatcher.java index 119e1d4..732521f 100644 --- a/src/com/vividsolutions/jcs/conflate/polygonmatch/WeightedMatcher.java +++ b/src/com/vividsolutions/jcs/conflate/polygonmatch/WeightedMatcher.java @@ -70,7 +70,7 @@ private void add(FeatureMatcher matcher, double weight) { if (weight == 0) { return; } - matcherToWeightMap.put(matcher, new Double(weight)); + matcherToWeightMap.put(matcher, Double.valueOf(weight)); } private Map matcherToWeightMap = new HashMap<>(); @@ -128,8 +128,8 @@ private void addToFeatureToScoreMap(Matches matches, FeatureMatcher matcher, private void addToFeatureToScoreMap(Feature feature, double score, Map featureToScoreMap) { Double oldScore = featureToScoreMap.get(feature); - if (oldScore == null) { oldScore = new Double(0); } - featureToScoreMap.put(feature, new Double(oldScore.doubleValue() + score)); + if (oldScore == null) { oldScore = Double.valueOf(0); } + featureToScoreMap.put(feature, Double.valueOf(oldScore.doubleValue() + score)); } private double normalizedWeight(FeatureMatcher matcher) { diff --git a/src/com/vividsolutions/jcs/plugin/conflate/polygonmatch/MyValidatingTextField.java b/src/com/vividsolutions/jcs/plugin/conflate/polygonmatch/MyValidatingTextField.java index f401555..39344f0 100644 --- a/src/com/vividsolutions/jcs/plugin/conflate/polygonmatch/MyValidatingTextField.java +++ b/src/com/vividsolutions/jcs/plugin/conflate/polygonmatch/MyValidatingTextField.java @@ -30,6 +30,7 @@ public MyValidatingTextField( super(text, columns, validator); setHorizontalAlignment(alignment); addFocusListener(new FocusAdapter() { + @Override public void focusLost(FocusEvent e) { if (getText().trim().length() == 0) { setText(emptyStringReplacement); @@ -42,6 +43,7 @@ public void focusLost(FocusEvent e) { public static final Validator NON_NEGATIVE_DOUBLE_VALIDATOR = new ValidatingTextField.Validator() { + @Override public boolean isValid(String text) { if (text.length() == 0) { return true; @@ -56,6 +58,7 @@ public boolean isValid(String text) { } }; + @Override public double getDouble() { try { return Double.parseDouble(getText().trim()); @@ -77,6 +80,7 @@ public double getDouble() { public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new ValidatingTextField.Validator() { + @Override public boolean isValid(String text) { if (text.length() == 0) { return true; @@ -94,6 +98,7 @@ public static class CompositeValidator implements Validator { public CompositeValidator(Validator[] validators) { this.validators = validators; } + @Override public boolean isValid(String text) { for (int i = 0; i < validators.length; i++) { if (!validators[i].isValid(text)) { @@ -109,6 +114,7 @@ public static class GreaterThanValidator implements Validator { public GreaterThanValidator(double threshold) { this.threshold = threshold; } + @Override public boolean isValid(String text) { return text.trim().length() == 0 || Double.parseDouble(text.trim()) > threshold; diff --git a/src/com/vividsolutions/jump/feature/BasicFeature.java b/src/com/vividsolutions/jump/feature/BasicFeature.java index 1a461e1..2d391f7 100644 --- a/src/com/vividsolutions/jump/feature/BasicFeature.java +++ b/src/com/vividsolutions/jump/feature/BasicFeature.java @@ -59,6 +59,7 @@ public BasicFeature(FeatureSchema featureSchema) { /** * A low-level accessor that is not normally used. It is called by ViewSchemaPlugIn. */ + @Override public void setAttributes(Object[] attributes) { this.attributes = attributes; } @@ -69,6 +70,7 @@ public void setAttributes(Object[] attributes) { *@param attributeIndex the array index at which to put the new attribute *@param newAttribute the new attribute */ + @Override public void setAttribute(int attributeIndex, Object newAttribute) { attributes[attributeIndex] = newAttribute; } @@ -79,6 +81,7 @@ public void setAttribute(int attributeIndex, Object newAttribute) { *@param i the index of the attribute to get *@return the attribute */ + @Override public Object getAttribute(int i) { return attributes[i]; //We used to eat ArrayOutOfBoundsExceptions here. I've removed this behaviour @@ -88,6 +91,7 @@ public Object getAttribute(int i) { /** * A low-level accessor that is not normally used. It is called by ViewSchemaPlugIn. */ + @Override public Object[] getAttributes() { return attributes; } diff --git a/src/com/vividsolutions/jump/feature/FeatureSchema.java b/src/com/vividsolutions/jump/feature/FeatureSchema.java index c1c9eda..2da16bc 100644 --- a/src/com/vividsolutions/jump/feature/FeatureSchema.java +++ b/src/com/vividsolutions/jump/feature/FeatureSchema.java @@ -111,7 +111,7 @@ public void addAttribute(String attributeName, AttributeType attributeType) { } attributeNames.add(attributeName); - attributeNameToIndexMap.put(attributeName, new Integer(attributeCount)); + attributeNameToIndexMap.put(attributeName, Integer.valueOf(attributeCount)); attributeTypes.add(attributeType); attributeCount++; } diff --git a/src/com/vividsolutions/jump/workbench/ui/ValidatingTextField.java b/src/com/vividsolutions/jump/workbench/ui/ValidatingTextField.java index 62286ec..d19558d 100644 --- a/src/com/vividsolutions/jump/workbench/ui/ValidatingTextField.java +++ b/src/com/vividsolutions/jump/workbench/ui/ValidatingTextField.java @@ -47,6 +47,7 @@ */ public class ValidatingTextField extends JTextField { public static final Validator LONG_VALIDATOR = new ValidatingTextField.Validator() { + @Override public boolean isValid(String text) { try { Long.parseLong(text.trim() + "0"); @@ -62,6 +63,7 @@ public boolean isValid(String text) { * Prevents the user from entering invalid integer. */ public static final Validator INTEGER_VALIDATOR = new ValidatingTextField.Validator() { + @Override public boolean isValid(String text) { try { //Add "0" so user can type "-" or make it blank [Jon Aquino] @@ -78,6 +80,7 @@ public boolean isValid(String text) { * Prevents the user from entering invalid double. */ public static final Validator DOUBLE_VALIDATOR = new ValidatingTextField.Validator() { + @Override public boolean isValid(String text) { try { //Add "0" so user can type "-" or make it blank [Jon Aquino] @@ -94,6 +97,7 @@ public boolean isValid(String text) { * Cleaner that does nothing. */ public static Cleaner DUMMY_CLEANER = new Cleaner() { + @Override public String clean(String text) { return text; } @@ -105,6 +109,7 @@ public String clean(String text) { * is reasonable. */ public static Cleaner NUMBER_CLEANER = new Cleaner() { + @Override public String clean(String text) { try { Double.parseDouble(text.trim()); @@ -120,6 +125,7 @@ public String clean(String text) { * Validator that does nothing. */ public static Validator DUMMY_VALIDATOR = new Validator() { + @Override public boolean isValid(String text) { return true; } @@ -162,6 +168,7 @@ public static void installValidationBehavior(final JTextField textField, final Validator validator, final Cleaner cleaner) { final boolean[] validating = new boolean[] { true }; textField.setDocument(new PlainDocument() { + @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if (!validating[0]) { @@ -178,6 +185,7 @@ public void insertString(int offs, String str, AttributeSet a) } } + @Override public void remove(int offs, int len) throws BadLocationException { if (!validating[0]) { super.remove(offs, len); @@ -194,6 +202,7 @@ public void remove(int offs, int len) throws BadLocationException { } }); textField.addFocusListener(new FocusAdapter() { + @Override public void focusLost(FocusEvent e) { // Added validating flag to fix bug: on losing focus, #setText // called #remove, which cleared the text, which then failed @@ -210,6 +219,7 @@ public void focusLost(FocusEvent e) { }); } + @Override public String getText() { //Focus may not be lost yet (e.g. when syncing with scrollbar) [Jon // Aquino] @@ -243,6 +253,7 @@ public GreaterThanValidator(double threshold) { this.threshold = threshold; } + @Override public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) > threshold; @@ -264,6 +275,7 @@ public LessThanValidator(double threshold) { this.threshold = threshold; } + @Override public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) < threshold; @@ -284,6 +296,7 @@ public GreaterThanOrEqualValidator(double threshold) { this.threshold = threshold; } + @Override public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) >= threshold; @@ -304,6 +317,7 @@ public LessThanOrEqualValidator(double threshold) { this.threshold = threshold; } + @Override public boolean isValid(String text) { try { return Double.parseDouble(text.trim()) <= threshold; @@ -324,6 +338,7 @@ public NumberCleaner(String replacement) { this.replacement = replacement; } + @Override public String clean(String text) { try { Double.parseDouble(text.trim()); @@ -351,6 +366,7 @@ public BlankCleaner(String replacement) { this.replacement = replacement; } + @Override public String clean(String text) { return (text.trim().length() == 0) ? getReplacement() : text; } @@ -367,6 +383,7 @@ public MinIntCleaner(int minimum) { this.minimum = minimum; } + @Override public String clean(String text) { return "" + Math.max(minimum, Integer.parseInt(text)); } @@ -405,6 +422,7 @@ public MaxIntCleaner(int maximum) { this.maximum = maximum; } + @Override public String clean(String text) { return "" + Math.min(maximum, Integer.parseInt(text)); } @@ -421,6 +439,7 @@ public CompositeValidator(Validator[] validators) { this.validators = validators; } + @Override public boolean isValid(String text) { for (int i = 0; i < validators.length; i++) { if (!validators[i].isValid(text)) { @@ -439,6 +458,7 @@ public CompositeCleaner(Cleaner[] cleaners) { this.cleaners = cleaners; } + @Override public String clean(String text) { String result = text; for (int i = 0; i < cleaners.length; i++) { diff --git a/src/org/openstreetmap/josm/plugins/conflation/ConflationToggleDialog.java b/src/org/openstreetmap/josm/plugins/conflation/ConflationToggleDialog.java index 1996636..a9c59d8 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/ConflationToggleDialog.java +++ b/src/org/openstreetmap/josm/plugins/conflation/ConflationToggleDialog.java @@ -87,7 +87,6 @@ public class ConflationToggleDialog extends ToggleDialog implements SelectionChangedListener, DataSetListener, SimpleMatchListListener, LayerChangeListener { public static final String TITLE_PREFIX = tr("Conflation"); - public static final String PREF_PREFIX = "conflation"; final JTabbedPane tabbedPane; final JTable matchTable; final JList referenceOnlyList; @@ -560,7 +559,7 @@ public void valueChanged(ListSelectionEvent e) { } } - class ColorTableCellRenderer extends JLabel implements TableCellRenderer { + static class ColorTableCellRenderer extends JLabel implements TableCellRenderer { private final String columnName; diff --git a/src/org/openstreetmap/josm/plugins/conflation/SimpleMatch.java b/src/org/openstreetmap/josm/plugins/conflation/SimpleMatch.java index 52b0cbe..2cfc85c 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/SimpleMatch.java +++ b/src/org/openstreetmap/josm/plugins/conflation/SimpleMatch.java @@ -22,13 +22,19 @@ public class SimpleMatch implements Comparable { public SimpleMatch(OsmPrimitive referenceObject, OsmPrimitive subjectObject, double score) { + this(referenceObject, subjectObject, score, + // TODO: use distance calculated in score function, and make sure it's in meters? + ConflationUtils.getCenter(referenceObject).distance(ConflationUtils.getCenter(subjectObject))); + } + + public SimpleMatch(OsmPrimitive referenceObject, + OsmPrimitive subjectObject, double score, double distance) { CheckParameterUtil.ensureParameterNotNull(referenceObject, "referenceObject"); CheckParameterUtil.ensureParameterNotNull(subjectObject, "subjectObject"); this.referenceObject = referenceObject; this.subjectObject = subjectObject; this.score = score; - // TODO: use distance calculated in score function, and make sure it's in meters? - this.distance = ConflationUtils.getCenter(referenceObject).distance(ConflationUtils.getCenter(subjectObject)); + this.distance = distance; } public OsmPrimitive getReferenceObject() { @@ -56,9 +62,9 @@ public Object getDistance() { * @return the TagCollection for the resulting object of the conflation of this match. */ public TagCollection getMergingTagCollection(SimpleMatchSettings settings) { - if ((subjectObject.getId() >= 0) && (referenceObject.getId() >= 0) + if ((subjectObject.getId() > 0) && (referenceObject.getId() > 0) && settings.isReplacingGeometry && referenceObject.getDataSet() == subjectObject.getDataSet()) { - // In this situation we are conflating already existing OSM objects (getId() >=0) + // In this situation we are conflating already existing OSM objects (getId() >0) // which are on the same DataSet so one will be deleted by the ReplaceGeometryCommand. // We don't wan't to silently delete important tags, so in this particular situation we force // tag merging dialogue for user confirmation by returning a full tag collection: @@ -71,7 +77,7 @@ public TagCollection getMergingTagCollection(SimpleMatchSettings settings) { if (settings.overwriteTags.contains(refTag.getKey())) { tagCollection.removeByKey(refTag.getKey()); tagCollection.add(refTag); - } else if (settings.mergeTags.contains(refTag.getKey())) { + } else if (settings.mergeTags.contains(refTag.getKey()) || (referenceObject.getId() > 0)) { tagCollection.add(refTag); } } diff --git a/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java b/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java index 608db93..549d196 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java +++ b/src/org/openstreetmap/josm/plugins/conflation/command/ConflateMatchCommand.java @@ -4,22 +4,38 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.stream.Collectors; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Stream; import javax.swing.Icon; +import javax.swing.JOptionPane; +import org.openstreetmap.josm.Main; +import org.openstreetmap.josm.command.AddPrimitivesCommand; +import org.openstreetmap.josm.command.ChangePropertyCommand; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.PseudoCommand; import org.openstreetmap.josm.command.SequenceCommand; import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.PrimitiveData; +import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.RelationData; +import org.openstreetmap.josm.data.osm.Tag; +import org.openstreetmap.josm.data.osm.TagCollection; +import org.openstreetmap.josm.data.osm.TagMap; import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.plugins.conflation.ConflationUtils; import org.openstreetmap.josm.plugins.conflation.SimpleMatch; import org.openstreetmap.josm.plugins.conflation.SimpleMatchList; import org.openstreetmap.josm.plugins.conflation.SimpleMatchSettings; +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.UserCancelException; @@ -32,10 +48,8 @@ public class ConflateMatchCommand extends Command { private final SimpleMatch match; private final SimpleMatchList matcheList; private final SimpleMatchSettings settings; - private final Command replaceGeometryOnlyCommand; - private Command tagsMergingCommand = null; - private boolean tagsMergingBuilded; - private boolean executionOk = false; + private SequenceCommand command; + private boolean builded; public ConflateMatchCommand(SimpleMatch match, SimpleMatchList matcheList, SimpleMatchSettings settings) { @@ -43,65 +57,40 @@ public ConflateMatchCommand(SimpleMatch match, this.match = match; this.matcheList = matcheList; this.settings = settings; - replaceGeometryOnlyCommand = settings.isReplacingGeometry ? - new ReplaceGeometryOnlyCommand(match.getSubjectObject(), match.getReferenceObject(), settings.subjectLayer) - : null; + // REM: the real commands are only built at execution time to ensure + // that many ConflateMatchCommand could be combined in a SequenecCommand even + // if a ConflateMatchCommand would impact a following ConflateMatchCommand. + // This is the case for instance when ReplaceeGeometry modify the relations + // members, this modifications need to be taken into account to compute + // the next ReplaceeGeometryCommand, if not, relation membership would + // be overwritten, see ticket https://josm.openstreetmap.de/ticket/7665 } @Override public boolean executeCommand() { - boolean tagsMerged = false; - try { - // Be sure we don't ask the user twice in case of undo/redo: - if (!tagsMergingBuilded) { - tagsMergingBuilded = true; - try { - tagsMergingCommand = new SequenceCommand(tr("Merge Tags"), - CombinePrimitiveResolverDialog.launchIfNecessary( - match.getMergingTagCollection(settings), - Arrays.asList(match.getReferenceObject(), match.getSubjectObject()), - Collections.singleton(match.getSubjectObject()))); - } catch (UserCancelException e) { - tagsMergingBuilded = true; - } - } - if ((tagsMergingCommand != null) && tagsMergingCommand.executeCommand()) { - tagsMerged = true; - if ((replaceGeometryOnlyCommand == null) || (replaceGeometryOnlyCommand.executeCommand())) { - matcheList.remove(match); - executionOk = true; - } - } - } finally { - if (tagsMerged && !executionOk) { - tagsMergingCommand.undoCommand(); + if (!builded) { + builded = true; + List list = (settings.isReplacingGeometry) ? + buildCopyAndReplaceGeometryCommand(match, settings) + : buildTagMergingCommand(match, settings); + if (list != null) { + list.add(new RemoveMatchCommand(matcheList, Arrays.asList(match))); + command = new SequenceCommand("", list); } } - return executionOk; + return (command != null) ? command.executeCommand() : false; } @Override public void undoCommand() { - if (executionOk) { - matcheList.add(match); - executionOk = false; - if (replaceGeometryOnlyCommand != null) { - replaceGeometryOnlyCommand.undoCommand(); - } - if (tagsMergingCommand != null) { - tagsMergingCommand.undoCommand(); - } - } + if (command != null) + command.undoCommand(); } @Override public void fillModifiedData(Collection modified, Collection deleted, Collection added) { - if (replaceGeometryOnlyCommand != null) { - replaceGeometryOnlyCommand.fillModifiedData(modified, deleted, added); - } - if (tagsMergingCommand != null) { - tagsMergingCommand.fillModifiedData(modified, deleted, added); - } + if (command != null) + command.fillModifiedData(modified, deleted, added); } @Override @@ -117,18 +106,136 @@ public Icon getDescriptionIcon() { @Override public Collection getParticipatingPrimitives() { - Collection prims = new HashSet<>(); - if (tagsMergingCommand != null) - prims.addAll(tagsMergingCommand.getParticipatingPrimitives()); - if (replaceGeometryOnlyCommand != null) - prims.addAll(replaceGeometryOnlyCommand.getParticipatingPrimitives()); - return prims; + if (command != null) + return command.getParticipatingPrimitives(); + else + return Arrays.asList(); } @Override public Collection getChildren() { - return Arrays.asList(tagsMergingCommand, replaceGeometryOnlyCommand).stream() - .filter(c -> c != null) - .collect(Collectors.toList()); + if (command != null) + return command.getChildren(); + else + return Arrays.asList(); + } + + /** + * Built Replace Geometry Command (that also merge tags). + * + * Support a reference object in a different DataSet than the subject's one, in which case it will be copied. + * + * Respect the settings for tag merging conflict. + * Will remove tags that where not supposed to be kept. + * + * @return the commands list or null if user canceled or error occurred. + */ + public static List buildCopyAndReplaceGeometryCommand(SimpleMatch match, SimpleMatchSettings settings) { + OsmPrimitive referenceObject = match.getReferenceObject(); + OsmPrimitive subjectObject = match.getSubjectObject(); + TagCollection tagCollection = match.getMergingTagCollection(settings); + List commands = new ArrayList<>(3); + if (settings.subjectLayer != settings.referenceLayer) { + Command copyPrimitiveCommand = buildCopyPrimitiveCommand(referenceObject, settings.subjectLayer); + commands.add(copyPrimitiveCommand); + if (!copyPrimitiveCommand.executeCommand()) { + return null; + } + try { + commands.add(buildReplaceGeometryCommand(subjectObject, + // get the copied reference object: + subjectObject.getDataSet().getPrimitiveById(referenceObject.getPrimitiveId()), + tagCollection)); + } finally { + copyPrimitiveCommand.undoCommand(); + } + } else { + commands.add(buildReplaceGeometryCommand(subjectObject, referenceObject, tagCollection)); + } + if (commands.get(commands.size()-1) == null) { + // could be null because of UserCancel or error. + return null; + } else { + // Remove keys untouched by ReplaceGeometryCommand but we don't want in our tagCollection + Stream.concat(referenceObject.getKeys().keySet().stream(), subjectObject.getKeys().keySet().stream()) + .filter(key -> !tagCollection.hasTagsFor(key)) + .forEach(key -> { + // I don't know who will survive ReplaceGeometryCommand: referenceObject or subjectObject. + // In the doubt I modify both: + commands.add(new ChangePropertyCommand(referenceObject, key, null)); + commands.add(new ChangePropertyCommand(subjectObject, key, null)); + }); + return commands; + } + } + + /** + * Built Replace Geometry Command (that also merge tags). + * @return the commands list or null if user canceled or error occurred. + */ + public static Command buildReplaceGeometryCommand(OsmPrimitive subjectObject, OsmPrimitive referenceObject, TagCollection tagCollection) { + // save and remove tags to avoid unwanted tags conflicts dialog: + TagMap savedReferenceTags = saveAndRemoveTagsNotInCollection(referenceObject, tagCollection); + TagMap savedSubjectTags = saveAndRemoveTagsNotInCollection(subjectObject, tagCollection); + Command command = null; + try { + command = ReplaceGeometryUtils.buildReplaceCommand(subjectObject, referenceObject); + } catch (ReplaceGeometryException ex) { + JOptionPane.showMessageDialog(Main.parent, + ex.getMessage(), tr("Cannot replace geometry."), JOptionPane.INFORMATION_MESSAGE); + } finally { + referenceObject.setKeys(savedReferenceTags); + subjectObject.setKeys(savedSubjectTags); + } + return command; + } + + public static Command buildCopyPrimitiveCommand(OsmPrimitive referenceObject, OsmDataLayer layer) { + List newObjects = ConflationUtils.copyObjects(referenceObject.getDataSet(), referenceObject); + return new AddPrimitivesCommand(newObjects, newObjects, layer); + } + + /** + * Built Tag only Merging Command + * @return the commands list or null if user canceled. + */ + public static List buildTagMergingCommand(SimpleMatch match, SimpleMatchSettings settings) { + // Temporarily remove relation membership to avoid conflict dialog about them, we won't really + // combine the primitives, we just want to combine the tags: + HashMap savedRelationsData = saveAndRemoveRelationMembersFor(match.getReferenceObject()); + saveAndRemoveRelationMembersFor(match.getSubjectObject()).forEach(savedRelationsData::putIfAbsent); + try { + return CombinePrimitiveResolverDialog.launchIfNecessary( + match.getMergingTagCollection(settings), + Arrays.asList(match.getReferenceObject(), match.getSubjectObject()), + Collections.singleton(match.getSubjectObject())); + } catch (UserCancelException e) { + return null; + } finally { + restoreRelationsData(savedRelationsData); + } + } + + private static HashMap saveAndRemoveRelationMembersFor(OsmPrimitive primitive) { + HashMap savedData = new HashMap<>(); + for (Relation r: OsmPrimitive.getFilteredList(primitive.getReferrers(), Relation.class)) { + savedData.put(r, r.save()); + r.removeMembersFor(primitive); + } + return savedData; + } + + private static void restoreRelationsData(HashMap savedData) { + savedData.entrySet().stream().forEach(entry -> entry.getKey().load(entry.getValue())); + } + + private static TagMap saveAndRemoveTagsNotInCollection(OsmPrimitive primitive, TagCollection tagCollection) { + TagMap savedTags = primitive.getKeys(); + for (Tag tag: savedTags.getTags()) { + if (!tagCollection.contains(tag)) { + primitive.remove(tag.getKey()); + } + } + return savedTags; } } diff --git a/src/org/openstreetmap/josm/plugins/conflation/command/RemoveMatchCommand.java b/src/org/openstreetmap/josm/plugins/conflation/command/RemoveMatchCommand.java index e7db55e..c9094cc 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/command/RemoveMatchCommand.java +++ b/src/org/openstreetmap/josm/plugins/conflation/command/RemoveMatchCommand.java @@ -48,7 +48,7 @@ public void fillModifiedData(Collection modified, Collection newObjects = ConflationUtils.copyObjects(referenceObject.getDataSet(), referenceObject); - newObjects.forEach(o -> { - if (o.getPrimitiveId().equals(referenceObject.getPrimitiveId())) { - // remove tags to avoid tag merging action by {@link ReplaceGeometryUtils} - o.removeAll(); - } - }); - addPrimitivesCommand = new AddPrimitivesCommand(newObjects, newObjects, getLayer()); - } - if (!addPrimitivesCommand.executeCommand()) { - ok = false; - } - } else { - // remove tags to avoid tag merging action by {@link ReplaceGeometryUtils} - referenceKeys = referenceObject.getKeys(); - referenceObject.removeAll(); - - } - if (ok) { - // be sure that we don't try again in case of undo/redo - if (!replaceGeometryCommandBuilded) { - replaceGeometryCommandBuilded = true; - replaceGeometryCommand = ReplaceGeometryUtils.buildReplaceCommand( - subjectObject, - subjectDataSet.getPrimitiveById(referenceObject.getPrimitiveId())); - } - ok = (replaceGeometryCommand != null) ? replaceGeometryCommand.executeCommand() : false; - } - } catch (ReplaceGeometryException ex) { - ok = false; - JOptionPane.showMessageDialog(Main.parent, - ex.getMessage(), tr("Cannot replace geometry."), JOptionPane.INFORMATION_MESSAGE); - } catch (RuntimeException e) { - ok = false; - throw e; - } finally { - if (!ok) { - if (referenceKeys != null) { - referenceObject.setKeys(referenceKeys); - } - replaceGeometryCommand = null; - if (addPrimitivesCommand != null) { - addPrimitivesCommand.undoCommand(); - addPrimitivesCommand = null; - } - } - } - return ok; - } - - @Override - public void undoCommand() { - if (replaceGeometryCommand != null) { - replaceGeometryCommand.undoCommand(); - } - if (referenceKeys != null) { - referenceObject.setKeys(referenceKeys); - } - if (addPrimitivesCommand != null) { - addPrimitivesCommand.undoCommand(); - } - } - - @Override - public void fillModifiedData(Collection modified, Collection deleted, Collection added) { - if (replaceGeometryCommand != null) { - replaceGeometryCommand.fillModifiedData(modified, deleted, added); - } - if (addPrimitivesCommand != null) { - addPrimitivesCommand.fillModifiedData(modified, deleted, added); - } - } - - @Override - public String getDescriptionText() { - return tr("Replace Geometry Only"); - } - - @Override - public Icon getDescriptionIcon() { - return ImageProvider.get("dialogs", "conflation"); - } - - @Override - public Collection getParticipatingPrimitives() { - Collection prims = new HashSet<>(); - if (addPrimitivesCommand != null) - prims.addAll(addPrimitivesCommand.getParticipatingPrimitives()); - if (replaceGeometryCommand != null) - prims.addAll(replaceGeometryCommand.getParticipatingPrimitives()); - return prims; - } - - @Override - public Collection getChildren() { - return Arrays.asList(addPrimitivesCommand, replaceGeometryCommand).stream() - .filter(c -> c != null) - .collect(Collectors.toList()); - } -} diff --git a/src/org/openstreetmap/josm/plugins/conflation/config/AdvancedMatchFinderPanel.java b/src/org/openstreetmap/josm/plugins/conflation/config/AdvancedMatchFinderPanel.java index 3871939..de2347f 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/config/AdvancedMatchFinderPanel.java +++ b/src/org/openstreetmap/josm/plugins/conflation/config/AdvancedMatchFinderPanel.java @@ -22,15 +22,14 @@ import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextArea; -import javax.swing.JTextField; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; -import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.plugins.conflation.matcher.AttributeMatcher; import org.openstreetmap.josm.plugins.conflation.matcher.ExactValueMatcher; import org.openstreetmap.josm.plugins.conflation.matcher.LevenshteinDistanceValueMatcher; import org.openstreetmap.josm.plugins.conflation.matcher.OsmNormalizeRule; +import org.openstreetmap.josm.plugins.conflation.matcher.StandardDistanceMatcher; import com.vividsolutions.jcs.conflate.polygonmatch.AngleHistogramMatcher; import com.vividsolutions.jcs.conflate.polygonmatch.AreaFilterFCMatchFinder; @@ -97,6 +96,15 @@ public class AdvancedMatchFinderPanel extends MatchFinderPanel { new MyValidatingTextField.GreaterThanValidator(1.5) }), "2"); private JLabel weightLabel = new JLabel(); + private JCheckBox stdDistanceCheckBox = new JCheckBox("", true); + private MyValidatingTextField stdDistanceWeightField = new MyValidatingTextField("10", + 3, MyValidatingTextField.NON_NEGATIVE_DOUBLE_VALIDATOR, "0"); + private JPanel stdDistancePanel = new JPanel(); + private GridBagLayout stdDistanceLayout = new GridBagLayout(); + private JLabel stdDistanceLabel = new JLabel(); + private JLabel stdDistanceBelow = new JLabel(); + private MyValidatingTextField stdDistanceThresholdField = new MyValidatingTextField("50", + 4, MyValidatingTextField.NON_NEGATIVE_DOUBLE_VALIDATOR, ""); private JCheckBox centroidCheckBox = new JCheckBox("", true); private MyValidatingTextField centroidDistanceWeightField = new MyValidatingTextField("10", 3, MyValidatingTextField.NON_NEGATIVE_DOUBLE_VALIDATOR, "0"); @@ -260,6 +268,7 @@ private void handleLabelClicks() { private void handleClicks(JLabel label, final JCheckBox checkBox) { label.addMouseListener(new MouseAdapter() { + @Override public void mouseClicked(MouseEvent e) { checkBox.doClick(); } @@ -279,6 +288,11 @@ private void jbInit() { unionLabel1.setText(tr("Union up to ")); unionLabel2.setText(tr(" adjacent Reference features")); weightLabel.setText(tr("Weight")); + stdDistanceLabel.setText(tr("Standard Distance")); + stdDistancePanel.setLayout(stdDistanceLayout); + stdDistanceBelow.setText(" < "); + stdDistanceBelow.setToolTipText(tr("below")); + stdDistanceThresholdField.setToolTipText(tr("Maximum Distance")); centroidLabel.setText(tr("Centroid Distance")); centroidPanel.setLayout(centroidLayout); centroidBelow.setText(" < "); @@ -393,94 +407,106 @@ private void jbInit() { new GridBagConstraints(1, 10, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(10, 4, 4, 4), 0, 0)); - matchingTab.add(centroidCheckBox, + matchingTab.add(stdDistanceCheckBox, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(centroidDistanceWeightField, + matchingTab.add(stdDistanceWeightField, new GridBagConstraints(3, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(centroidPanel, + matchingTab.add(stdDistancePanel, new GridBagConstraints(4, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(hausdorffCheckBox, + matchingTab.add(centroidCheckBox, new GridBagConstraints(2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(hausdorffDistanceWeightField, + matchingTab.add(centroidDistanceWeightField, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(hausdorffLabel, + matchingTab.add(centroidPanel, new GridBagConstraints(4, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffCheckBox, + matchingTab.add(hausdorffCheckBox, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffWeightField, + matchingTab.add(hausdorffDistanceWeightField, new GridBagConstraints(3, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffLabel, + matchingTab.add(hausdorffLabel, new GridBagConstraints(4, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffCentroidsAlignedCheckBox, + matchingTab.add(symDiffCheckBox, new GridBagConstraints(2, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffCentroidsAlignedWeightField, + matchingTab.add(symDiffWeightField, new GridBagConstraints(3, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(symDiffCentroidsAlignedLabel, + matchingTab.add(symDiffLabel, new GridBagConstraints(4, 4, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(compactnessCheckBox, + matchingTab.add(symDiffCentroidsAlignedCheckBox, new GridBagConstraints(2, 5, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(compactnessWeightField, + matchingTab.add(symDiffCentroidsAlignedWeightField, new GridBagConstraints(3, 5, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(compactnessLabel, + matchingTab.add(symDiffCentroidsAlignedLabel, new GridBagConstraints(4, 5, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(angleCheckBox, + matchingTab.add(compactnessCheckBox, new GridBagConstraints(2, 6, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(angleWeightField, + matchingTab.add(compactnessWeightField, new GridBagConstraints(3, 6, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(anglePanel, + matchingTab.add(compactnessLabel, new GridBagConstraints(4, 6, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(levenshteinTagsCheckBox, + matchingTab.add(angleCheckBox, new GridBagConstraints(2, 7, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(levenshteinTagsWeightField, + matchingTab.add(angleWeightField, new GridBagConstraints(3, 7, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); - matchingTab.add(levenshteinTagsPanel, + matchingTab.add(anglePanel, new GridBagConstraints(4, 7, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); + matchingTab.add(levenshteinTagsCheckBox, + new GridBagConstraints(2, 8, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + matchingTab.add(levenshteinTagsWeightField, + new GridBagConstraints(3, 8, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + matchingTab.add(levenshteinTagsPanel, + new GridBagConstraints(4, 8, 1, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); matchingTab.add(exactTagsPanel, - new GridBagConstraints(2, 8, 3, 1, 0.0, 0.0, - GridBagConstraints.WEST, GridBagConstraints.NONE, - new Insets(0, 0, 0, 0), 0, 0)); + new GridBagConstraints(2, 9, 3, 1, 0.0, 0.0, + GridBagConstraints.WEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); anglePanel.add(angleLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, @@ -493,6 +519,18 @@ private void jbInit() { new GridBagConstraints(50, 50, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); + stdDistancePanel.add(stdDistanceLabel, + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + stdDistancePanel.add(stdDistanceBelow, + new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); + stdDistancePanel.add(stdDistanceThresholdField, + new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, + GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0)); centroidPanel.add(centroidLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, @@ -529,40 +567,49 @@ public FeatureMatcher createFeatureMatcher() { if (getFilterByWindowCheckBox().isSelected()) { chainArgs.add(new WindowFilter(filterByWindowField.getDouble())); } + if (stdDistanceCheckBox.isSelected()) { + double max = stdDistanceThresholdField.getDouble(); + FeatureMatcher matcher = new StandardDistanceMatcher(max); + if (max > 0) { + chainArgs.add(matcher); + } + weightedArgs.add(Double.valueOf(stdDistanceWeightField.getDouble())); + weightedArgs.add(matcher); + } if (getCentroidCheckBox().isSelected()) { double max = centroidThresholdField.getDouble(); FeatureMatcher matcher = new CentroidDistanceMatcher(max); if (max > 0) { chainArgs.add(matcher); } - weightedArgs.add(new Double(getCentroidDistanceWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getCentroidDistanceWeightField().getDouble())); weightedArgs.add(matcher); } if (getHausdorffCheckBox().isSelected()) { - weightedArgs.add(new Double(getHausdorffDistanceWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getHausdorffDistanceWeightField().getDouble())); weightedArgs.add(new CentroidAligner(new HausdorffDistanceMatcher())); } if (getSymDiffCheckBox().isSelected()) { - weightedArgs.add(new Double(getSymDiffWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getSymDiffWeightField().getDouble())); weightedArgs.add(new SymDiffMatcher()); } if (getSymDiffCentroidsAlignedCheckBox().isSelected()) { - weightedArgs.add(new Double(getSymDiffCentroidsAlignedWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getSymDiffCentroidsAlignedWeightField().getDouble())); weightedArgs.add(new CentroidAligner(new SymDiffMatcher())); } if (getCompactnessCheckBox().isSelected()) { - weightedArgs.add(new Double(getCompactnessWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getCompactnessWeightField().getDouble())); weightedArgs.add(new CompactnessMatcher()); } if (getAngleCheckBox().isSelected()) { - weightedArgs.add(new Double(getAngleWeightField().getDouble())); + weightedArgs.add(Double.valueOf(getAngleWeightField().getDouble())); weightedArgs.add(new AngleHistogramMatcher(getAngleBinField().getInteger())); } if (levenshteinTagsCheckBox.isSelected()) { double weight = levenshteinTagsWeightField.getDouble(); List tags = SimpleMatchFinderPanel.splitBySpaceComaOrSemicolon(levenshteinTagsField.getText()); for (String tag: tags) { - weightedArgs.add(new Double(weight / tags.size())); + weightedArgs.add(Double.valueOf(weight / tags.size())); weightedArgs.add(new AttributeMatcher(tag, LevenshteinDistanceValueMatcher.INSTANCE, OsmNormalizeRule.get(tag))); } @@ -601,6 +648,7 @@ public void savePreferences() { Main.pref.put(getClass().getName() + ".filterByAreaCheckBox", filterByAreaCheckBox.isSelected()); Main.pref.put(getClass().getName() + ".filterByWindowCheckBox", filterByWindowCheckBox.isSelected()); Main.pref.put(getClass().getName() + ".unionCheckBox", unionCheckBox.isSelected()); + Main.pref.put(getClass().getName() + ".stdDistanceCheckBox", stdDistanceCheckBox.isSelected()); Main.pref.put(getClass().getName() + ".centroidCheckBox", centroidCheckBox.isSelected()); Main.pref.put(getClass().getName() + ".hausdorffCheckBox", hausdorffCheckBox.isSelected()); Main.pref.put(getClass().getName() + ".symDiffCheckBox", symDiffCheckBox.isSelected()); @@ -613,6 +661,8 @@ public void savePreferences() { Main.pref.putDouble(getClass().getName() + ".filterByAreaMinField", filterByAreaMinField.getDouble()); Main.pref.putDouble(getClass().getName() + ".filterByAreaMaxField", filterByAreaMaxField.getDouble()); Main.pref.putDouble(getClass().getName() + ".filterByWindowField", filterByWindowField.getDouble()); + Main.pref.putDouble(getClass().getName() + ".stdDistanceWeightField", stdDistanceWeightField.getDouble()); + Main.pref.putDouble(getClass().getName() + ".stdDistanceThresholdField", stdDistanceThresholdField.getDouble()); Main.pref.putDouble(getClass().getName() + ".centroidDistanceWeightField", centroidDistanceWeightField.getDouble()); Main.pref.putDouble(getClass().getName() + ".centroidThresholdField", centroidThresholdField.getDouble()); Main.pref.putDouble(getClass().getName() + ".hausdorffDistanceWeightField", hausdorffDistanceWeightField.getDouble()); @@ -629,6 +679,7 @@ public void restoreFromPreferences() { filterByAreaCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".filterByAreaCheckBox", true)); filterByWindowCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".filterByWindowCheckBox", true)); unionCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".unionCheckBox", false)); + stdDistanceCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".stdDistanceCheckBox", true)); centroidCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".centroidCheckBox", true)); hausdorffCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".hausdorffCheckBox", true)); symDiffCheckBox.setSelected(Main.pref.getBoolean(getClass().getName() + ".symDiffCheckBox", true)); @@ -641,6 +692,9 @@ public void restoreFromPreferences() { filterByAreaMinField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".filterByAreaMinField", 0.0))); filterByAreaMaxField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".filterByAreaMaxField", 9E6))); filterByWindowField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".filterByWindowField", 50.0))); + stdDistanceWeightField.setText("" + Double.max(0.0, + Main.pref.getDouble(getClass().getName() + ".stdDistanceWeightField", 10.0))); + stdDistanceThresholdField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".stdDistanceThresholdField", 50.0))); centroidDistanceWeightField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".centroidDistanceWeightField", 10.0))); centroidThresholdField.setText("" + Double.max(0.0, Main.pref.getDouble(getClass().getName() + ".centroidThresholdField", 50.0))); diff --git a/src/org/openstreetmap/josm/plugins/conflation/config/ProgrammingMatchFinderPanel.java b/src/org/openstreetmap/josm/plugins/conflation/config/ProgrammingMatchFinderPanel.java index 20f8426..187a94b 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/config/ProgrammingMatchFinderPanel.java +++ b/src/org/openstreetmap/josm/plugins/conflation/config/ProgrammingMatchFinderPanel.java @@ -42,10 +42,12 @@ public final class ProgrammingMatchFinderPanel extends MatchFinderPanel { "\t\t\tBasic(\n" + "\t\t\t\tChain(\n" + "\t\t\t\t\tWindow( 50 ),\n" + + "\t\t\t\t\tStandardDistance( 50 ),\n" + "\t\t\t\t\tCentroidDistance( 50 ),\n" + "\t\t\t\t\tAttribute( 'exact_tag', Exact, NONE ),\n" + "\t\t\t\t\tWeighted(\n" + - "\t\t\t\t\t\t10, CentroidDistance( 0 ),\n" + + "\t\t\t\t\t\t10, StandardDistance( 50 ),\n" + + "\t\t\t\t\t\t10, CentroidDistance( 50 ),\n" + "\t\t\t\t\t\t10, CentroidAligner( HausdorffDistance( 0 ) ),\n" + "\t\t\t\t\t\t10, SymDiff,\n" + "\t\t\t\t\t\t10, CentroidAligner( SymDiff ),\n" + @@ -94,6 +96,7 @@ public void restoreFromPreferences() { editorPanel.getTextArea().setText(Main.pref.get(getClass().getName() + ".expression", SIMPLE_EXAMPLE)); } + @Override public void savePreferences() { Main.pref.put(getClass().getName() + ".expression", editorPanel.getTextArea().getText()); } @@ -197,6 +200,11 @@ public void savePreferences() { "IdenticalFilter", "Filters out matches where features are identical.", new String[] {}), + new InstanceConstructor( + org.openstreetmap.josm.plugins.conflation.matcher.StandardDistanceMatcher.class, + "StandardDistance", + "Standard Distance (i.e. the minimum) between the two geometries.", + new String[] {"maximum distance, if 0 then score will be relative to the combined envelope diagonale."}), new InstanceConstructor( com.vividsolutions.jcs.conflate.polygonmatch.MinScoreMatcher.class, "MinScore", diff --git a/src/org/openstreetmap/josm/plugins/conflation/config/SettingsDialog.java b/src/org/openstreetmap/josm/plugins/conflation/config/SettingsDialog.java index 88ebb56..1f42538 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/config/SettingsDialog.java +++ b/src/org/openstreetmap/josm/plugins/conflation/config/SettingsDialog.java @@ -147,6 +147,8 @@ public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { }; this.addComponentListener(new ComponentAdapter() { private boolean listenersAdded = false; + + @Override public void componentHidden(ComponentEvent e) { if (listenersAdded) { DataSet.removeSelectionListener(selectionChangeListener); @@ -155,6 +157,7 @@ public void componentHidden(ComponentEvent e) { } } + @Override public void componentShown(ComponentEvent e) { if (!listenersAdded) { DataSet.addSelectionListener(selectionChangeListener); @@ -660,7 +663,7 @@ public void visitKeyValue(AbstractPrimitive primitive, String key, String value) } p.visitKeys(referenceKeysVisitor); } - referenceKeys.remove(OsmPrimitive.getDiscardableKeys()); + referenceKeys.removeAll(OsmPrimitive.getDiscardableKeys()); referenceTagsAutoCompletionList.clear(); referenceTagsAutoCompletionList.add(referenceKeys, AutoCompletionItemPriority.IS_IN_DATASET); referenceLayerLabel.setText(referenceLayer.getName()); diff --git a/src/org/openstreetmap/josm/plugins/conflation/config/SimpleMatchFinderPanel.java b/src/org/openstreetmap/josm/plugins/conflation/config/SimpleMatchFinderPanel.java index b3d922c..48c0273 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/config/SimpleMatchFinderPanel.java +++ b/src/org/openstreetmap/josm/plugins/conflation/config/SimpleMatchFinderPanel.java @@ -19,6 +19,7 @@ import org.openstreetmap.josm.plugins.conflation.matcher.AttributeMatcher; import org.openstreetmap.josm.plugins.conflation.matcher.LevenshteinDistanceValueMatcher; import org.openstreetmap.josm.plugins.conflation.matcher.OsmNormalizeRule; +import org.openstreetmap.josm.plugins.conflation.matcher.StandardDistanceMatcher; import com.vividsolutions.jcs.conflate.polygonmatch.AbstractDistanceMatcher; import com.vividsolutions.jcs.conflate.polygonmatch.BasicFCMatchFinder; @@ -42,7 +43,7 @@ public class SimpleMatchFinderPanel extends MatchFinderPanel { private final String[] methodString = {tr("Disambiguating"), tr("One to One")}; private final JComboBox methodCombeBox = new JComboBox<>(methodString); private final JLabel distanceLabel = new JLabel(tr("Distance")); - private final String[] distanceStrings = {tr("Centroid"), tr("Hausdorff")}; + private final String[] distanceStrings = {tr("Standard"), tr("Centroid"), tr("Hausdorff")}; private final JComboBox distanceComboBox = new JComboBox<>(distanceStrings); private final JLabel threshDistanceLabel = new JLabel(" < "); private final MyValidatingTextField threshDistanceField = new MyValidatingTextField( @@ -52,7 +53,6 @@ public class SimpleMatchFinderPanel extends MatchFinderPanel { public SimpleMatchFinderPanel(AutoCompletionList referenceKeysAutocompletionList) { super(); - distanceComboBox.setSelectedIndex(1); threshDistanceField.setToolTipText(tr("Maximum Distance")); tagsField.setToolTipText(tr("List of tags to match (default: none)")); methodCombeBox.setFont(methodCombeBox.getFont().deriveFont(Font.PLAIN)); @@ -97,14 +97,19 @@ public SimpleMatchFinderPanel(AutoCompletionList referenceKeysAutocompletionList restoreFromPreferences(); } + @Override public FCMatchFinder getMatchFinder() { ArrayList matchers = new ArrayList<>(); if (threshDistanceField.getDouble() > 0) { // Use a WindowMatcher limit the search area and speed up execution time. matchers.add(new WindowMatcher(threshDistanceField.getDouble())); } - AbstractDistanceMatcher distanceMatcher = (distanceComboBox.getSelectedIndex() == 0) ? - new CentroidDistanceMatcher() : new HausdorffDistanceMatcher(); + AbstractDistanceMatcher distanceMatcher; + switch(distanceComboBox.getSelectedIndex()) { + case 1: distanceMatcher = new CentroidDistanceMatcher(); break; + case 2: distanceMatcher = new HausdorffDistanceMatcher(); break; + default: distanceMatcher = new StandardDistanceMatcher(); break; + } distanceMatcher.setMaxDistance(threshDistanceField.getDouble()); matchers.add(distanceMatcher); List tags = splitBySpaceComaOrSemicolon(tagsField.getText()); @@ -140,6 +145,7 @@ public void restoreFromPreferences() { tagsField.setText(Main.pref.get(getClass().getName() + ".tags", "")); } + @Override public void savePreferences() { Main.pref.putInteger(getClass().getName() + ".methodIndex", methodCombeBox.getSelectedIndex()); Main.pref.putInteger(getClass().getName() + ".distanceIndex", distanceComboBox.getSelectedIndex()); diff --git a/src/org/openstreetmap/josm/plugins/conflation/config/parser/InstanceParser.java b/src/org/openstreetmap/josm/plugins/conflation/config/parser/InstanceParser.java index 3c316b4..6316df2 100644 --- a/src/org/openstreetmap/josm/plugins/conflation/config/parser/InstanceParser.java +++ b/src/org/openstreetmap/josm/plugins/conflation/config/parser/InstanceParser.java @@ -50,6 +50,7 @@ public boolean parse(String text) { return isValid(); } + @Override public String toString() { StringBuilder sb = new StringBuilder("InstanceParser(" + mainType.getSimpleName() + ")\n"); sb.append("text: '" + text + "'\n"); @@ -131,21 +132,21 @@ public List getCompletionList() { private T parse(Class type, boolean lastArg, String description) { this.completionList.clear(); if (Byte.TYPE.isAssignableFrom(type) || Byte.class.isAssignableFrom(type)) { - return (T) new Byte(findToken(INTEGER_PATTERN, description, "Integer number")); + return (T) Byte.valueOf(findToken(INTEGER_PATTERN, description, "Integer number")); } else if (Short.TYPE.isAssignableFrom(type) || Short.class.isAssignableFrom(type)) { - return (T) new Short(findToken(INTEGER_PATTERN, description, "Integer number")); + return (T) Short.valueOf(findToken(INTEGER_PATTERN, description, "Integer number")); } else if (Integer.TYPE.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) { - return (T) new Integer(findToken(INTEGER_PATTERN, description, "Integer number")); + return (T) Integer.valueOf(findToken(INTEGER_PATTERN, description, "Integer number")); } else if (Long.TYPE.isAssignableFrom(type) || Long.class.isAssignableFrom(type)) { - return (T) new Long(findToken(INTEGER_PATTERN, description, "Integer number")); + return (T) Long.valueOf(findToken(INTEGER_PATTERN, description, "Integer number")); } else if (Float.TYPE.isAssignableFrom(type) || Float.class.isAssignableFrom(type)) { - return (T) new Float(findToken(FLOAT_PATTERN, description, "Floating-point number")); + return (T) Float.valueOf(findToken(FLOAT_PATTERN, description, "Floating-point number")); } else if (Double.TYPE.isAssignableFrom(type) || Double.class.isAssignableFrom(type)) { - return (T) new Double(findToken(FLOAT_PATTERN, description, "Floating-point number")); + return (T) Double.valueOf(findToken(FLOAT_PATTERN, description, "Floating-point number")); } else if (Boolean.TYPE.isAssignableFrom(type) || Boolean.class.isAssignableFrom(type)) { completionList.add("true"); completionList.add("false"); - return (T) new Boolean(findToken(BOOLEAN_PATTERN, description, "True or false")); + return (T) Boolean.valueOf(findToken(BOOLEAN_PATTERN, description, "True or false")); } else if (Character.TYPE.isAssignableFrom(type) || Character.class.isAssignableFrom(type)) { String s = findToken(STRING_PATTERN, description, "Quoted char"); //TODO: correctly unescaping the string @@ -153,7 +154,7 @@ private T parse(Class type, boolean lastArg, String description) { if (s.length() != 3) { throw new Error("A single character was exepcted"); } - return (T) new Character(s.charAt(1)); + return (T) Character.valueOf(s.charAt(1)); } else if (String.class.isAssignableFrom(type)) { String s = findToken(STRING_PATTERN, description, "Quoted string"); //TODO: correctly unescaping the string diff --git a/src/org/openstreetmap/josm/plugins/conflation/matcher/StandardDistanceMatcher.java b/src/org/openstreetmap/josm/plugins/conflation/matcher/StandardDistanceMatcher.java new file mode 100644 index 0000000..d0aa95f --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/conflation/matcher/StandardDistanceMatcher.java @@ -0,0 +1,24 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.conflation.matcher; + +import com.vividsolutions.jcs.conflate.polygonmatch.AbstractDistanceMatcher; +import com.vividsolutions.jts.geom.Geometry; + +/** + * Compute the standard distance (i.e. the minimum) between two geometries. + */ +public class StandardDistanceMatcher extends AbstractDistanceMatcher { + + public StandardDistanceMatcher() {} + + public StandardDistanceMatcher(double maxDistance) { + super(); + setMaxDistance(maxDistance); + } + + @Override + protected double distance(Geometry target, Geometry candidate) { + return target.distance(candidate); + } + +} diff --git a/test/unit/org/openstreetmap/josm/plugins/conflation/SimpleMatchTest.java b/test/unit/org/openstreetmap/josm/plugins/conflation/SimpleMatchTest.java new file mode 100644 index 0000000..45c04cb --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/conflation/SimpleMatchTest.java @@ -0,0 +1,32 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.conflation; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.TagCollection; + +public class SimpleMatchTest { + + @Test + public void testGetMergingTagCollectionOverwrite() { + SimpleMatchSettings settings = new SimpleMatchSettings(); + settings.isReplacingGeometry = true; + settings.mergeTags = SimpleMatchSettings.ALL; + settings.overwriteTags = Arrays.asList("addr:housenumber"); + OsmPrimitive n1 = new Node(); + OsmPrimitive n2 = new Node(); + SimpleMatch match = new SimpleMatch(n1, n2, 0.5, 10.0); + n1.put("addr:housenumber", "1"); + n2.put("addr:housenumber", "2"); + n1.put("addr:street", "Street One"); + n2.put("addr:street", "Street One Two"); + TagCollection tagCollection = match.getMergingTagCollection(settings); + assertTrue(tagCollection.getNumTagsFor("addr:street") == 2); + assertTrue(tagCollection.getNumTagsFor("addr:housenumber") == 1); + } +}