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

Last change on this file since 30782 was 30782, checked in by donvip, 11 years ago

[josm-merge_overlap] fix sonar issues

File size: 27.7 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
189 && way.getNodesCount() == opositWay.getNodesCount()) {
190 boolean equals1 = true;
191 for (int i = 0; i < way.getNodesCount(); i++) {
192 if (way.getNode(i) != opositWay.getNode(i)) {
193 equals1 = false;
194 break;
195 }
196 }
197 boolean equals2 = true;
198 for (int i = 0; i < way.getNodesCount(); i++) {
199 if (way.getNode(i) != opositWay.getNode(way
200 .getNodesCount()
201 - i - 1)) {
202 equals2 = false;
203 break;
204 }
205 }
206 if (equals1 || equals2) {
207 combine.add(opositWay);
208 }
209 }
210 }
211 ways.removeAll(combine);
212 if (combine.size() > 1) {
213 sel.removeAll(combine);
214 // combine
215 Pair<Way, List<Command>> combineResult;
216 try {
217 combineResult = combineWaysWorker(combine);
218 } catch (UserCancelException ex) {
219 return;
220 }
221 sel.add(combineResult.a);
222 cmds.addAll(combineResult.b);
223 }
224 }
225
226 for (Relation old : newRelations.keySet()) {
227 cmds.add(new ChangeCommand(old, newRelations.get(old)));
228 }
229
230 List<Way> del = new LinkedList<>();
231 for (Way w : deletes) {
232 if (!w.isDeleted()) {
233 del.add(w);
234 }
235 }
236 if (!del.isEmpty()) {
237 cmds.add(new DeleteCommand(del));
238 }
239
240 // Commit
241 Main.main.undoRedo.add(new SequenceCommand(tr("Merge Overlap (combine)"), cmds));
242 getCurrentDataSet().setSelected(sel);
243 Main.map.repaint();
244
245 relations.clear();
246 newRelations.clear();
247 oldWays.clear();
248 }
249
250 private static class NodePos {
251 Node node;
252 int pos;
253 int opositPos;
254
255 NodePos(Node n, int p, int op) {
256 node = n;
257 pos = p;
258 opositPos = op;
259 }
260
261 @Override
262 public String toString() {
263 return "NodePos: " + pos + ", " + opositPos + ", " + node;
264 }
265 }
266
267 private boolean addNodes(NodePos start, NodePos end, Way way,
268 Set<Node> nodes, boolean hasFirst) {
269 if (way.isClosed() || (start.node != way.getNode(0) && start.node != way.getNode(way.getNodesCount() - 1))) {
270 hasFirst = hasFirst || start.node == way.getNode(0);
271 nodes.add(start.node);
272 }
273 if (way.isClosed() || (end.node != way.getNode(0) && end.node != way.getNode(way.getNodesCount() - 1))) {
274 if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
275 nodes.remove(way.getNode(0));
276 } else {
277 nodes.add(end.node);
278 }
279 }
280 return hasFirst;
281 }
282
283 private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2,
284 int incr) {
285 if (way2.isClosed() && incr == 1
286 && np1.opositPos == way2.getNodesCount() - 2) {
287 return np2.pos == np1.pos + 1 && np2.opositPos == 0;
288 } else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
289 return np2.pos == np1.pos && np2.opositPos == 0
290 || np2.pos == np1.pos + 1 && np2.opositPos == 1;
291 } else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
292 return np2.pos == np1.pos && np2.opositPos == 0
293 || np2.pos == np1.pos + 1
294 && np2.opositPos == way2.getNodesCount() - 2;
295 } else {
296 return np2.pos == np1.pos + 1
297 && np2.opositPos == np1.opositPos + incr;
298 }
299 }
300
301 /**
302 * Splits a way
303 *
304 * @param layer
305 * @param way
306 * @param wayChunks
307 * @return
308 */
309 private SplitWayResult splitWay(OsmDataLayer layer, Way way,
310 List<List<Node>> wayChunks) {
311 // build a list of commands, and also a new selection list
312 Collection<Command> commandList = new ArrayList<>(wayChunks
313 .size());
314
315 Iterator<List<Node>> chunkIt = wayChunks.iterator();
316 Collection<String> nowarnroles = Main.pref.getCollection(
317 "way.split.roles.nowarn", Arrays.asList(new String[] { "outer",
318 "inner", "forward", "backward" }));
319
320 // First, change the original way
321 Way changedWay = new Way(way);
322 oldWays.put(changedWay, way);
323 changedWay.setNodes(chunkIt.next());
324 commandList.add(new ChangeCommand(way, changedWay));
325
326 List<Way> newWays = new ArrayList<>();
327 // Second, create new ways
328 while (chunkIt.hasNext()) {
329 Way wayToAdd = new Way();
330 wayToAdd.setKeys(way.getKeys());
331 newWays.add(wayToAdd);
332 wayToAdd.setNodes(chunkIt.next());
333 commandList.add(new AddCommand(layer, wayToAdd));
334 }
335 boolean warnmerole = false;
336 boolean warnme = false;
337 // now copy all relations to new way also
338
339 for (Relation r : getParentRelations(way)) {
340 if (!r.isUsable()) {
341 continue;
342 }
343 Relation c = null;
344 String type = r.get("type");
345 if (type == null) {
346 type = "";
347 }
348
349 int ic = 0, ir = 0;
350 List<RelationMember> relationMembers = r.getMembers();
351 for (RelationMember rm : relationMembers) {
352 if (rm.isWay() && rm.getMember() == way) {
353 boolean insert = true;
354 if ("restriction".equals(type)) {
355 /*
356 * this code assumes the restriction is correct. No real
357 * error checking done
358 */
359 String role = rm.getRole();
360 if ("from".equals(role) || "to".equals(role)) {
361 OsmPrimitive via = null;
362 for (RelationMember rmv : r.getMembers()) {
363 if ("via".equals(rmv.getRole())) {
364 via = rmv.getMember();
365 }
366 }
367 List<Node> nodes = new ArrayList<>();
368 if (via != null) {
369 if (via instanceof Node) {
370 nodes.add((Node) via);
371 } else if (via instanceof Way) {
372 nodes.add(((Way) via).lastNode());
373 nodes.add(((Way) via).firstNode());
374 }
375 }
376 Way res = null;
377 for (Node n : nodes) {
378 if (changedWay.isFirstLastNode(n)) {
379 res = way;
380 }
381 }
382 if (res == null) {
383 for (Way wayToAdd : newWays) {
384 for (Node n : nodes) {
385 if (wayToAdd.isFirstLastNode(n)) {
386 res = wayToAdd;
387 }
388 }
389 }
390 if (res != null) {
391 if (c == null) {
392 c = getNew(r);
393 }
394 c.addMember(new RelationMember(role, res));
395 c.removeMembersFor(way);
396 insert = false;
397 }
398 } else {
399 insert = false;
400 }
401 } else if (!"via".equals(role)) {
402 warnme = true;
403 }
404 } else if (!("route".equals(type))
405 && !("multipolygon".equals(type))) {
406 warnme = true;
407 }
408 if (c == null) {
409 c = getNew(r);
410 }
411
412 if (insert) {
413 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
414 warnmerole = true;
415 }
416
417 Boolean backwards = null;
418 int k = 1;
419 while (ir - k >= 0 || ir + k < relationMembers.size()) {
420 if ((ir - k >= 0)
421 && relationMembers.get(ir - k).isWay()) {
422 Way w = relationMembers.get(ir - k).getWay();
423 if ((w.lastNode() == way.firstNode())
424 || w.firstNode() == way.firstNode()) {
425 backwards = false;
426 } else if ((w.firstNode() == way.lastNode())
427 || w.lastNode() == way.lastNode()) {
428 backwards = true;
429 }
430 break;
431 }
432 if ((ir + k < relationMembers.size())
433 && relationMembers.get(ir + k).isWay()) {
434 Way w = relationMembers.get(ir + k).getWay();
435 if ((w.lastNode() == way.firstNode())
436 || w.firstNode() == way.firstNode()) {
437 backwards = true;
438 } else if ((w.firstNode() == way.lastNode())
439 || w.lastNode() == way.lastNode()) {
440 backwards = false;
441 }
442 break;
443 }
444 k++;
445 }
446
447 int j = ic;
448 for (Way wayToAdd : newWays) {
449 RelationMember em = new RelationMember(
450 rm.getRole(), wayToAdd);
451 j++;
452 if ((backwards != null) && backwards) {
453 c.addMember(ic, em);
454 } else {
455 c.addMember(j, em);
456 }
457 }
458 ic = j;
459 }
460 }
461 ic++;
462 ir++;
463 }
464
465 if (c != null) {
466 // commandList.add(new ChangeCommand(layer, r, c));
467 newRelations.put(r, c);
468 }
469 }
470 if (warnmerole) {
471 JOptionPane.showMessageDialog(
472 Main.parent,
473 tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
474 tr("Warning"), JOptionPane.WARNING_MESSAGE);
475 } else if (warnme) {
476 JOptionPane.showMessageDialog(
477 Main.parent,
478 tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
479 tr("Warning"), JOptionPane.WARNING_MESSAGE);
480 }
481
482 return new SplitWayResult(
483 new SequenceCommand(tr("Split way"), commandList), null,
484 changedWay, newWays);
485 }
486
487 /**
488 * @param ways
489 * @return null if ways cannot be combined. Otherwise returns the combined
490 * ways and the commands to combine
491 * @throws UserCancelException
492 */
493 private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways)
494 throws UserCancelException {
495
496 // prepare and clean the list of ways to combine
497 if (ways == null || ways.isEmpty())
498 return null;
499 ways.remove(null); // just in case - remove all null ways from the
500 // collection
501
502 // remove duplicates, preserving order
503 ways = new LinkedHashSet<>(ways);
504
505 // try to build a new way which includes all the combined ways
506 NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
507 List<Node> path = graph.buildSpanningPath();
508
509 // check whether any ways have been reversed in the process
510 // and build the collection of tags used by the ways to combine
511 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
512
513 List<Way> reversedWays = new LinkedList<>();
514 List<Way> unreversedWays = new LinkedList<>();
515 for (Way w : ways) {
516 if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w
517 .getNode(1))) {
518 unreversedWays.add(w);
519 } else {
520 reversedWays.add(w);
521 }
522 }
523 // reverse path if all ways have been reversed
524 if (unreversedWays.isEmpty()) {
525 Collections.reverse(path);
526 unreversedWays = reversedWays;
527 reversedWays = null;
528 }
529 if ((reversedWays != null) && !reversedWays.isEmpty()) {
530 // filter out ways that have no direction-dependent tags
531 unreversedWays = ReverseWayTagCorrector
532 .irreversibleWays(unreversedWays);
533 reversedWays = ReverseWayTagCorrector
534 .irreversibleWays(reversedWays);
535 // reverse path if there are more reversed than unreversed ways with
536 // direction-dependent tags
537 if (reversedWays.size() > unreversedWays.size()) {
538 Collections.reverse(path);
539 List<Way> tempWays = unreversedWays;
540 unreversedWays = reversedWays;
541 reversedWays = tempWays;
542 }
543 // if there are still reversed ways with direction-dependent tags,
544 // reverse their tags
545 if (!reversedWays.isEmpty()) {
546 List<Way> unreversedTagWays = new ArrayList<>(ways);
547 unreversedTagWays.removeAll(reversedWays);
548 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
549 List<Way> reversedTagWays = new ArrayList<>();
550 Collection<Command> changePropertyCommands = null;
551 for (Way w : reversedWays) {
552 Way wnew = new Way(w);
553 reversedTagWays.add(wnew);
554 changePropertyCommands = reverseWayTagCorrector.execute(w,
555 wnew);
556 }
557 if ((changePropertyCommands != null)
558 && !changePropertyCommands.isEmpty()) {
559 for (Command c : changePropertyCommands) {
560 c.executeCommand();
561 }
562 }
563 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
564 wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
565 }
566 }
567
568 // create the new way and apply the new node list
569 Way targetWay = getTargetWay(ways);
570 Way modifiedTargetWay = new Way(targetWay);
571 modifiedTargetWay.setNodes(path);
572
573 TagCollection completeWayTags = new TagCollection(wayTags);
574 combineTigerTags(completeWayTags);
575 normalizeTagCollectionBeforeEditing(completeWayTags, ways);
576 TagCollection tagsToEdit = new TagCollection(completeWayTags);
577 completeTagCollectionForEditing(tagsToEdit);
578
579 MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog.getInstance();
580 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
581 dialog.setTargetPrimitive(targetWay);
582 Set<Relation> parentRelations = getParentRelations(ways);
583 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, ways, oldWays);
584 dialog.prepareDefaultDecisions();
585
586 // resolve tag conflicts if necessary
587 if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
588 dialog.setVisible(true);
589 if (dialog.isCanceled())
590 throw new UserCancelException();
591 }
592
593 List<Command> cmds = new LinkedList<>();
594 deletes.addAll(ways);
595 deletes.remove(targetWay);
596
597 cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
598 cmds.addAll(dialog.buildWayResolutionCommands());
599 dialog.buildRelationCorrespondance(newRelations, oldWays);
600
601 return new Pair<>(targetWay, cmds);
602 }
603
604 private static Way getTargetWay(Collection<Way> combinedWays) {
605 // init with an arbitrary way
606 Way targetWay = combinedWays.iterator().next();
607
608 // look for the first way already existing on the server
609 for (Way w : combinedWays) {
610 targetWay = w;
611 if (!w.isNew()) {
612 break;
613 }
614 }
615 return targetWay;
616 }
617
618 /**
619 * @return has tag to be merged (=> ask)
620 */
621 private static boolean askForMergeTag(Collection<Way> ways) {
622 for (Way way : ways) {
623 for (Way oposite : ways) {
624 for (String key : way.getKeys().keySet()) {
625 if (!"source".equals(key) && oposite.hasKey(key)
626 && !way.get(key).equals(oposite.get(key))) {
627 return true;
628 }
629 }
630 }
631 }
632 return false;
633 }
634
635 /**
636 * @return has duplicate parent relation
637 */
638 private boolean duplicateParentRelations(Collection<Way> ways) {
639 Set<Relation> relations = new HashSet<>();
640 for (Way w : ways) {
641 List<Relation> rs = getParentRelations(w);
642 for (Relation r : rs) {
643 if (relations.contains(r)) {
644 return true;
645 }
646 }
647 relations.addAll(rs);
648 }
649 return false;
650 }
651
652 /**
653 * Replies the set of referring relations
654 *
655 * @return the set of referring relations
656 */
657 private List<Relation> getParentRelations(Way way) {
658 List<Relation> rels = new ArrayList<>();
659 for (Relation r : relations.get(way)) {
660 if (newRelations.containsKey(r)) {
661 rels.add(newRelations.get(r));
662 } else {
663 rels.add(r);
664 }
665 }
666 return rels;
667 }
668
669 private Relation getNew(Relation r) {
670 return getNew(r, newRelations);
671 }
672
673 public static Relation getNew(Relation r, Map<Relation, Relation> newRelations) {
674 if (newRelations.containsValue(r)) {
675 return r;
676 } else {
677 Relation c = new Relation(r);
678 newRelations.put(r, c);
679 return c;
680 }
681 }
682/*
683 private Way getOld(Way r) {
684 return getOld(r, oldWays);
685 }*/
686
687 public static Way getOld(Way w, Map<Way, Way> oldWays) {
688 if (oldWays.containsKey(w)) {
689 return oldWays.get(w);
690 } else {
691 return w;
692 }
693 }
694
695 /**
696 * Replies the set of referring relations
697 *
698 * @return the set of referring relations
699 */
700 private Set<Relation> getParentRelations(Collection<Way> ways) {
701 Set<Relation> ret = new HashSet<>();
702 for (Way w : ways) {
703 ret.addAll(getParentRelations(w));
704 }
705 return ret;
706 }
707
708 /** Enable this action only if something is selected */
709 @Override
710 protected void updateEnabledState() {
711 if (getCurrentDataSet() == null) {
712 setEnabled(false);
713 } else {
714 updateEnabledState(getCurrentDataSet().getSelected());
715 }
716 }
717
718 /** Enable this action only if something is selected */
719 @Override
720 protected void updateEnabledState(
721 Collection<? extends OsmPrimitive> selection) {
722 if (selection == null) {
723 setEnabled(false);
724 return;
725 }
726 for (OsmPrimitive primitive : selection) {
727 if (!(primitive instanceof Way) || primitive.isDeleted()) {
728 setEnabled(false);
729 return;
730 }
731 }
732 setEnabled(selection.size() >= 2);
733 }
734}
Note: See TracBrowser for help on using the repository browser.