source: josm/trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java@ 12942

Last change on this file since 12942 was 12641, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.main.undoRedo. Replacement: gui.MainApplication.undoRedo

  • Property svn:eol-style set to native
File size: 10.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.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.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.stream.Collectors;
17
18import javax.swing.JOptionPane;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.command.ChangeCommand;
22import org.openstreetmap.josm.command.Command;
23import org.openstreetmap.josm.command.DeleteCommand;
24import org.openstreetmap.josm.command.SequenceCommand;
25import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.data.osm.Node;
28import org.openstreetmap.josm.data.osm.NodeGraph;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.TagCollection;
31import org.openstreetmap.josm.data.osm.Way;
32import org.openstreetmap.josm.data.preferences.BooleanProperty;
33import org.openstreetmap.josm.gui.ExtendedDialog;
34import org.openstreetmap.josm.gui.MainApplication;
35import org.openstreetmap.josm.gui.Notification;
36import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
37import org.openstreetmap.josm.gui.util.GuiHelper;
38import org.openstreetmap.josm.tools.Logging;
39import org.openstreetmap.josm.tools.Pair;
40import org.openstreetmap.josm.tools.Shortcut;
41import org.openstreetmap.josm.tools.UserCancelException;
42
43/**
44 * Combines multiple ways into one.
45 * @since 213
46 */
47public class CombineWayAction extends JosmAction {
48
49 private static final BooleanProperty PROP_REVERSE_WAY = new BooleanProperty("tag-correction.reverse-way", true);
50
51 /**
52 * Constructs a new {@code CombineWayAction}.
53 */
54 public CombineWayAction() {
55 super(tr("Combine Way"), "combineway", tr("Combine several ways into one."),
56 Shortcut.registerShortcut("tools:combineway", tr("Tool: {0}", tr("Combine Way")), KeyEvent.VK_C, Shortcut.DIRECT), true);
57 putValue("help", ht("/Action/CombineWay"));
58 }
59
60 protected static boolean confirmChangeDirectionOfWays() {
61 return new ExtendedDialog(Main.parent,
62 tr("Change directions?"),
63 tr("Reverse and Combine"), tr("Cancel"))
64 .setButtonIcons("wayflip", "cancel")
65 .setContent(tr("The ways can not be combined in their current directions. "
66 + "Do you want to reverse some of them?"))
67 .toggleEnable("combineway-reverse")
68 .showDialog()
69 .getValue() == 1;
70 }
71
72 protected static void warnCombiningImpossible() {
73 String msg = tr("Could not combine ways<br>"
74 + "(They could not be merged into a single string of nodes)");
75 new Notification(msg)
76 .setIcon(JOptionPane.INFORMATION_MESSAGE)
77 .show();
78 }
79
80 protected static Way getTargetWay(Collection<Way> combinedWays) {
81 // init with an arbitrary way
82 Way targetWay = combinedWays.iterator().next();
83
84 // look for the first way already existing on
85 // the server
86 for (Way w : combinedWays) {
87 targetWay = w;
88 if (!w.isNew()) {
89 break;
90 }
91 }
92 return targetWay;
93 }
94
95 /**
96 * Combine multiple ways into one.
97 * @param ways the way to combine to one way
98 * @return null if ways cannot be combined. Otherwise returns the combined ways and the commands to combine
99 * @throws UserCancelException if the user cancelled a dialog.
100 */
101 public static Pair<Way, Command> combineWaysWorker(Collection<Way> ways) throws UserCancelException {
102
103 // prepare and clean the list of ways to combine
104 //
105 if (ways == null || ways.isEmpty())
106 return null;
107 ways.remove(null); // just in case - remove all null ways from the collection
108
109 // remove duplicates, preserving order
110 ways = new LinkedHashSet<>(ways);
111 // remove incomplete ways
112 ways.removeIf(OsmPrimitive::isIncomplete);
113 // we need at least two ways
114 if (ways.size() < 2)
115 return null;
116
117 List<DataSet> dataSets = ways.stream().map(Way::getDataSet).distinct().collect(Collectors.toList());
118 if (dataSets.size() != 1) {
119 throw new IllegalArgumentException("Cannot combine ways of multiple data sets.");
120 }
121
122 // try to build a new way which includes all the combined ways
123 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways);
124 List<Node> path = graph.buildSpanningPath();
125 if (path == null) {
126 warnCombiningImpossible();
127 return null;
128 }
129 // check whether any ways have been reversed in the process
130 // and build the collection of tags used by the ways to combine
131 //
132 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
133
134 final List<Command> reverseWayTagCommands = new LinkedList<>();
135 List<Way> reversedWays = new LinkedList<>();
136 List<Way> unreversedWays = new LinkedList<>();
137 for (Way w: ways) {
138 // Treat zero or one-node ways as unreversed as Combine action action is a good way to fix them (see #8971)
139 if (w.getNodesCount() < 2 || (path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
140 unreversedWays.add(w);
141 } else {
142 reversedWays.add(w);
143 }
144 }
145 // reverse path if all ways have been reversed
146 if (unreversedWays.isEmpty()) {
147 Collections.reverse(path);
148 unreversedWays = reversedWays;
149 reversedWays = null;
150 }
151 if ((reversedWays != null) && !reversedWays.isEmpty()) {
152 if (!confirmChangeDirectionOfWays()) return null;
153 // filter out ways that have no direction-dependent tags
154 unreversedWays = ReverseWayTagCorrector.irreversibleWays(unreversedWays);
155 reversedWays = ReverseWayTagCorrector.irreversibleWays(reversedWays);
156 // reverse path if there are more reversed than unreversed ways with direction-dependent tags
157 if (reversedWays.size() > unreversedWays.size()) {
158 Collections.reverse(path);
159 List<Way> tempWays = unreversedWays;
160 unreversedWays = null;
161 reversedWays = tempWays;
162 }
163 // if there are still reversed ways with direction-dependent tags, reverse their tags
164 if (!reversedWays.isEmpty() && PROP_REVERSE_WAY.get()) {
165 List<Way> unreversedTagWays = new ArrayList<>(ways);
166 unreversedTagWays.removeAll(reversedWays);
167 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
168 List<Way> reversedTagWays = new ArrayList<>(reversedWays.size());
169 for (Way w : reversedWays) {
170 Way wnew = new Way(w);
171 reversedTagWays.add(wnew);
172 reverseWayTagCommands.addAll(reverseWayTagCorrector.execute(w, wnew));
173 }
174 if (!reverseWayTagCommands.isEmpty()) {
175 // commands need to be executed for CombinePrimitiveResolverDialog
176 MainApplication.undoRedo.add(new SequenceCommand(tr("Reverse Ways"), reverseWayTagCommands));
177 }
178 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
179 wayTags.add(TagCollection.unionOfAllPrimitives(unreversedTagWays));
180 }
181 }
182
183 // create the new way and apply the new node list
184 //
185 Way targetWay = getTargetWay(ways);
186 Way modifiedTargetWay = new Way(targetWay);
187 modifiedTargetWay.setNodes(path);
188
189 final List<Command> resolution;
190 try {
191 resolution = CombinePrimitiveResolverDialog.launchIfNecessary(wayTags, ways, Collections.singleton(targetWay));
192 } finally {
193 if (!reverseWayTagCommands.isEmpty()) {
194 // undo reverseWayTagCorrector and merge into SequenceCommand below
195 MainApplication.undoRedo.undo();
196 }
197 }
198
199 List<Command> cmds = new LinkedList<>();
200 List<Way> deletedWays = new LinkedList<>(ways);
201 deletedWays.remove(targetWay);
202
203 cmds.add(new ChangeCommand(dataSets.get(0), targetWay, modifiedTargetWay));
204 cmds.addAll(reverseWayTagCommands);
205 cmds.addAll(resolution);
206 cmds.add(new DeleteCommand(dataSets.get(0), deletedWays));
207 final Command sequenceCommand = new SequenceCommand(/* for correct i18n of plural forms - see #9110 */
208 trn("Combine {0} way", "Combine {0} ways", ways.size(), ways.size()), cmds);
209
210 return new Pair<>(targetWay, sequenceCommand);
211 }
212
213 @Override
214 public void actionPerformed(ActionEvent event) {
215 final DataSet ds = getLayerManager().getEditDataSet();
216 if (ds == null)
217 return;
218 Collection<Way> selectedWays = ds.getSelectedWays();
219 if (selectedWays.size() < 2) {
220 new Notification(
221 tr("Please select at least two ways to combine."))
222 .setIcon(JOptionPane.INFORMATION_MESSAGE)
223 .setDuration(Notification.TIME_SHORT)
224 .show();
225 return;
226 }
227 // combine and update gui
228 Pair<Way, Command> combineResult;
229 try {
230 combineResult = combineWaysWorker(selectedWays);
231 } catch (UserCancelException ex) {
232 Logging.trace(ex);
233 return;
234 }
235
236 if (combineResult == null)
237 return;
238 final Way selectedWay = combineResult.a;
239 MainApplication.undoRedo.add(combineResult.b);
240 if (selectedWay != null) {
241 GuiHelper.runInEDT(() -> ds.setSelected(selectedWay));
242 }
243 }
244
245 @Override
246 protected void updateEnabledState() {
247 updateEnabledStateOnCurrentSelection();
248 }
249
250 @Override
251 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
252 int numWays = 0;
253 for (OsmPrimitive osm : selection) {
254 if (osm instanceof Way && !osm.isIncomplete() && ++numWays >= 2) {
255 break;
256 }
257 }
258 setEnabled(numWays >= 2);
259 }
260}
Note: See TracBrowser for help on using the repository browser.