Ticket #7278: resolve_tag_conflicts.patch
File resolve_tag_conflicts.patch, 16.2 KB (added by , 13 years ago) |
---|
-
src/utilsplugin2/UtilsPlugin2.java
1 1 // License: GPL v2 or later. See LICENSE file for details. 2 2 package utilsplugin2; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 4 6 import utilsplugin2.customurl.ChooseURLAction; 5 7 import utilsplugin2.customurl.OpenPageAction; 6 8 import utilsplugin2.customurl.UtilsPluginPreferences; 7 9 8 10 import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 9 11 import java.awt.event.KeyEvent; 12 import java.util.Arrays; 13 import java.util.Collection; 14 import java.util.HashSet; 15 import java.util.Set; 10 16 import javax.swing.JMenu; 11 17 import javax.swing.JMenuItem; 12 18 import utilsplugin2.selection.*; 13 19 import utilsplugin2.dumbutils.*; 14 20 15 21 import org.openstreetmap.josm.Main; 22 import org.openstreetmap.josm.actions.search.PushbackTokenizer; 23 import org.openstreetmap.josm.actions.search.SearchCompiler; 24 import org.openstreetmap.josm.actions.search.SearchCompiler.*; 25 import org.openstreetmap.josm.data.osm.OsmPrimitive; 26 import org.openstreetmap.josm.data.osm.Relation; 27 import org.openstreetmap.josm.data.osm.Way; 16 28 import org.openstreetmap.josm.gui.MainMenu; 17 29 import org.openstreetmap.josm.gui.MapFrame; 18 30 import org.openstreetmap.josm.plugins.Plugin; … … 89 101 90 102 selectURL = MainMenu.add(toolsMenu, new ChooseURLAction()); 91 103 92 104 // register search operators 105 SearchCompiler.addMatchFactory(new UtilsUnaryMatchFactory()); 93 106 } 94 107 95 108 @Override … … 126 139 public PreferenceSetting getPreferenceSetting() { 127 140 return new UtilsPluginPreferences(); 128 141 } 142 143 public static class UtilsUnaryMatchFactory implements UnaryMatchFactory { 144 private static Collection<String> keywords = Arrays.asList("inside", 145 "intersecting", "allintersecting"); 146 147 @Override 148 public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError { 149 if ("inside".equals(keyword)) 150 return new InsideMatch(matchOperand); 151 else if ("intersecting".equals(keyword)) 152 return new IntersectingMatch(matchOperand, false); 153 else if ("allintersecting".equals(keyword)) 154 return new IntersectingMatch(matchOperand, true); 155 return null; 156 } 129 157 158 @Override 159 public Collection<String> getKeywords() { 160 return keywords; 161 } 162 } 163 164 /** 165 * Matches all objects contained within the match expression. 166 */ 167 public static class InsideMatch extends UnaryMatch { 168 private Collection<OsmPrimitive> inside = null; 169 170 public InsideMatch(Match match) { 171 super(match); 172 init(); 173 } 174 175 /** 176 * Find all objects inside areas which match the expression 177 */ 178 private void init() { 179 Collection<OsmPrimitive> matchedAreas = new HashSet<OsmPrimitive>(); 180 181 // find all ways that match the expression 182 Collection<Way> ways = Main.main.getCurrentDataSet().getWays(); 183 for (Way way : ways) { 184 if (match.match(way)) 185 matchedAreas.add(way); 186 } 187 188 // find all relations that match the expression 189 Collection<Relation> rels = Main.main.getCurrentDataSet().getRelations(); 190 for (Relation rel : rels) { 191 if (match.match(rel)) 192 matchedAreas.add(rel); 193 } 194 195 inside = NodeWayUtils.selectAllInside(matchedAreas, Main.main.getCurrentDataSet()); 196 } 197 198 @Override 199 public boolean match(OsmPrimitive osm) { 200 return inside.contains(osm); 201 } 202 } 203 204 public static class IntersectingMatch extends UnaryMatch { 205 private Collection<Way> intersecting = null; 206 207 public IntersectingMatch(Match match, boolean all) { 208 super(match); 209 init(all); 210 } 211 212 /** 213 * Find (all) ways intersecting ways which match the expression. 214 */ 215 private void init(boolean all) { 216 Collection<Way> matchedWays = new HashSet<Way>(); 217 218 // find all ways that match the expression 219 Collection<Way> allWays = Main.main.getCurrentDataSet().getWays(); 220 for (Way way : allWays) { 221 if (match.match(way)) 222 matchedWays.add(way); 223 } 224 225 Set<Way> newWays = new HashSet<Way>(); 226 if (all) 227 NodeWayUtils.addWaysIntersectingWaysRecursively(allWays, matchedWays, newWays); 228 else 229 NodeWayUtils.addWaysIntersectingWays(allWays, matchedWays, newWays); 230 intersecting = newWays; 231 } 232 233 @Override 234 public boolean match(OsmPrimitive osm) { 235 return intersecting.contains(osm); 236 } 237 } 130 238 } -
src/utilsplugin2/selection/SelectAllInsideAction.java
14 14 15 15 import org.openstreetmap.josm.Main; 16 16 import org.openstreetmap.josm.actions.JosmAction; 17 import org.openstreetmap.josm.data.osm.Node; 18 import org.openstreetmap.josm.data.osm.OsmPrimitive; 19 import org.openstreetmap.josm.data.osm.Relation; 20 import org.openstreetmap.josm.data.osm.Way; 17 import org.openstreetmap.josm.data.osm.*; 21 18 import org.openstreetmap.josm.tools.Shortcut; 22 19 23 20 /** … … 31 28 KeyEvent.VK_I, Shortcut.GROUP_EDIT ,KeyEvent.ALT_DOWN_MASK|KeyEvent.SHIFT_DOWN_MASK), true); 32 29 putValue("help", ht("/Action/SelectAllInside")); 33 30 } 34 31 32 @Override 35 33 public void actionPerformed(ActionEvent e) { 36 34 long t=System.currentTimeMillis(); 37 Set<Way> selectedWays = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Way.class); 38 Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(getCurrentDataSet().getSelected(), Relation.class); 39 40 for (Relation r: selectedRels) { 41 if (!r.isMultipolygon()) selectedRels.remove(r); 42 } 43 44 Set<Way> newWays = new HashSet<Way>(); 45 Set<Node> newNodes = new HashSet<Node>(); 46 // select ways attached to already selected ways 47 if (!selectedWays.isEmpty()) { 48 for (Way w: selectedWays) { 49 NodeWayUtils.addAllInsideWay(getCurrentDataSet(),w,newWays,newNodes); 50 } 51 } 52 if (!selectedRels.isEmpty()) { 53 for (Relation r: selectedRels) { 54 NodeWayUtils.addAllInsideMultipolygon(getCurrentDataSet(),r,newWays,newNodes); 55 } 56 } 57 if (!newWays.isEmpty() || !newNodes.isEmpty()) { 58 getCurrentDataSet().addSelected(newWays); 59 getCurrentDataSet().addSelected(newNodes); 35 Collection<OsmPrimitive> insideSelected = NodeWayUtils.selectAllInside(getCurrentDataSet().getSelected(), getCurrentDataSet()); 36 37 if (!insideSelected.isEmpty()) { 38 getCurrentDataSet().addSelected(insideSelected); 60 39 } else{ 61 40 JOptionPane.showMessageDialog(Main.parent, 62 41 tr("Nothing found. Please select some closed ways or multipolygons to find all primitives inside them!"), -
src/utilsplugin2/selection/NodeWayUtils.java
147 147 * @param initWays ways to check intersections 148 148 * @param newWays set to place the ways we found 149 149 */ 150 static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) {150 public static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) { 151 151 int count=0; 152 152 for (Way w : initWays){ 153 153 count+=addWaysIntersectingWay(allWays, w, newWays); … … 171 171 return newNodes.size()-s; 172 172 } 173 173 174 static void addWaysIntersectingWaysRecursively174 public static void addWaysIntersectingWaysRecursively 175 175 (Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) 176 176 { 177 177 Set<Way> foundWays = new HashSet<Way>(); … … 457 457 // System.out.printf("Intersected intercount %d %s\n",interCount, point.toString()); 458 458 if (interCount%2 == 1) return 1; else return 0; 459 459 } 460 461 public static Collection<OsmPrimitive> selectAllInside(Collection<OsmPrimitive> selected, DataSet dataset) { 462 Set<Way> selectedWays = OsmPrimitive.getFilteredSet(selected, Way.class); 463 Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(selected, Relation.class); 460 464 465 for (Relation r: selectedRels) { 466 if (!r.isMultipolygon()) selectedRels.remove(r); 467 } 468 469 Set<Way> newWays = new HashSet<Way>(); 470 Set<Node> newNodes = new HashSet<Node>(); 471 // select ways attached to already selected ways 472 if (!selectedWays.isEmpty()) { 473 for (Way w: selectedWays) { 474 addAllInsideWay(dataset,w,newWays,newNodes); 475 } 476 } 477 if (!selectedRels.isEmpty()) { 478 for (Relation r: selectedRels) { 479 addAllInsideMultipolygon(dataset,r,newWays,newNodes); 480 } 481 } 482 483 Set<OsmPrimitive> insideSelection = new HashSet<OsmPrimitive>(); 484 if (!newWays.isEmpty() || !newNodes.isEmpty()) { 485 insideSelection.addAll(newWays); 486 insideSelection.addAll(newNodes); 487 } 488 return insideSelection; 489 } 490 491 461 492 } -
src/utilsplugin2/dumbutils/ReplaceGeometryAction.java
12 12 import org.openstreetmap.josm.tools.Shortcut; 13 13 import java.awt.event.ActionEvent; 14 14 import org.openstreetmap.josm.actions.JosmAction; 15 import org.openstreetmap.josm.data.osm. OsmPrimitive;15 import org.openstreetmap.josm.data.osm.*; 16 16 import org.openstreetmap.josm.gui.DefaultNameFormatter; 17 import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog; 18 import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil; 17 19 import static org.openstreetmap.josm.tools.I18n.tr; 18 20 19 21 /** … … 62 64 return; 63 65 } 64 66 } 65 67 66 68 public void replaceNodeWithWay(Node node, Way way) { 67 69 if (!node.getReferrers().isEmpty()) { 68 70 JOptionPane.showMessageDialog(Main.parent, tr("Node has referrers, cannot replace with way."), TITLE, JOptionPane.INFORMATION_MESSAGE); … … 75 77 Collection<Node> nodePool = getUnimportantNodes(way); 76 78 nodeToReplace = findNearestNode(node, nodePool); 77 79 } 78 80 79 81 List<Command> commands = new ArrayList<Command>(); 80 82 AbstractMap<String, String> nodeTags = (AbstractMap<String, String>) node.getKeys(); 81 83 84 // merge tags 85 Collection<Command> tagResolutionCommands = getTagConflictResolutionCommands(node, way); 86 if (tagResolutionCommands == null) { 87 // user canceled tag merge dialog 88 return; 89 } 90 commands.addAll(tagResolutionCommands); 91 82 92 // replace sacrificial node in way with node that is being upgraded 83 93 if (nodeToReplace != null) { 84 94 List<Node> wayNodes = way.getNodes(); 85 95 int idx = wayNodes.indexOf(nodeToReplace); 86 96 wayNodes.set(idx, node); 87 if (idx == 0 ) {97 if (idx == 0 && way.isClosed()) { 88 98 // node is at start/end of way 89 99 wayNodes.set(wayNodes.size() - 1, node); 90 100 } 91 101 commands.add(new ChangeNodesCommand(way, wayNodes)); 92 102 commands.add(new MoveCommand(node, nodeToReplace.getCoor())); 93 103 commands.add(new DeleteCommand(nodeToReplace)); 94 104 95 105 // delete tags from node 96 106 if (!nodeTags.isEmpty()) { 97 107 AbstractMap<String, String> nodeTagsToDelete = new HashMap<String, String>(); … … 105 115 commands.add(new DeleteCommand(node)); 106 116 } 107 117 108 // Copy tags from node109 // TODO: use merge tag conflict dialog instead110 commands.add(new ChangePropertyCommand(Arrays.asList(way), nodeTags));111 112 118 getCurrentDataSet().setSelected(way); 113 119 114 120 Main.main.undoRedo.add(new SequenceCommand( 115 121 tr("Replace geometry for way {0}", way.getDisplayName(DefaultNameFormatter.getInstance())), 116 122 commands)); 117 123 } 118 124 119 125 public void replaceWayWithWay(List<Way> selection) { 120 126 boolean overrideNewCheck = false; 121 127 int idxNew = selection.get(0).isNew() ? 0 : 1; … … 186 192 for( Node node : nodeAssoc.keySet() ) 187 193 commands.add(new MoveCommand(nodeAssoc.get(node), node.getCoor())); 188 194 189 // Copy tags from temporary way (source etc.) 190 for( String key : geometry.keySet() ) 191 commands.add(new ChangePropertyCommand(way, key, geometry.get(key))); 195 // Merge tags from source to target way 196 commands.addAll(getTagConflictResolutionCommands(geometry, way)); 192 197 193 198 // Remove geometry way from selection 194 199 getCurrentDataSet().clearSelection(geometry); … … 208 213 209 214 /** 210 215 * Create a list of nodes that are not used anywhere except in the way. 216 * 211 217 * @param way 212 * @return 218 * @return 213 219 */ 214 220 public Collection<Node> getUnimportantNodes(Way way) { 215 221 Set<Node> nodePool = new HashSet<Node>(); … … 223 229 } 224 230 return nodePool; 225 231 } 226 232 227 233 /** 234 * Merge tags from source to target object, showing resolution dialog if 235 * needed. 236 * 237 * @param source 238 * @param target 239 * @return 240 */ 241 public List<Command> getTagConflictResolutionCommands(OsmPrimitive source, OsmPrimitive target) { 242 Collection<OsmPrimitive> primitives = Arrays.asList(source, target); 243 244 Set<RelationToChildReference> relationToNodeReferences = RelationToChildReference.getRelationToChildReferences(primitives); 245 246 // build the tag collection 247 TagCollection tags = TagCollection.unionOfAllPrimitives(primitives); 248 TagConflictResolutionUtil.combineTigerTags(tags); 249 TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(tags, primitives); 250 TagCollection tagsToEdit = new TagCollection(tags); 251 TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit); 252 253 // launch a conflict resolution dialog, if necessary 254 CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance(); 255 dialog.getTagConflictResolverModel().populate(tagsToEdit, tags.getKeysWithMultipleValues()); 256 dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences); 257 dialog.setTargetPrimitive(target); 258 dialog.prepareDefaultDecisions(); 259 260 // conflict resolution is necessary if there are conflicts in the merged tags 261 // or if at least one of the merged nodes is referred to by a relation 262 if (!tags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) { 263 dialog.setVisible(true); 264 if (dialog.isCanceled()) { 265 return null; 266 } 267 } 268 return dialog.buildResolutionCommands(); 269 } 270 271 /** 228 272 * Find node from the collection which is nearest to <tt>node</tt>. Max distance is taken in consideration. 229 273 * @return null if there is no such node. 230 274 */ 231 275 private Node findNearestNode( Node node, Collection<Node> nodes ) { 232 276 if( nodes.contains(node) ) 233 277 return node; 234 278 235 279 Node nearest = null; 236 280 double distance = MAX_NODE_REPLACEMENT_DISTANCE; 237 281 Point2D coor = node.getCoor();