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

Last change on this file since 35583 was 35583, checked in by Klumbumbus, 4 years ago

see #19851 - Fix shortcut names

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("More tools: {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 if (!cmds.isEmpty()) {
233 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Merge Overlap (combine)"), cmds));
234 getLayerManager().getEditDataSet().setSelected(sel);
235 MainApplication.getMap().repaint();
236 }
237
238 relations.clear();
239 newRelations.clear();
240 oldWays.clear();
241 }
242
243 private static class NodePos {
244 Node node;
245 int pos;
246 int opositPos;
247
248 NodePos(Node n, int p, int op) {
249 node = n;
250 pos = p;
251 opositPos = op;
252 }
253
254 @Override
255 public String toString() {
256 return "NodePos: " + pos + ", " + opositPos + ", " + node;
257 }
258 }
259
260 private boolean addNodes(NodePos start, NodePos end, Way way,
261 Set<Node> nodes, boolean hasFirst) {
262 if (way.isClosed() || (start.node != way.getNode(0) && start.node != way.getNode(way.getNodesCount() - 1))) {
263 hasFirst = hasFirst || start.node == way.getNode(0);
264 nodes.add(start.node);
265 }
266 if (way.isClosed() || (end.node != way.getNode(0) && end.node != way.getNode(way.getNodesCount() - 1))) {
267 if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
268 nodes.remove(way.getNode(0));
269 } else {
270 nodes.add(end.node);
271 }
272 }
273 return hasFirst;
274 }
275
276 private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2,
277 int incr) {
278 if (way2.isClosed() && incr == 1 && np1.opositPos == way2.getNodesCount() - 2) {
279 return np2.pos == np1.pos + 1 && np2.opositPos == 0;
280 } else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
281 return np2.pos == np1.pos && np2.opositPos == 0
282 || np2.pos == np1.pos + 1 && np2.opositPos == 1;
283 } else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
284 return np2.pos == np1.pos && np2.opositPos == 0 || np2.pos == np1.pos + 1
285 && np2.opositPos == way2.getNodesCount() - 2;
286 } else {
287 return np2.pos == np1.pos + 1 && np2.opositPos == np1.opositPos + incr;
288 }
289 }
290
291 /**
292 * @param ways
293 * @return null if ways cannot be combined. Otherwise returns the combined
294 * ways and the commands to combine
295 * @throws UserCancelException
296 */
297 private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways) throws UserCancelException {
298
299 // prepare and clean the list of ways to combine
300 if (ways == null || ways.isEmpty())
301 return null;
302 ways.remove(null); // just in case - remove all null ways from the collection
303
304 // remove duplicates, preserving order
305 ways = new LinkedHashSet<>(ways);
306
307 // try to build a new way which includes all the combined ways
308 NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
309 List<Node> path = graph.buildSpanningPath();
310
311 // check whether any ways have been reversed in the process
312 // and build the collection of tags used by the ways to combine
313 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
314
315 List<Way> reversedWays = new LinkedList<>();
316 List<Way> unreversedWays = new LinkedList<>();
317 for (Way w : ways) {
318 if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
319 unreversedWays.add(w);
320 } else {
321 reversedWays.add(w);
322 }
323 }
324 // reverse path if all ways have been reversed
325 if (unreversedWays.isEmpty()) {
326 Collections.reverse(path);
327 unreversedWays = reversedWays;
328 reversedWays = null;
329 }
330 if ((reversedWays != null) && !reversedWays.isEmpty()) {
331 // filter out ways that have no direction-dependent tags
332 unreversedWays = ReverseWayTagCorrector.irreversibleWays(unreversedWays);
333 reversedWays = ReverseWayTagCorrector.irreversibleWays(reversedWays);
334 // reverse path if there are more reversed than unreversed ways with
335 // direction-dependent tags
336 if (reversedWays.size() > unreversedWays.size()) {
337 Collections.reverse(path);
338 List<Way> tempWays = unreversedWays;
339 unreversedWays = reversedWays;
340 reversedWays = tempWays;
341 }
342 // if there are still reversed ways with direction-dependent tags,
343 // reverse their tags
344 if (!reversedWays.isEmpty()) {
345 List<Way> unreversedTagWays = new ArrayList<>(ways);
346 unreversedTagWays.removeAll(reversedWays);
347 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
348 List<Way> reversedTagWays = new ArrayList<>();
349 Collection<Command> changePropertyCommands = null;
350 for (Way w : reversedWays) {
351 Way wnew = new Way(w);
352 reversedTagWays.add(wnew);
353 changePropertyCommands = reverseWayTagCorrector.execute(w, wnew);
354 }
355 if ((changePropertyCommands != null) && !changePropertyCommands.isEmpty()) {
356 for (Command c : changePropertyCommands) {
357 c.executeCommand();
358 }
359 }
360 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
361 wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
362 }
363 }
364
365 // create the new way and apply the new node list
366 Way targetWay = getTargetWay(ways);
367 Way modifiedTargetWay = new Way(targetWay);
368 modifiedTargetWay.setNodes(path);
369
370 TagCollection completeWayTags = new TagCollection(wayTags);
371 applyAutomaticTagConflictResolution(completeWayTags);
372 normalizeTagCollectionBeforeEditing(completeWayTags, ways);
373 TagCollection tagsToEdit = new TagCollection(completeWayTags);
374 completeTagCollectionForEditing(tagsToEdit);
375
376 MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog.getInstance();
377 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
378 dialog.setTargetPrimitive(targetWay);
379 Set<Relation> parentRelations = getParentRelations(ways);
380 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, ways, oldWays);
381 dialog.prepareDefaultDecisions();
382
383 // resolve tag conflicts if necessary
384 if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
385 dialog.setVisible(true);
386 if (!dialog.isApplied())
387 throw new UserCancelException();
388 }
389
390 List<Command> cmds = new LinkedList<>();
391 deletes.addAll(ways);
392 deletes.remove(targetWay);
393
394 cmds.add(new ChangeCommand(getLayerManager().getEditDataSet(), targetWay, modifiedTargetWay));
395 cmds.addAll(dialog.buildWayResolutionCommands());
396 dialog.buildRelationCorrespondance(newRelations, oldWays);
397
398 return new Pair<>(targetWay, cmds);
399 }
400
401 private static Way getTargetWay(Collection<Way> combinedWays) {
402 // init with an arbitrary way
403 Way targetWay = combinedWays.iterator().next();
404
405 // look for the first way already existing on the server
406 for (Way w : combinedWays) {
407 targetWay = w;
408 if (!w.isNew()) {
409 break;
410 }
411 }
412 return targetWay;
413 }
414
415 /**
416 * @return has tag to be merged (=> ask)
417 */
418 private static boolean askForMergeTag(Collection<Way> ways) {
419 for (Way way : ways) {
420 for (Way oposite : ways) {
421 for (String key : way.getKeys().keySet()) {
422 if (!"source".equals(key) && oposite.hasKey(key)
423 && !way.get(key).equals(oposite.get(key))) {
424 return true;
425 }
426 }
427 }
428 }
429 return false;
430 }
431
432 /**
433 * @return has duplicate parent relation
434 */
435 private boolean duplicateParentRelations(Collection<Way> ways) {
436 Set<Relation> relations = new HashSet<>();
437 for (Way w : ways) {
438 List<Relation> rs = getParentRelations(w);
439 for (Relation r : rs) {
440 if (relations.contains(r)) {
441 return true;
442 }
443 }
444 relations.addAll(rs);
445 }
446 return false;
447 }
448
449 /**
450 * Replies the set of referring relations
451 *
452 * @return the set of referring relations
453 */
454 private List<Relation> getParentRelations(Way way) {
455 List<Relation> rels = new ArrayList<>();
456 for (Relation r : relations.get(way)) {
457 if (newRelations.containsKey(r)) {
458 rels.add(newRelations.get(r));
459 } else {
460 rels.add(r);
461 }
462 }
463 return rels;
464 }
465
466 public static Relation getNew(Relation r, Map<Relation, Relation> newRelations) {
467 if (newRelations.containsValue(r)) {
468 return r;
469 } else {
470 Relation c = new Relation(r);
471 newRelations.put(r, c);
472 return c;
473 }
474 }
475/*
476 private Way getOld(Way r) {
477 return getOld(r, oldWays);
478 }*/
479
480 public static Way getOld(Way w, Map<Way, Way> oldWays) {
481 if (oldWays.containsKey(w)) {
482 return oldWays.get(w);
483 } else {
484 return w;
485 }
486 }
487
488 /**
489 * Replies the set of referring relations
490 *
491 * @return the set of referring relations
492 */
493 private Set<Relation> getParentRelations(Collection<Way> ways) {
494 Set<Relation> ret = new HashSet<>();
495 for (Way w : ways) {
496 ret.addAll(getParentRelations(w));
497 }
498 return ret;
499 }
500
501 /** Enable this action only if something is selected */
502 @Override
503 protected void updateEnabledState() {
504 if (getLayerManager().getEditDataSet() == null) {
505 setEnabled(false);
506 } else {
507 updateEnabledState(getLayerManager().getEditDataSet().getSelected());
508 }
509 }
510
511 /** Enable this action only if something is selected */
512 @Override
513 protected void updateEnabledState(
514 Collection<? extends OsmPrimitive> selection) {
515 if (selection == null) {
516 setEnabled(false);
517 return;
518 }
519 for (OsmPrimitive primitive : selection) {
520 if (!(primitive instanceof Way) || primitive.isDeleted()) {
521 setEnabled(false);
522 return;
523 }
524 }
525 setEnabled(selection.size() >= 2);
526 }
527}
Note: See TracBrowser for help on using the repository browser.