source: subversion/applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/actions/SplitObjectAction.java

Last change on this file was 34824, checked in by gerdp, 3 months ago

fix #12657 Split area (Alt+X) is too restrictive

Probably a simple typo (<= instead of <)

  • Property svn:eol-style set to native
File size: 11.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.utilsplugin2.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
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.List;
16import java.util.Map;
17import java.util.Map.Entry;
18
19import javax.swing.JOptionPane;
20
21import org.openstreetmap.josm.actions.JosmAction;
22import org.openstreetmap.josm.command.DeleteCommand;
23import org.openstreetmap.josm.command.SplitWayCommand;
24import org.openstreetmap.josm.data.UndoRedoHandler;
25import org.openstreetmap.josm.data.osm.DataSet;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.gui.Notification;
30import org.openstreetmap.josm.tools.Shortcut;
31
32/**
33 * Splits a closed way (polygon) into two closed ways.
34 *
35 * The closed ways are just split at the selected nodes (which must be exactly two).
36 * The nodes remain in their original order.
37 *
38 * This is similar to SplitWayAction with the addition that the split ways are closed
39 * immediately.
40 */
41public class SplitObjectAction extends JosmAction {
42    /**
43     * Create a new SplitObjectAction.
44     */
45    public SplitObjectAction() {
46        super(tr("Split Object"), "splitobject", tr("Split an object at the selected nodes."),
47                Shortcut.registerShortcut("tools:splitobject", tr("Tool: {0}", tr("Split Object")), KeyEvent.VK_X, Shortcut.ALT),
48                true);
49        putValue("help", ht("/Action/SplitObject"));
50    }
51
52    /**
53     * Called when the action is executed.
54     *
55     * This method performs an expensive check whether the selection clearly defines one
56     * of the split actions outlined above, and if yes, calls the splitObject method.
57     */
58    @Override
59    public void actionPerformed(ActionEvent e) {
60        DataSet ds = getLayerManager().getEditDataSet();
61        if (!checkSelection(ds.getSelected())) {
62            showWarningNotification(tr("The current selection cannot be used for splitting."));
63            return;
64        }
65
66        List<Node> selectedNodes = new ArrayList<>(ds.getSelectedNodes());
67        List<Way> selectedWays = new ArrayList<>(ds.getSelectedWays());
68
69        Way selectedWay = null;
70        Way splitWay = null;
71
72        if (selectedNodes.size() != 2) {            // if not exactly 2 nodes are selected - try to find split way
73            selectedNodes.clear();                  // empty selected nodes (see #8237)
74            for (Way selWay : selectedWays) {       // we assume not more 2 ways in the list
75                if (selWay != null &&               // If one of selected ways is not closed we have it to get split points
76                        selWay.isUsable() &&
77                        !selWay.isClosed() &&
78                        selWay.getKeys().isEmpty()) {
79                    selectedNodes.add(selWay.firstNode());
80                    selectedNodes.add(selWay.lastNode());
81                    splitWay = selWay;
82                } else {
83                    selectedWay = selWay;           // use another way as selected way
84                }
85            }
86        } else if (selectedWays.size() == 1) {
87            selectedWay = selectedWays.get(0);      // two nodes and a way is selected, so use this selected way
88        }
89
90        // If only nodes are selected, try to guess which way to split. This works if there
91        // is exactly one way that all nodes are part of.
92        if (selectedWay == null && !selectedNodes.isEmpty()) {
93            Map<Way, Integer> wayOccurenceCounter = new HashMap<>();
94            for (Node n : selectedNodes) {
95                for (Way w : n.getParentWays()) {
96                    if (!w.isUsable()) {
97                        continue;
98                    }
99                    // Only closed ways with at least four distinct nodes
100                    // (i.e. five members since the first/last is listed twice)
101                    // can be split into two objects
102                    if (w.getNodesCount() < 5 || !w.isClosed()) {
103                        continue;
104                    }
105                    for (Node wn : w.getNodes()) {
106                        if (n.equals(wn)) {
107                            Integer old = wayOccurenceCounter.get(w);
108                            wayOccurenceCounter.put(w, (old == null) ? 1 : old + 1);
109                            break;
110                        }
111                    }
112                }
113            }
114            if (wayOccurenceCounter.isEmpty()) {
115                showWarningNotification(
116                        trn("The selected node is not in the middle of any way.",
117                                "The selected nodes are not in the middle of any way.",
118                                selectedNodes.size()));
119                return;
120            }
121
122            for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
123                if (entry.getValue().equals(selectedNodes.size())) {
124                    if (selectedWay != null) {
125                        showWarningNotification(
126                                trn("There is more than one way using the node you selected. Please select the way also.",
127                                        "There is more than one way using the nodes you selected. Please select the way also.",
128                                        selectedNodes.size())
129                                );
130                        return;
131                    }
132                    selectedWay = entry.getKey();
133                }
134            }
135
136            if (selectedWay == null) {
137                showWarningNotification(tr("The selected nodes do not share the same way."));
138                return;
139            }
140
141            // If a way and nodes are selected, verify that the nodes
142            // are part of the way and that the way is closed.
143        } else if (selectedWay != null && !selectedNodes.isEmpty()) {
144            if (!selectedWay.isClosed()) {
145                showWarningNotification(tr("The selected way is not closed."));
146                return;
147            }
148            HashSet<Node> nds = new HashSet<>(selectedNodes);
149            nds.removeAll(selectedWay.getNodes());
150            if (!nds.isEmpty()) {
151                showWarningNotification(
152                        trn("The selected way does not contain the selected node.",
153                                "The selected way does not contain all the selected nodes.",
154                                selectedNodes.size()));
155                return;
156            }
157        } else if (selectedWay != null && selectedNodes.isEmpty()) {
158            showWarningNotification(
159                    tr("The selected way is not a split way, please select split points or split way too."));
160            return;
161        }
162
163        // we're guaranteed to have two nodes
164        Node node1 = selectedNodes.get(0);
165        int nodeIndex1 = -1;
166        Node node2 = selectedNodes.get(1);
167        int nodeIndex2 = -1;
168        int i = 0;
169        for (Node wn : selectedWay.getNodes()) {
170            if (nodeIndex1 == -1 && wn.equals(node1)) {
171                nodeIndex1 = i;
172            } else if (nodeIndex2 == -1 && wn.equals(node2)) {
173                nodeIndex2 = i;
174            }
175            i++;
176        }
177        // both nodes aren't allowed to be consecutive
178        if (nodeIndex1 == nodeIndex2 + 1 ||
179                nodeIndex2 == nodeIndex1 + 1 ||
180                // minus 2 because we've a circular way where
181                // the penultimate node is the last unique one
182                (nodeIndex1 == 0 && nodeIndex2 == selectedWay.getNodesCount() - 2) ||
183                (nodeIndex2 == 0 && nodeIndex1 == selectedWay.getNodesCount() - 2)) {
184            showWarningNotification(
185                    tr("The selected nodes can not be consecutive nodes in the object."));
186            return;
187        }
188
189        List<List<Node>> wayChunks = SplitWayCommand.buildSplitChunks(selectedWay, selectedNodes);
190        if (wayChunks != null) {
191            // close the chunks
192            // update the logic - if we have splitWay not null, we have to add points from it to both chunks (in the correct direction)
193            if (splitWay == null) {
194                for (List<Node> wayChunk : wayChunks) {
195                    wayChunk.add(wayChunk.get(0));
196                }
197            } else {
198                for (List<Node> wayChunk : wayChunks) {
199                    // check direction of the chunk and add splitWay nodes in the correct order
200                    List<Node> way = splitWay.getNodes();
201                    if (wayChunk.get(0).equals(splitWay.firstNode())) {
202                        // add way to the end in the opposite direction.
203                        way.remove(way.size()-1); // remove the last node
204                        Collections.reverse(way);
205                    } else {
206                        // add way to the end in the given direction, remove the first node
207                        way.remove(0);
208                    }
209                    wayChunk.addAll(way);
210                }
211            }
212            SplitWayCommand result = SplitWayCommand.splitWay(
213                    selectedWay, wayChunks, Collections.<OsmPrimitive>emptyList());
214            UndoRedoHandler.getInstance().add(result);
215            if (splitWay != null)
216                UndoRedoHandler.getInstance().add(new DeleteCommand(splitWay));
217            getLayerManager().getEditDataSet().setSelected(result.getNewSelection());
218        }
219    }
220
221    /**
222     * Checks if the selection consists of something we can work with.
223     * Checks only if the number and type of items selected looks good;
224     * does not check whether the selected items are really a valid
225     * input for splitting (this would be too expensive to be carried
226     * out from the selectionChanged listener).
227     * @param selection the selection
228     * @return true if the selection is usable
229     */
230    private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
231        int node = 0;
232        int ways = 0;
233        for (OsmPrimitive p : selection) {
234            if (p instanceof Way) {
235                ways++;
236            } else if (p instanceof Node) {
237                node++;
238            } else
239                return false;
240        }
241        return node == 2 || ways == 1 || ways == 2; //only 2 nodes selected. one split-way selected. split-way + way to split.
242    }
243
244    @Override
245    protected void updateEnabledState() {
246        updateEnabledStateOnCurrentSelection();
247    }
248
249    @Override
250    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
251        if (selection == null) {
252            setEnabled(false);
253            return;
254        }
255        setEnabled(checkSelection(selection));
256    }
257
258    void showWarningNotification(String msg) {
259        new Notification(msg)
260        .setIcon(JOptionPane.WARNING_MESSAGE).show();
261    }
262}
Note: See TracBrowser for help on using the repository browser.