source: osm/applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java@ 35032

Last change on this file since 35032 was 34972, checked in by donvip, 6 years ago

see #josm17580 #josm17581 #josm17582 #josm17583 #josm17584 #josm17585 #josm17586 #josm17587 #josm17588 - remove deprecated api (patches by taylor.smock)

File size: 19.3 KB
Line 
1package mergeoverlap;
2
3import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.applyAutomaticTagConflictResolution;
4import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
5import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.LinkedHashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import org.openstreetmap.josm.actions.JosmAction;
22import org.openstreetmap.josm.actions.corrector.ReverseWayTagCorrector;
23import org.openstreetmap.josm.command.ChangeCommand;
24import org.openstreetmap.josm.command.Command;
25import org.openstreetmap.josm.command.DeleteCommand;
26import org.openstreetmap.josm.command.SequenceCommand;
27import org.openstreetmap.josm.command.SplitWayCommand;
28import org.openstreetmap.josm.data.UndoRedoHandler;
29import org.openstreetmap.josm.data.osm.Node;
30import org.openstreetmap.josm.data.osm.NodeGraph;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.TagCollection;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.tools.Pair;
37import org.openstreetmap.josm.tools.Shortcut;
38import org.openstreetmap.josm.tools.UserCancelException;
39import org.openstreetmap.josm.tools.Utils;
40
41import mergeoverlap.hack.MyCombinePrimitiveResolverDialog;
42
43/**
44 * Merge overlapping part of ways.
45 */
46public class MergeOverlapAction extends JosmAction {
47
48 Map<Way, List<Relation>> relations = new HashMap<>();
49 Map<Way, Way> oldWays = new HashMap<>();
50 Map<Relation, Relation> newRelations = new HashMap<>();
51 Set<Way> deletes = new HashSet<>();
52
53 /**
54 * Constructs a new {@code MergeOverlapAction}.
55 */
56 public MergeOverlapAction() {
57 super(tr("Merge overlap"), "merge_overlap",
58 tr("Merge overlap of ways."),
59 Shortcut.registerShortcut("tools:mergeoverlap",tr("Tool: {0}", tr("Merge overlap")), KeyEvent.VK_O,
60 Shortcut.ALT_CTRL), true);
61 }
62
63 /**
64 * The action button has been clicked
65 *
66 * @param e
67 * Action Event
68 */
69 @Override
70 public void actionPerformed(ActionEvent e) {
71
72 // List of selected ways
73 List<Way> ways = new ArrayList<>();
74 relations.clear();
75 newRelations.clear();
76
77 // For every selected way
78 for (OsmPrimitive osm : getLayerManager().getEditDataSet().getSelected()) {
79 if (osm instanceof Way && !osm.isDeleted()) {
80 Way way = (Way) osm;
81 ways.add(way);
82 List<Relation> rels = new ArrayList<>();
83 for (Relation r : Utils.filteredCollection(way.getReferrers(), Relation.class)) {
84 rels.add(r);
85 }
86 relations.put(way, rels);
87 }
88 }
89
90 List<Way> sel = new ArrayList<>(ways);
91 Collection<Command> cmds = new LinkedList<>();
92
93 // *****
94 // split
95 // *****
96 for (Way way : ways) {
97 Set<Node> nodes = new HashSet<>();
98 for (Way opositWay : ways) {
99 if (way != opositWay) {
100 List<NodePos> nodesPos = new LinkedList<>();
101
102 int pos = 0;
103 for (Node node : way.getNodes()) {
104 int opositPos = 0;
105 for (Node opositNode : opositWay.getNodes()) {
106 if (node == opositNode) {
107 if (opositWay.isClosed()) {
108 opositPos %= opositWay.getNodesCount() - 1;
109 }
110 nodesPos.add(new NodePos(node, pos, opositPos));
111 break;
112 }
113 opositPos++;
114 }
115 pos++;
116 }
117
118 NodePos start = null;
119 NodePos end = null;
120 int increment = 0;
121
122 boolean hasFirst = false;
123 for (NodePos node : nodesPos) {
124 if (start == null) {
125 start = node;
126 } else {
127 if (end == null) {
128 if (follows(way, opositWay, start, node, 1)) {
129 end = node;
130 increment = +1;
131 } else if (follows(way, opositWay, start, node, -1)) {
132 end = node;
133 increment = -1;
134 } else {
135 start = node;
136 end = null;
137 }
138 } else {
139 if (follows(way, opositWay, end, node, increment)) {
140 end = node;
141 } else {
142 hasFirst = addNodes(start, end, way, nodes, hasFirst);
143 start = node;
144 end = null;
145 }
146 }
147 }
148 }
149
150 if (start != null && end != null) {
151 hasFirst = addNodes(start, end, way, nodes, hasFirst);
152 start = null;
153 end = null;
154 }
155 }
156 }
157 if (!nodes.isEmpty() && !way.isClosed() || nodes.size() >= 2) {
158 List<List<Node>> wayChunks = SplitWayCommand.buildSplitChunks(way, new ArrayList<>(nodes));
159 SplitWayCommand result = SplitWayCommand.splitWay(way, wayChunks, Collections.emptyList());
160
161 cmds.add(result);
162 sel.remove(way);
163 sel.add(result.getOriginalWay());
164 sel.addAll(result.getNewWays());
165 List<Relation> rels = relations.remove(way);
166 relations.put(result.getOriginalWay(), rels);
167 for (Way w : result.getNewWays()) {
168 relations.put(w, rels);
169 }
170 }
171 }
172
173 // *****
174 // merge
175 // *****
176 ways = new ArrayList<>(sel);
177 while (!ways.isEmpty()) {
178 Way way = ways.get(0);
179 List<Way> combine = new ArrayList<>();
180 combine.add(way);
181 for (Way opositWay : ways) {
182 if (way != opositWay && way.getNodesCount() == opositWay.getNodesCount()) {
183 boolean equals1 = true;
184 for (int i = 0; i < way.getNodesCount(); i++) {
185 if (way.getNode(i) != opositWay.getNode(i)) {
186 equals1 = false;
187 break;
188 }
189 }
190 boolean equals2 = true;
191 for (int i = 0; i < way.getNodesCount(); i++) {
192 if (way.getNode(i) != opositWay.getNode(way.getNodesCount() - i - 1)) {
193 equals2 = false;
194 break;
195 }
196 }
197 if (equals1 || equals2) {
198 combine.add(opositWay);
199 }
200 }
201 }
202 ways.removeAll(combine);
203 if (combine.size() > 1) {
204 sel.removeAll(combine);
205 // combine
206 Pair<Way, List<Command>> combineResult;
207 try {
208 combineResult = combineWaysWorker(combine);
209 } catch (UserCancelException ex) {
210 return;
211 }
212 sel.add(combineResult.a);
213 cmds.addAll(combineResult.b);
214 }
215 }
216
217 for (Relation old : newRelations.keySet()) {
218 cmds.add(new ChangeCommand(old, newRelations.get(old)));
219 }
220
221 List<Way> del = new LinkedList<>();
222 for (Way w : deletes) {
223 if (w.getDataSet() != null && !w.isDeleted()) {
224 del.add(w);
225 }
226 }
227 if (!del.isEmpty()) {
228 cmds.add(new DeleteCommand(del));
229 }
230
231 // Commit
232 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Merge Overlap (combine)"), cmds));
233 getLayerManager().getEditDataSet().setSelected(sel);
234 MainApplication.getMap().repaint();
235
236 relations.clear();
237 newRelations.clear();
238 oldWays.clear();
239 }
240
241 private static class NodePos {
242 Node node;
243 int pos;
244 int opositPos;
245
246 NodePos(Node n, int p, int op) {
247 node = n;
248 pos = p;
249 opositPos = op;
250 }
251
252 @Override
253 public String toString() {
254 return "NodePos: " + pos + ", " + opositPos + ", " + node;
255 }
256 }
257
258 private boolean addNodes(NodePos start, NodePos end, Way way,
259 Set<Node> nodes, boolean hasFirst) {
260 if (way.isClosed() || (start.node != way.getNode(0) && start.node != way.getNode(way.getNodesCount() - 1))) {
261 hasFirst = hasFirst || start.node == way.getNode(0);
262 nodes.add(start.node);
263 }
264 if (way.isClosed() || (end.node != way.getNode(0) && end.node != way.getNode(way.getNodesCount() - 1))) {
265 if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
266 nodes.remove(way.getNode(0));
267 } else {
268 nodes.add(end.node);
269 }
270 }
271 return hasFirst;
272 }
273
274 private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2,
275 int incr) {
276 if (way2.isClosed() && incr == 1 && np1.opositPos == way2.getNodesCount() - 2) {
277 return np2.pos == np1.pos + 1 && np2.opositPos == 0;
278 } else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
279 return np2.pos == np1.pos && np2.opositPos == 0
280 || np2.pos == np1.pos + 1 && np2.opositPos == 1;
281 } else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
282 return np2.pos == np1.pos && np2.opositPos == 0 || np2.pos == np1.pos + 1
283 && np2.opositPos == way2.getNodesCount() - 2;
284 } else {
285 return np2.pos == np1.pos + 1 && np2.opositPos == np1.opositPos + incr;
286 }
287 }
288
289 /**
290 * @param ways
291 * @return null if ways cannot be combined. Otherwise returns the combined
292 * ways and the commands to combine
293 * @throws UserCancelException
294 */
295 private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways) throws UserCancelException {
296
297 // prepare and clean the list of ways to combine
298 if (ways == null || ways.isEmpty())
299 return null;
300 ways.remove(null); // just in case - remove all null ways from the collection
301
302 // remove duplicates, preserving order
303 ways = new LinkedHashSet<>(ways);
304
305 // try to build a new way which includes all the combined ways
306 NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
307 List<Node> path = graph.buildSpanningPath();
308
309 // check whether any ways have been reversed in the process
310 // and build the collection of tags used by the ways to combine
311 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
312
313 List<Way> reversedWays = new LinkedList<>();
314 List<Way> unreversedWays = new LinkedList<>();
315 for (Way w : ways) {
316 if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
317 unreversedWays.add(w);
318 } else {
319 reversedWays.add(w);
320 }
321 }
322 // reverse path if all ways have been reversed
323 if (unreversedWays.isEmpty()) {
324 Collections.reverse(path);
325 unreversedWays = reversedWays;
326 reversedWays = null;
327 }
328 if ((reversedWays != null) && !reversedWays.isEmpty()) {
329 // filter out ways that have no direction-dependent tags
330 unreversedWays = ReverseWayTagCorrector.irreversibleWays(unreversedWays);
331 reversedWays = ReverseWayTagCorrector.irreversibleWays(reversedWays);
332 // reverse path if there are more reversed than unreversed ways with
333 // direction-dependent tags
334 if (reversedWays.size() > unreversedWays.size()) {
335 Collections.reverse(path);
336 List<Way> tempWays = unreversedWays;
337 unreversedWays = reversedWays;
338 reversedWays = tempWays;
339 }
340 // if there are still reversed ways with direction-dependent tags,
341 // reverse their tags
342 if (!reversedWays.isEmpty()) {
343 List<Way> unreversedTagWays = new ArrayList<>(ways);
344 unreversedTagWays.removeAll(reversedWays);
345 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
346 List<Way> reversedTagWays = new ArrayList<>();
347 Collection<Command> changePropertyCommands = null;
348 for (Way w : reversedWays) {
349 Way wnew = new Way(w);
350 reversedTagWays.add(wnew);
351 changePropertyCommands = reverseWayTagCorrector.execute(w, wnew);
352 }
353 if ((changePropertyCommands != null) && !changePropertyCommands.isEmpty()) {
354 for (Command c : changePropertyCommands) {
355 c.executeCommand();
356 }
357 }
358 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
359 wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
360 }
361 }
362
363 // create the new way and apply the new node list
364 Way targetWay = getTargetWay(ways);
365 Way modifiedTargetWay = new Way(targetWay);
366 modifiedTargetWay.setNodes(path);
367
368 TagCollection completeWayTags = new TagCollection(wayTags);
369 applyAutomaticTagConflictResolution(completeWayTags);
370 normalizeTagCollectionBeforeEditing(completeWayTags, ways);
371 TagCollection tagsToEdit = new TagCollection(completeWayTags);
372 completeTagCollectionForEditing(tagsToEdit);
373
374 MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog.getInstance();
375 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
376 dialog.setTargetPrimitive(targetWay);
377 Set<Relation> parentRelations = getParentRelations(ways);
378 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, ways, oldWays);
379 dialog.prepareDefaultDecisions();
380
381 // resolve tag conflicts if necessary
382 if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
383 dialog.setVisible(true);
384 if (!dialog.isApplied())
385 throw new UserCancelException();
386 }
387
388 List<Command> cmds = new LinkedList<>();
389 deletes.addAll(ways);
390 deletes.remove(targetWay);
391
392 cmds.add(new ChangeCommand(getLayerManager().getEditDataSet(), targetWay, modifiedTargetWay));
393 cmds.addAll(dialog.buildWayResolutionCommands());
394 dialog.buildRelationCorrespondance(newRelations, oldWays);
395
396 return new Pair<>(targetWay, cmds);
397 }
398
399 private static Way getTargetWay(Collection<Way> combinedWays) {
400 // init with an arbitrary way
401 Way targetWay = combinedWays.iterator().next();
402
403 // look for the first way already existing on the server
404 for (Way w : combinedWays) {
405 targetWay = w;
406 if (!w.isNew()) {
407 break;
408 }
409 }
410 return targetWay;
411 }
412
413 /**
414 * @return has tag to be merged (=> ask)
415 */
416 private static boolean askForMergeTag(Collection<Way> ways) {
417 for (Way way : ways) {
418 for (Way oposite : ways) {
419 for (String key : way.getKeys().keySet()) {
420 if (!"source".equals(key) && oposite.hasKey(key)
421 && !way.get(key).equals(oposite.get(key))) {
422 return true;
423 }
424 }
425 }
426 }
427 return false;
428 }
429
430 /**
431 * @return has duplicate parent relation
432 */
433 private boolean duplicateParentRelations(Collection<Way> ways) {
434 Set<Relation> relations = new HashSet<>();
435 for (Way w : ways) {
436 List<Relation> rs = getParentRelations(w);
437 for (Relation r : rs) {
438 if (relations.contains(r)) {
439 return true;
440 }
441 }
442 relations.addAll(rs);
443 }
444 return false;
445 }
446
447 /**
448 * Replies the set of referring relations
449 *
450 * @return the set of referring relations
451 */
452 private List<Relation> getParentRelations(Way way) {
453 List<Relation> rels = new ArrayList<>();
454 for (Relation r : relations.get(way)) {
455 if (newRelations.containsKey(r)) {
456 rels.add(newRelations.get(r));
457 } else {
458 rels.add(r);
459 }
460 }
461 return rels;
462 }
463
464 public static Relation getNew(Relation r, Map<Relation, Relation> newRelations) {
465 if (newRelations.containsValue(r)) {
466 return r;
467 } else {
468 Relation c = new Relation(r);
469 newRelations.put(r, c);
470 return c;
471 }
472 }
473/*
474 private Way getOld(Way r) {
475 return getOld(r, oldWays);
476 }*/
477
478 public static Way getOld(Way w, Map<Way, Way> oldWays) {
479 if (oldWays.containsKey(w)) {
480 return oldWays.get(w);
481 } else {
482 return w;
483 }
484 }
485
486 /**
487 * Replies the set of referring relations
488 *
489 * @return the set of referring relations
490 */
491 private Set<Relation> getParentRelations(Collection<Way> ways) {
492 Set<Relation> ret = new HashSet<>();
493 for (Way w : ways) {
494 ret.addAll(getParentRelations(w));
495 }
496 return ret;
497 }
498
499 /** Enable this action only if something is selected */
500 @Override
501 protected void updateEnabledState() {
502 if (getLayerManager().getEditDataSet() == null) {
503 setEnabled(false);
504 } else {
505 updateEnabledState(getLayerManager().getEditDataSet().getSelected());
506 }
507 }
508
509 /** Enable this action only if something is selected */
510 @Override
511 protected void updateEnabledState(
512 Collection<? extends OsmPrimitive> selection) {
513 if (selection == null) {
514 setEnabled(false);
515 return;
516 }
517 for (OsmPrimitive primitive : selection) {
518 if (!(primitive instanceof Way) || primitive.isDeleted()) {
519 setEnabled(false);
520 return;
521 }
522 }
523 setEnabled(selection.size() >= 2);
524 }
525}
Note: See TracBrowser for help on using the repository browser.