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

Last change on this file since 34159 was 34056, checked in by donvip, 7 years ago

fix #14971 - NPE

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