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

Last change on this file was 36483, checked in by stoecker, 7 months ago

set eol-style, fix checkstyle issues, add ignores

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