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

Last change on this file since 14134 was 14134, checked in by Don-vip, 6 years ago

see #15229 - deprecate Main*.undoRedo - make UndoRedoHandler a singleton

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