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

Last change on this file since 30783 was 30783, checked in by donvip, 10 years ago

[josm-merge_overlap] code cleanup

File size: 26.9 KB
Line 
1package mergeoverlap;
2
3import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
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.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.Iterator;
17import java.util.LinkedHashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import javax.swing.JOptionPane;
24
25import mergeoverlap.hack.MyCombinePrimitiveResolverDialog;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.actions.JosmAction;
29import org.openstreetmap.josm.actions.SplitWayAction;
30import org.openstreetmap.josm.actions.CombineWayAction.NodeGraph;
31import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
32import org.openstreetmap.josm.command.AddCommand;
33import org.openstreetmap.josm.command.ChangeCommand;
34import org.openstreetmap.josm.command.Command;
35import org.openstreetmap.josm.command.DeleteCommand;
36import org.openstreetmap.josm.command.SequenceCommand;
37import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
38import org.openstreetmap.josm.corrector.UserCancelException;
39import org.openstreetmap.josm.data.osm.Node;
40import org.openstreetmap.josm.data.osm.OsmPrimitive;
41import org.openstreetmap.josm.data.osm.Relation;
42import org.openstreetmap.josm.data.osm.RelationMember;
43import org.openstreetmap.josm.data.osm.TagCollection;
44import org.openstreetmap.josm.data.osm.Way;
45import org.openstreetmap.josm.gui.layer.OsmDataLayer;
46import org.openstreetmap.josm.tools.Pair;
47import org.openstreetmap.josm.tools.Shortcut;
48
49/**
50 * Merge overlapping part of ways.
51 */
52public class MergeOverlapAction extends JosmAction {
53
54 /**
55 * Constructs a new {@code MergeOverlapAction}.
56 */
57 public MergeOverlapAction() {
58 super(tr("Merge overlap"), "merge_overlap",
59 tr("Merge overlap of ways."),
60 Shortcut.registerShortcut("tools:mergeoverlap",tr("Tool: {0}", tr("Merge overlap")), KeyEvent.VK_O,
61 Shortcut.ALT_CTRL), true);
62 }
63
64 Map<Way, List<Relation>> relations = new HashMap<>();
65 Map<Way, Way> oldWays = new HashMap<>();
66 Map<Relation, Relation> newRelations = new HashMap<>();
67 Set<Way> deletes = new HashSet<>();
68
69 /**
70 * The action button has been clicked
71 *
72 * @param e
73 * Action Event
74 */
75 @Override
76 public void actionPerformed(ActionEvent e) {
77
78 // List of selected ways
79 List<Way> ways = new ArrayList<>();
80 relations.clear();
81 newRelations.clear();
82
83 // For every selected way
84 for (OsmPrimitive osm : Main.main.getCurrentDataSet().getSelected()) {
85 if (osm instanceof Way && !osm.isDeleted()) {
86 Way way = (Way) osm;
87 ways.add(way);
88 List<Relation> rels = new ArrayList<>();
89 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
90 rels.add(r);
91 }
92 relations.put(way, rels);
93 }
94 }
95
96 List<Way> sel = new ArrayList<>(ways);
97 Collection<Command> cmds = new LinkedList<>();
98
99 // *****
100 // split
101 // *****
102 for (Way way : ways) {
103 Set<Node> nodes = new HashSet<>();
104 for (Way opositWay : ways) {
105 if (way != opositWay) {
106 List<NodePos> nodesPos = new LinkedList<>();
107
108 int pos = 0;
109 for (Node node : way.getNodes()) {
110 int opositPos = 0;
111 for (Node opositNode : opositWay.getNodes()) {
112 if (node == opositNode) {
113 if (opositWay.isClosed()) {
114 opositPos %= opositWay.getNodesCount() - 1;
115 }
116 nodesPos.add(new NodePos(node, pos, opositPos));
117 break;
118 }
119 opositPos++;
120 }
121 pos++;
122 }
123
124 NodePos start = null;
125 NodePos end = null;
126 int increment = 0;
127
128 boolean hasFirst = false;
129 for (NodePos node : nodesPos) {
130 if (start == null) {
131 start = node;
132 } else {
133 if (end == null) {
134 if (follows(way, opositWay, start, node, 1)) {
135 end = node;
136 increment = +1;
137 } else if (follows(way, opositWay, start, node, -1)) {
138 end = node;
139 increment = -1;
140 } else {
141 start = node;
142 end = null;
143 }
144 } else {
145 if (follows(way, opositWay, end, node, increment)) {
146 end = node;
147 } else {
148 hasFirst = addNodes(start, end, way, nodes, hasFirst);
149 start = node;
150 end = null;
151 }
152 }
153 }
154 }
155
156 if (start != null && end != null) {
157 hasFirst = addNodes(start, end, way, nodes, hasFirst);
158 start = null;
159 end = null;
160 }
161 }
162 }
163 if (!nodes.isEmpty() && !way.isClosed() || nodes.size() >= 2) {
164 List<List<Node>> wayChunks = SplitWayAction.buildSplitChunks(way, new ArrayList<>(nodes));
165 SplitWayResult result = splitWay(getEditLayer(), way, wayChunks);
166
167 cmds.add(result.getCommand());
168 sel.remove(way);
169 sel.add(result.getOriginalWay());
170 sel.addAll(result.getNewWays());
171 List<Relation> rels = relations.remove(way);
172 relations.put(result.getOriginalWay(), rels);
173 for (Way w : result.getNewWays()) {
174 relations.put(w, rels);
175 }
176 }
177 }
178
179 // *****
180 // merge
181 // *****
182 ways = new ArrayList<>(sel);
183 while (!ways.isEmpty()) {
184 Way way = ways.get(0);
185 List<Way> combine = new ArrayList<>();
186 combine.add(way);
187 for (Way opositWay : ways) {
188 if (way != opositWay && way.getNodesCount() == opositWay.getNodesCount()) {
189 boolean equals1 = true;
190 for (int i = 0; i < way.getNodesCount(); i++) {
191 if (way.getNode(i) != opositWay.getNode(i)) {
192 equals1 = false;
193 break;
194 }
195 }
196 boolean equals2 = true;
197 for (int i = 0; i < way.getNodesCount(); i++) {
198 if (way.getNode(i) != opositWay.getNode(way.getNodesCount() - i - 1)) {
199 equals2 = false;
200 break;
201 }
202 }
203 if (equals1 || equals2) {
204 combine.add(opositWay);
205 }
206 }
207 }
208 ways.removeAll(combine);
209 if (combine.size() > 1) {
210 sel.removeAll(combine);
211 // combine
212 Pair<Way, List<Command>> combineResult;
213 try {
214 combineResult = combineWaysWorker(combine);
215 } catch (UserCancelException ex) {
216 return;
217 }
218 sel.add(combineResult.a);
219 cmds.addAll(combineResult.b);
220 }
221 }
222
223 for (Relation old : newRelations.keySet()) {
224 cmds.add(new ChangeCommand(old, newRelations.get(old)));
225 }
226
227 List<Way> del = new LinkedList<>();
228 for (Way w : deletes) {
229 if (!w.isDeleted()) {
230 del.add(w);
231 }
232 }
233 if (!del.isEmpty()) {
234 cmds.add(new DeleteCommand(del));
235 }
236
237 // Commit
238 Main.main.undoRedo.add(new SequenceCommand(tr("Merge Overlap (combine)"), cmds));
239 getCurrentDataSet().setSelected(sel);
240 Main.map.repaint();
241
242 relations.clear();
243 newRelations.clear();
244 oldWays.clear();
245 }
246
247 private static class NodePos {
248 Node node;
249 int pos;
250 int opositPos;
251
252 NodePos(Node n, int p, int op) {
253 node = n;
254 pos = p;
255 opositPos = op;
256 }
257
258 @Override
259 public String toString() {
260 return "NodePos: " + pos + ", " + opositPos + ", " + node;
261 }
262 }
263
264 private boolean addNodes(NodePos start, NodePos end, Way way,
265 Set<Node> nodes, boolean hasFirst) {
266 if (way.isClosed() || (start.node != way.getNode(0) && start.node != way.getNode(way.getNodesCount() - 1))) {
267 hasFirst = hasFirst || start.node == way.getNode(0);
268 nodes.add(start.node);
269 }
270 if (way.isClosed() || (end.node != way.getNode(0) && end.node != way.getNode(way.getNodesCount() - 1))) {
271 if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
272 nodes.remove(way.getNode(0));
273 } else {
274 nodes.add(end.node);
275 }
276 }
277 return hasFirst;
278 }
279
280 private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2,
281 int incr) {
282 if (way2.isClosed() && incr == 1 && np1.opositPos == way2.getNodesCount() - 2) {
283 return np2.pos == np1.pos + 1 && np2.opositPos == 0;
284 } else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
285 return np2.pos == np1.pos && np2.opositPos == 0
286 || np2.pos == np1.pos + 1 && np2.opositPos == 1;
287 } else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
288 return np2.pos == np1.pos && np2.opositPos == 0 || np2.pos == np1.pos + 1
289 && np2.opositPos == way2.getNodesCount() - 2;
290 } else {
291 return np2.pos == np1.pos + 1 && np2.opositPos == np1.opositPos + incr;
292 }
293 }
294
295 /**
296 * Splits a way
297 *
298 * @param layer
299 * @param way
300 * @param wayChunks
301 * @return
302 */
303 private SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks) {
304 // build a list of commands, and also a new selection list
305 Collection<Command> commandList = new ArrayList<>(wayChunks.size());
306
307 Iterator<List<Node>> chunkIt = wayChunks.iterator();
308 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn",
309 Arrays.asList(new String[] { "outer", "inner", "forward", "backward" }));
310
311 // First, change the original way
312 Way changedWay = new Way(way);
313 oldWays.put(changedWay, way);
314 changedWay.setNodes(chunkIt.next());
315 commandList.add(new ChangeCommand(way, changedWay));
316
317 List<Way> newWays = new ArrayList<>();
318 // Second, create new ways
319 while (chunkIt.hasNext()) {
320 Way wayToAdd = new Way();
321 wayToAdd.setKeys(way.getKeys());
322 newWays.add(wayToAdd);
323 wayToAdd.setNodes(chunkIt.next());
324 commandList.add(new AddCommand(layer, wayToAdd));
325 }
326 boolean warnmerole = false;
327 boolean warnme = false;
328 // now copy all relations to new way also
329
330 for (Relation r : getParentRelations(way)) {
331 if (!r.isUsable()) {
332 continue;
333 }
334 Relation c = null;
335 String type = r.get("type");
336 if (type == null) {
337 type = "";
338 }
339
340 int ic = 0, ir = 0;
341 List<RelationMember> relationMembers = r.getMembers();
342 for (RelationMember rm : relationMembers) {
343 if (rm.isWay() && rm.getMember() == way) {
344 boolean insert = true;
345 if ("restriction".equals(type)) {
346 /*
347 * this code assumes the restriction is correct. No real
348 * error checking done
349 */
350 String role = rm.getRole();
351 if ("from".equals(role) || "to".equals(role)) {
352 OsmPrimitive via = null;
353 for (RelationMember rmv : r.getMembers()) {
354 if ("via".equals(rmv.getRole())) {
355 via = rmv.getMember();
356 }
357 }
358 List<Node> nodes = new ArrayList<>();
359 if (via != null) {
360 if (via instanceof Node) {
361 nodes.add((Node) via);
362 } else if (via instanceof Way) {
363 nodes.add(((Way) via).lastNode());
364 nodes.add(((Way) via).firstNode());
365 }
366 }
367 Way res = null;
368 for (Node n : nodes) {
369 if (changedWay.isFirstLastNode(n)) {
370 res = way;
371 }
372 }
373 if (res == null) {
374 for (Way wayToAdd : newWays) {
375 for (Node n : nodes) {
376 if (wayToAdd.isFirstLastNode(n)) {
377 res = wayToAdd;
378 }
379 }
380 }
381 if (res != null) {
382 if (c == null) {
383 c = getNew(r);
384 }
385 c.addMember(new RelationMember(role, res));
386 c.removeMembersFor(way);
387 insert = false;
388 }
389 } else {
390 insert = false;
391 }
392 } else if (!"via".equals(role)) {
393 warnme = true;
394 }
395 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) {
396 warnme = true;
397 }
398 if (c == null) {
399 c = getNew(r);
400 }
401
402 if (insert) {
403 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
404 warnmerole = true;
405 }
406
407 Boolean backwards = null;
408 int k = 1;
409 while (ir - k >= 0 || ir + k < relationMembers.size()) {
410 if ((ir - k >= 0) && relationMembers.get(ir - k).isWay()) {
411 Way w = relationMembers.get(ir - k).getWay();
412 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
413 backwards = false;
414 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
415 backwards = true;
416 }
417 break;
418 }
419 if ((ir + k < relationMembers.size()) && relationMembers.get(ir + k).isWay()) {
420 Way w = relationMembers.get(ir + k).getWay();
421 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
422 backwards = true;
423 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
424 backwards = false;
425 }
426 break;
427 }
428 k++;
429 }
430
431 int j = ic;
432 for (Way wayToAdd : newWays) {
433 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
434 j++;
435 if ((backwards != null) && backwards) {
436 c.addMember(ic, em);
437 } else {
438 c.addMember(j, em);
439 }
440 }
441 ic = j;
442 }
443 }
444 ic++;
445 ir++;
446 }
447
448 if (c != null) {
449 // commandList.add(new ChangeCommand(layer, r, c));
450 newRelations.put(r, c);
451 }
452 }
453 if (warnmerole) {
454 JOptionPane.showMessageDialog(Main.parent,
455 tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
456 tr("Warning"), JOptionPane.WARNING_MESSAGE);
457 } else if (warnme) {
458 JOptionPane.showMessageDialog(Main.parent,
459 tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
460 tr("Warning"), JOptionPane.WARNING_MESSAGE);
461 }
462
463 return new SplitWayResult(new SequenceCommand(tr("Split way"), commandList), null, changedWay, newWays);
464 }
465
466 /**
467 * @param ways
468 * @return null if ways cannot be combined. Otherwise returns the combined
469 * ways and the commands to combine
470 * @throws UserCancelException
471 */
472 private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways) throws UserCancelException {
473
474 // prepare and clean the list of ways to combine
475 if (ways == null || ways.isEmpty())
476 return null;
477 ways.remove(null); // just in case - remove all null ways from the collection
478
479 // remove duplicates, preserving order
480 ways = new LinkedHashSet<>(ways);
481
482 // try to build a new way which includes all the combined ways
483 NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
484 List<Node> path = graph.buildSpanningPath();
485
486 // check whether any ways have been reversed in the process
487 // and build the collection of tags used by the ways to combine
488 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
489
490 List<Way> reversedWays = new LinkedList<>();
491 List<Way> unreversedWays = new LinkedList<>();
492 for (Way w : ways) {
493 if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
494 unreversedWays.add(w);
495 } else {
496 reversedWays.add(w);
497 }
498 }
499 // reverse path if all ways have been reversed
500 if (unreversedWays.isEmpty()) {
501 Collections.reverse(path);
502 unreversedWays = reversedWays;
503 reversedWays = null;
504 }
505 if ((reversedWays != null) && !reversedWays.isEmpty()) {
506 // filter out ways that have no direction-dependent tags
507 unreversedWays = ReverseWayTagCorrector.irreversibleWays(unreversedWays);
508 reversedWays = ReverseWayTagCorrector.irreversibleWays(reversedWays);
509 // reverse path if there are more reversed than unreversed ways with
510 // direction-dependent tags
511 if (reversedWays.size() > unreversedWays.size()) {
512 Collections.reverse(path);
513 List<Way> tempWays = unreversedWays;
514 unreversedWays = reversedWays;
515 reversedWays = tempWays;
516 }
517 // if there are still reversed ways with direction-dependent tags,
518 // reverse their tags
519 if (!reversedWays.isEmpty()) {
520 List<Way> unreversedTagWays = new ArrayList<>(ways);
521 unreversedTagWays.removeAll(reversedWays);
522 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
523 List<Way> reversedTagWays = new ArrayList<>();
524 Collection<Command> changePropertyCommands = null;
525 for (Way w : reversedWays) {
526 Way wnew = new Way(w);
527 reversedTagWays.add(wnew);
528 changePropertyCommands = reverseWayTagCorrector.execute(w, wnew);
529 }
530 if ((changePropertyCommands != null) && !changePropertyCommands.isEmpty()) {
531 for (Command c : changePropertyCommands) {
532 c.executeCommand();
533 }
534 }
535 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
536 wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
537 }
538 }
539
540 // create the new way and apply the new node list
541 Way targetWay = getTargetWay(ways);
542 Way modifiedTargetWay = new Way(targetWay);
543 modifiedTargetWay.setNodes(path);
544
545 TagCollection completeWayTags = new TagCollection(wayTags);
546 combineTigerTags(completeWayTags);
547 normalizeTagCollectionBeforeEditing(completeWayTags, ways);
548 TagCollection tagsToEdit = new TagCollection(completeWayTags);
549 completeTagCollectionForEditing(tagsToEdit);
550
551 MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog.getInstance();
552 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
553 dialog.setTargetPrimitive(targetWay);
554 Set<Relation> parentRelations = getParentRelations(ways);
555 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, ways, oldWays);
556 dialog.prepareDefaultDecisions();
557
558 // resolve tag conflicts if necessary
559 if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
560 dialog.setVisible(true);
561 if (dialog.isCanceled())
562 throw new UserCancelException();
563 }
564
565 List<Command> cmds = new LinkedList<>();
566 deletes.addAll(ways);
567 deletes.remove(targetWay);
568
569 cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
570 cmds.addAll(dialog.buildWayResolutionCommands());
571 dialog.buildRelationCorrespondance(newRelations, oldWays);
572
573 return new Pair<>(targetWay, cmds);
574 }
575
576 private static Way getTargetWay(Collection<Way> combinedWays) {
577 // init with an arbitrary way
578 Way targetWay = combinedWays.iterator().next();
579
580 // look for the first way already existing on the server
581 for (Way w : combinedWays) {
582 targetWay = w;
583 if (!w.isNew()) {
584 break;
585 }
586 }
587 return targetWay;
588 }
589
590 /**
591 * @return has tag to be merged (=> ask)
592 */
593 private static boolean askForMergeTag(Collection<Way> ways) {
594 for (Way way : ways) {
595 for (Way oposite : ways) {
596 for (String key : way.getKeys().keySet()) {
597 if (!"source".equals(key) && oposite.hasKey(key)
598 && !way.get(key).equals(oposite.get(key))) {
599 return true;
600 }
601 }
602 }
603 }
604 return false;
605 }
606
607 /**
608 * @return has duplicate parent relation
609 */
610 private boolean duplicateParentRelations(Collection<Way> ways) {
611 Set<Relation> relations = new HashSet<>();
612 for (Way w : ways) {
613 List<Relation> rs = getParentRelations(w);
614 for (Relation r : rs) {
615 if (relations.contains(r)) {
616 return true;
617 }
618 }
619 relations.addAll(rs);
620 }
621 return false;
622 }
623
624 /**
625 * Replies the set of referring relations
626 *
627 * @return the set of referring relations
628 */
629 private List<Relation> getParentRelations(Way way) {
630 List<Relation> rels = new ArrayList<>();
631 for (Relation r : relations.get(way)) {
632 if (newRelations.containsKey(r)) {
633 rels.add(newRelations.get(r));
634 } else {
635 rels.add(r);
636 }
637 }
638 return rels;
639 }
640
641 private Relation getNew(Relation r) {
642 return getNew(r, newRelations);
643 }
644
645 public static Relation getNew(Relation r, Map<Relation, Relation> newRelations) {
646 if (newRelations.containsValue(r)) {
647 return r;
648 } else {
649 Relation c = new Relation(r);
650 newRelations.put(r, c);
651 return c;
652 }
653 }
654/*
655 private Way getOld(Way r) {
656 return getOld(r, oldWays);
657 }*/
658
659 public static Way getOld(Way w, Map<Way, Way> oldWays) {
660 if (oldWays.containsKey(w)) {
661 return oldWays.get(w);
662 } else {
663 return w;
664 }
665 }
666
667 /**
668 * Replies the set of referring relations
669 *
670 * @return the set of referring relations
671 */
672 private Set<Relation> getParentRelations(Collection<Way> ways) {
673 Set<Relation> ret = new HashSet<>();
674 for (Way w : ways) {
675 ret.addAll(getParentRelations(w));
676 }
677 return ret;
678 }
679
680 /** Enable this action only if something is selected */
681 @Override
682 protected void updateEnabledState() {
683 if (getCurrentDataSet() == null) {
684 setEnabled(false);
685 } else {
686 updateEnabledState(getCurrentDataSet().getSelected());
687 }
688 }
689
690 /** Enable this action only if something is selected */
691 @Override
692 protected void updateEnabledState(
693 Collection<? extends OsmPrimitive> selection) {
694 if (selection == null) {
695 setEnabled(false);
696 return;
697 }
698 for (OsmPrimitive primitive : selection) {
699 if (!(primitive instanceof Way) || primitive.isDeleted()) {
700 setEnabled(false);
701 return;
702 }
703 }
704 setEnabled(selection.size() >= 2);
705 }
706}
Note: See TracBrowser for help on using the repository browser.