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

Last change on this file was 34972, checked in by donvip, 10 days ago

see #josm17580 #josm17581 #josm17582 #josm17583 #josm17584 #josm17585 #josm17586 #josm17587 #josm17588 - remove deprecated api (patches by taylor.smock)

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