[3704] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
[15160] | 5 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
[3704] | 6 |
|
---|
| 7 | import java.awt.event.ActionEvent;
|
---|
| 8 | import java.awt.event.KeyEvent;
|
---|
| 9 | import java.util.ArrayList;
|
---|
[5225] | 10 | import java.util.Arrays;
|
---|
[3704] | 11 | import java.util.Collection;
|
---|
[6600] | 12 | import java.util.Collections;
|
---|
[3704] | 13 | import java.util.HashMap;
|
---|
[6564] | 14 | import java.util.HashSet;
|
---|
[15160] | 15 | import java.util.LinkedHashSet;
|
---|
[3704] | 16 | import java.util.List;
|
---|
| 17 | import java.util.Map;
|
---|
[6258] | 18 | import java.util.Map.Entry;
|
---|
[5225] | 19 | import java.util.Set;
|
---|
| 20 | import java.util.TreeSet;
|
---|
[16438] | 21 | import java.util.stream.Collectors;
|
---|
[6258] | 22 |
|
---|
[3704] | 23 | import javax.swing.JOptionPane;
|
---|
[6258] | 24 | import javax.swing.SwingUtilities;
|
---|
[3704] | 25 |
|
---|
[7946] | 26 | import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction;
|
---|
[3704] | 27 | import org.openstreetmap.josm.command.AddCommand;
|
---|
[17358] | 28 | import org.openstreetmap.josm.command.ChangeMembersCommand;
|
---|
[3704] | 29 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
---|
| 30 | import org.openstreetmap.josm.command.Command;
|
---|
| 31 | import org.openstreetmap.josm.command.SequenceCommand;
|
---|
[14134] | 32 | import org.openstreetmap.josm.data.UndoRedoHandler;
|
---|
[10382] | 33 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[15531] | 34 | import org.openstreetmap.josm.data.osm.IPrimitive;
|
---|
[3704] | 35 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[12188] | 36 | import org.openstreetmap.josm.data.osm.OsmUtils;
|
---|
[3704] | 37 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
| 38 | import org.openstreetmap.josm.data.osm.RelationMember;
|
---|
| 39 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[15160] | 40 | import org.openstreetmap.josm.data.validation.TestError;
|
---|
| 41 | import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
|
---|
[12630] | 42 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
[6130] | 43 | import org.openstreetmap.josm.gui.Notification;
|
---|
[7946] | 44 | import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
|
---|
[6600] | 45 | import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationTask;
|
---|
[3704] | 46 | import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
|
---|
[10010] | 47 | import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;
|
---|
[13486] | 48 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
---|
[7945] | 49 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[12846] | 50 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
[6564] | 51 | import org.openstreetmap.josm.tools.Pair;
|
---|
[3704] | 52 | import org.openstreetmap.josm.tools.Shortcut;
|
---|
[15531] | 53 | import org.openstreetmap.josm.tools.SubclassFilteredCollection;
|
---|
[6597] | 54 | import org.openstreetmap.josm.tools.Utils;
|
---|
[3704] | 55 |
|
---|
| 56 | /**
|
---|
| 57 | * Create multipolygon from selected ways automatically.
|
---|
| 58 | *
|
---|
[7423] | 59 | * New relation with type=multipolygon is created.
|
---|
[3704] | 60 | *
|
---|
| 61 | * If one or more of ways is already in relation with type=multipolygon or the
|
---|
[7423] | 62 | * way is not closed, then error is reported and no relation is created.
|
---|
[3704] | 63 | *
|
---|
| 64 | * The "inner" and "outer" roles are guessed automatically. First, bbox is
|
---|
| 65 | * calculated for each way. then the largest area is assumed to be outside and
|
---|
| 66 | * the rest inside. In cases with one "outside" area and several cut-ins, the
|
---|
| 67 | * guess should be always good ... In more complex (multiple outer areas) or
|
---|
| 68 | * buggy (inner and outer ways intersect) scenarios the result is likely to be
|
---|
| 69 | * wrong.
|
---|
| 70 | */
|
---|
| 71 | public class CreateMultipolygonAction extends JosmAction {
|
---|
| 72 |
|
---|
[6597] | 73 | private final boolean update;
|
---|
[15531] | 74 | private static final int MAX_MEMBERS_TO_DOWNLOAD = 100;
|
---|
[6597] | 75 |
|
---|
[6258] | 76 | /**
|
---|
| 77 | * Constructs a new {@code CreateMultipolygonAction}.
|
---|
[6623] | 78 | * @param update {@code true} if the multipolygon must be updated, {@code false} if it must be created
|
---|
[6258] | 79 | */
|
---|
[6597] | 80 | public CreateMultipolygonAction(final boolean update) {
|
---|
[17033] | 81 | super(getName(update),
|
---|
| 82 | update ? /* ICON */ "multipoly_update" : /* ICON */ "multipoly_create",
|
---|
| 83 | getName(update),
|
---|
[14302] | 84 | /* at least three lines for each shortcut or the server extractor fails */
|
---|
[10378] | 85 | update ? Shortcut.registerShortcut("tools:multipoly_update",
|
---|
[17188] | 86 | tr("Tools: {0}", getName(true)),
|
---|
[7666] | 87 | KeyEvent.VK_B, Shortcut.CTRL_SHIFT)
|
---|
[10378] | 88 | : Shortcut.registerShortcut("tools:multipoly_create",
|
---|
[17188] | 89 | tr("Tools: {0}", getName(false)),
|
---|
[7666] | 90 | KeyEvent.VK_B, Shortcut.CTRL),
|
---|
[6597] | 91 | true, update ? "multipoly_update" : "multipoly_create", true);
|
---|
| 92 | this.update = update;
|
---|
[3704] | 93 | }
|
---|
[6597] | 94 |
|
---|
| 95 | private static String getName(boolean update) {
|
---|
| 96 | return update ? tr("Update multipolygon") : tr("Create multipolygon");
|
---|
| 97 | }
|
---|
[6792] | 98 |
|
---|
[8419] | 99 | private static final class CreateUpdateMultipolygonTask implements Runnable {
|
---|
[6623] | 100 | private final Collection<Way> selectedWays;
|
---|
| 101 | private final Relation multipolygonRelation;
|
---|
[6597] | 102 |
|
---|
[7945] | 103 | private CreateUpdateMultipolygonTask(Collection<Way> selectedWays, Relation multipolygonRelation) {
|
---|
[6623] | 104 | this.selectedWays = selectedWays;
|
---|
| 105 | this.multipolygonRelation = multipolygonRelation;
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | @Override
|
---|
| 109 | public void run() {
|
---|
| 110 | final Pair<SequenceCommand, Relation> commandAndRelation = createMultipolygonCommand(selectedWays, multipolygonRelation);
|
---|
| 111 | if (commandAndRelation == null) {
|
---|
| 112 | return;
|
---|
| 113 | }
|
---|
| 114 | final Command command = commandAndRelation.a;
|
---|
| 115 |
|
---|
| 116 | // to avoid EDT violations
|
---|
[10601] | 117 | SwingUtilities.invokeLater(() -> {
|
---|
[17358] | 118 | if (multipolygonRelation != null) {
|
---|
| 119 | // rather ugly: update generated a ChangeMembersCommand with a copy of the member list, so clear the list now
|
---|
| 120 | commandAndRelation.b.setMembers(null); // #see 19885
|
---|
| 121 | }
|
---|
[16557] | 122 | UndoRedoHandler.getInstance().add(command);
|
---|
[16554] | 123 | final Relation relation = (Relation) MainApplication.getLayerManager().getEditDataSet()
|
---|
| 124 | .getPrimitiveById(commandAndRelation.b);
|
---|
[16557] | 125 | if (relation == null || relation.getDataSet() == null)
|
---|
| 126 | return; // should not happen
|
---|
[6623] | 127 |
|
---|
[16554] | 128 | // Use 'SwingUtilities.invokeLater' to make sure the relationListDialog
|
---|
| 129 | // knows about the new relation before we try to select it.
|
---|
| 130 | // (Yes, we are already in event dispatch thread. But DatasetEventManager
|
---|
| 131 | // uses 'SwingUtilities.invokeLater' to fire events so we have to do the same.)
|
---|
| 132 | SwingUtilities.invokeLater(() -> {
|
---|
| 133 | MainApplication.getMap().relationListDialog.selectRelation(relation);
|
---|
| 134 | if (Config.getPref().getBoolean("multipoly.show-relation-editor", false)) {
|
---|
| 135 | //Open relation edit window, if set up in preferences
|
---|
[16557] | 136 | // see #19346 un-select updated multipolygon
|
---|
| 137 | MainApplication.getLayerManager().getEditDataSet().clearSelection(relation);
|
---|
[16554] | 138 | RelationEditor editor = RelationEditor
|
---|
| 139 | .getEditor(MainApplication.getLayerManager().getEditLayer(), relation, null);
|
---|
| 140 | editor.setVisible(true);
|
---|
| 141 | } else {
|
---|
| 142 | MainApplication.getLayerManager().getEditLayer().setRecentRelation(relation);
|
---|
[16557] | 143 | if (multipolygonRelation == null) {
|
---|
| 144 | // see #19346 select new multipolygon
|
---|
| 145 | MainApplication.getLayerManager().getEditDataSet().setSelected(relation);
|
---|
| 146 | }
|
---|
[16554] | 147 | }
|
---|
| 148 | });
|
---|
[6623] | 149 | });
|
---|
| 150 | }
|
---|
| 151 | }
|
---|
| 152 |
|
---|
[6084] | 153 | @Override
|
---|
[3704] | 154 | public void actionPerformed(ActionEvent e) {
|
---|
[12636] | 155 | DataSet dataSet = getLayerManager().getEditDataSet();
|
---|
[10453] | 156 | if (dataSet == null) {
|
---|
[6130] | 157 | new Notification(
|
---|
| 158 | tr("No data loaded."))
|
---|
| 159 | .setIcon(JOptionPane.WARNING_MESSAGE)
|
---|
| 160 | .setDuration(Notification.TIME_SHORT)
|
---|
| 161 | .show();
|
---|
[3704] | 162 | return;
|
---|
| 163 | }
|
---|
| 164 |
|
---|
[10453] | 165 | final Collection<Way> selectedWays = dataSet.getSelectedWays();
|
---|
[3704] | 166 |
|
---|
[7945] | 167 | if (selectedWays.isEmpty()) {
|
---|
[3704] | 168 | // Sometimes it make sense creating multipoly of only one way (so it will form outer way)
|
---|
| 169 | // and then splitting the way later (so there are multiple ways forming outer way)
|
---|
[6130] | 170 | new Notification(
|
---|
| 171 | tr("You must select at least one way."))
|
---|
| 172 | .setIcon(JOptionPane.INFORMATION_MESSAGE)
|
---|
| 173 | .setDuration(Notification.TIME_SHORT)
|
---|
| 174 | .show();
|
---|
[3704] | 175 | return;
|
---|
| 176 | }
|
---|
| 177 |
|
---|
[10453] | 178 | final Collection<Relation> selectedRelations = dataSet.getSelectedRelations();
|
---|
[6597] | 179 | final Relation multipolygonRelation = update
|
---|
| 180 | ? getSelectedMultipolygonRelation(selectedWays, selectedRelations)
|
---|
| 181 | : null;
|
---|
[6569] | 182 |
|
---|
[15143] | 183 | if (update && multipolygonRelation == null)
|
---|
| 184 | return;
|
---|
[7946] | 185 | // download incomplete relation or incomplete members if necessary
|
---|
[13486] | 186 | OsmDataLayer editLayer = getLayerManager().getEditLayer();
|
---|
| 187 | if (multipolygonRelation != null && editLayer != null && editLayer.isDownloadable()) {
|
---|
[7946] | 188 | if (!multipolygonRelation.isNew() && multipolygonRelation.isIncomplete()) {
|
---|
[15531] | 189 | MainApplication.worker
|
---|
| 190 | .submit(new DownloadRelationTask(Collections.singleton(multipolygonRelation), editLayer));
|
---|
[7946] | 191 | } else if (multipolygonRelation.hasIncompleteMembers()) {
|
---|
[15531] | 192 | // with complex relations the download of the full relation is much faster than download of almost all members, see #18341
|
---|
| 193 | SubclassFilteredCollection<IPrimitive, OsmPrimitive> incompleteMembers = Utils
|
---|
| 194 | .filteredCollection(DownloadSelectedIncompleteMembersAction.buildSetOfIncompleteMembers(
|
---|
| 195 | Collections.singleton(multipolygonRelation)), OsmPrimitive.class);
|
---|
| 196 |
|
---|
| 197 | if (incompleteMembers.size() <= MAX_MEMBERS_TO_DOWNLOAD) {
|
---|
| 198 | MainApplication.worker
|
---|
| 199 | .submit(new DownloadRelationMemberTask(multipolygonRelation, incompleteMembers, editLayer));
|
---|
| 200 | } else {
|
---|
| 201 | MainApplication.worker
|
---|
| 202 | .submit(new DownloadRelationTask(Collections.singleton(multipolygonRelation), editLayer));
|
---|
| 203 |
|
---|
| 204 | }
|
---|
[7946] | 205 | }
|
---|
[6600] | 206 | }
|
---|
| 207 | // create/update multipolygon relation
|
---|
[12634] | 208 | MainApplication.worker.submit(new CreateUpdateMultipolygonTask(selectedWays, multipolygonRelation));
|
---|
[6564] | 209 | }
|
---|
[3704] | 210 |
|
---|
[6597] | 211 | private static Relation getSelectedMultipolygonRelation(Collection<Way> selectedWays, Collection<Relation> selectedRelations) {
|
---|
[15137] | 212 | Relation candidate = null;
|
---|
| 213 | if (selectedRelations.size() == 1) {
|
---|
| 214 | candidate = selectedRelations.iterator().next();
|
---|
| 215 | if (!candidate.hasTag("type", "multipolygon"))
|
---|
| 216 | candidate = null;
|
---|
| 217 | } else if (!selectedWays.isEmpty()) {
|
---|
[6597] | 218 | for (final Way w : selectedWays) {
|
---|
[15137] | 219 | for (OsmPrimitive r : w.getReferrers()) {
|
---|
[15143] | 220 | if (r != candidate && !r.isDisabled() && r instanceof Relation && r.hasTag("type", "multipolygon")) {
|
---|
[15137] | 221 | if (candidate != null)
|
---|
| 222 | return null; // found another multipolygon relation
|
---|
| 223 | candidate = (Relation) r;
|
---|
| 224 | }
|
---|
| 225 | }
|
---|
[6597] | 226 | }
|
---|
| 227 | }
|
---|
[15137] | 228 | return candidate;
|
---|
[6597] | 229 | }
|
---|
| 230 |
|
---|
[6564] | 231 | /**
|
---|
| 232 | * Returns a {@link Pair} of the old multipolygon {@link Relation} (or null) and the newly created/modified multipolygon {@link Relation}.
|
---|
[8795] | 233 | * @param selectedWays selected ways
|
---|
| 234 | * @param selectedMultipolygonRelation selected multipolygon relation
|
---|
[17358] | 235 | * @return null if ways don't build a valid multipolygon, pair of old and new multipolygon relation if a difference was found,
|
---|
| 236 | * else the pair contains the old relation twice
|
---|
[6564] | 237 | */
|
---|
[6597] | 238 | public static Pair<Relation, Relation> updateMultipolygonRelation(Collection<Way> selectedWays, Relation selectedMultipolygonRelation) {
|
---|
[3704] | 239 |
|
---|
[6597] | 240 | // add ways of existing relation to include them in polygon analysis
|
---|
[7005] | 241 | Set<Way> ways = new HashSet<>(selectedWays);
|
---|
[6623] | 242 | ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
|
---|
[6564] | 243 |
|
---|
[15160] | 244 | // even if no way was added the inner/outer roles might be different
|
---|
| 245 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 246 | Relation calculated = mpTest.makeFromWays(ways);
|
---|
| 247 | if (mpTest.getErrors().isEmpty()) {
|
---|
| 248 | return mergeRelationsMembers(selectedMultipolygonRelation, calculated);
|
---|
[6564] | 249 | }
|
---|
[15160] | 250 | showErrors(mpTest.getErrors());
|
---|
[17358] | 251 | calculated.setMembers(null); // see #19885
|
---|
[15160] | 252 | return null; //could not make multipolygon.
|
---|
[6597] | 253 | }
|
---|
[6564] | 254 |
|
---|
[6597] | 255 | /**
|
---|
[15160] | 256 | * Merge members of multipolygon relation. Maintains the order of the old relation. May change roles,
|
---|
| 257 | * removes duplicate and non-way members and adds new members found in {@code calculated}.
|
---|
| 258 | * @param old old multipolygon relation
|
---|
| 259 | * @param calculated calculated multipolygon relation
|
---|
| 260 | * @return pair of old and new multipolygon relation if a difference was found, else the pair contains the old relation twice
|
---|
| 261 | */
|
---|
| 262 | private static Pair<Relation, Relation> mergeRelationsMembers(Relation old, Relation calculated) {
|
---|
| 263 | Set<RelationMember> merged = new LinkedHashSet<>();
|
---|
| 264 | boolean foundDiff = false;
|
---|
| 265 | int nonWayMember = 0;
|
---|
| 266 | // maintain order of members in updated relation
|
---|
| 267 | for (RelationMember oldMem :old.getMembers()) {
|
---|
| 268 | if (oldMem.isNode() || oldMem.isRelation()) {
|
---|
| 269 | nonWayMember++;
|
---|
| 270 | continue;
|
---|
| 271 | }
|
---|
| 272 | for (RelationMember newMem : calculated.getMembers()) {
|
---|
| 273 | if (newMem.getMember().equals(oldMem.getMember())) {
|
---|
| 274 | if (!newMem.getRole().equals(oldMem.getRole())) {
|
---|
| 275 | foundDiff = true;
|
---|
| 276 | }
|
---|
| 277 | foundDiff |= !merged.add(newMem); // detect duplicate members in old relation
|
---|
| 278 | break;
|
---|
| 279 | }
|
---|
| 280 | }
|
---|
| 281 | }
|
---|
| 282 | if (nonWayMember > 0) {
|
---|
| 283 | foundDiff = true;
|
---|
| 284 | String msg = trn("Non-Way member removed from multipolygon", "Non-Way members removed from multipolygon", nonWayMember);
|
---|
| 285 | GuiHelper.runInEDT(() -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
|
---|
| 286 | }
|
---|
| 287 | foundDiff |= merged.addAll(calculated.getMembers());
|
---|
[17358] | 288 | calculated.setMembers(null); // see #19885
|
---|
[15160] | 289 | if (!foundDiff) {
|
---|
| 290 | return Pair.create(old, old); // unchanged
|
---|
| 291 | }
|
---|
| 292 | Relation toModify = new Relation(old);
|
---|
| 293 | toModify.setMembers(new ArrayList<>(merged));
|
---|
| 294 | return Pair.create(old, toModify);
|
---|
| 295 | }
|
---|
| 296 |
|
---|
| 297 | /**
|
---|
[6597] | 298 | * Returns a {@link Pair} null and the newly created/modified multipolygon {@link Relation}.
|
---|
[8795] | 299 | * @param selectedWays selected ways
|
---|
| 300 | * @param showNotif if {@code true}, shows a notification if an error occurs
|
---|
| 301 | * @return pair of null and new multipolygon relation
|
---|
[6597] | 302 | */
|
---|
[6622] | 303 | public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
|
---|
[15160] | 304 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 305 | Relation calculated = mpTest.makeFromWays(selectedWays);
|
---|
| 306 | calculated.setMembers(RelationSorter.sortMembersByConnectivity(calculated.getMembers()));
|
---|
| 307 | if (mpTest.getErrors().isEmpty())
|
---|
| 308 | return Pair.create(null, calculated);
|
---|
| 309 | if (showNotif) {
|
---|
| 310 | showErrors(mpTest.getErrors());
|
---|
| 311 | }
|
---|
[17219] | 312 | calculated.setMembers(null); // see #19885
|
---|
[15160] | 313 | return null; //could not make multipolygon.
|
---|
| 314 | }
|
---|
[6597] | 315 |
|
---|
[15160] | 316 | private static void showErrors(List<TestError> errors) {
|
---|
| 317 | if (!errors.isEmpty()) {
|
---|
[17003] | 318 | String errorMessages = errors.stream()
|
---|
| 319 | .map(TestError::getMessage)
|
---|
| 320 | .distinct()
|
---|
| 321 | .collect(Collectors.joining("\n"));
|
---|
| 322 | GuiHelper.runInEDT(() -> new Notification(errorMessages).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
|
---|
[3704] | 323 | }
|
---|
[6564] | 324 | }
|
---|
[3704] | 325 |
|
---|
[6564] | 326 | /**
|
---|
[6623] | 327 | * Returns a {@link Pair} of a multipolygon creating/modifying {@link Command} as well as the multipolygon {@link Relation}.
|
---|
[8795] | 328 | * @param selectedWays selected ways
|
---|
| 329 | * @param selectedMultipolygonRelation selected multipolygon relation
|
---|
| 330 | * @return pair of command and multipolygon relation
|
---|
[6564] | 331 | */
|
---|
[8540] | 332 | public static Pair<SequenceCommand, Relation> createMultipolygonCommand(Collection<Way> selectedWays,
|
---|
| 333 | Relation selectedMultipolygonRelation) {
|
---|
[3704] | 334 |
|
---|
[6597] | 335 | final Pair<Relation, Relation> rr = selectedMultipolygonRelation == null
|
---|
[6622] | 336 | ? createMultipolygonRelation(selectedWays, true)
|
---|
[6597] | 337 | : updateMultipolygonRelation(selectedWays, selectedMultipolygonRelation);
|
---|
[6564] | 338 | if (rr == null) {
|
---|
| 339 | return null;
|
---|
| 340 | }
|
---|
[15160] | 341 | boolean unchanged = rr.a == rr.b;
|
---|
[6564] | 342 | final Relation existingRelation = rr.a;
|
---|
| 343 | final Relation relation = rr.b;
|
---|
| 344 |
|
---|
| 345 | final List<Command> list = removeTagsFromWaysIfNeeded(relation);
|
---|
| 346 | final String commandName;
|
---|
| 347 | if (existingRelation == null) {
|
---|
[12726] | 348 | list.add(new AddCommand(selectedWays.iterator().next().getDataSet(), relation));
|
---|
[6597] | 349 | commandName = getName(false);
|
---|
[6564] | 350 | } else {
|
---|
[15160] | 351 | if (!unchanged) {
|
---|
[17358] | 352 | list.add(new ChangeMembersCommand(existingRelation, new ArrayList<>(relation.getMembers())));
|
---|
[15160] | 353 | }
|
---|
| 354 | if (list.isEmpty()) {
|
---|
| 355 | if (unchanged) {
|
---|
| 356 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 357 | mpTest.visit(existingRelation);
|
---|
| 358 | if (!mpTest.getErrors().isEmpty()) {
|
---|
| 359 | showErrors(mpTest.getErrors());
|
---|
| 360 | return null;
|
---|
| 361 | }
|
---|
| 362 | }
|
---|
| 363 |
|
---|
[15161] | 364 | GuiHelper.runInEDT(() -> new Notification(tr("Nothing changed")).setDuration(Notification.TIME_SHORT)
|
---|
| 365 | .setIcon(JOptionPane.INFORMATION_MESSAGE).show());
|
---|
[15160] | 366 | return null;
|
---|
| 367 | }
|
---|
[6597] | 368 | commandName = getName(true);
|
---|
[6564] | 369 | }
|
---|
| 370 | return Pair.create(new SequenceCommand(commandName, list), relation);
|
---|
[3704] | 371 | }
|
---|
| 372 |
|
---|
| 373 | /** Enable this action only if something is selected */
|
---|
[7423] | 374 | @Override
|
---|
| 375 | protected void updateEnabledState() {
|
---|
[10548] | 376 | updateEnabledStateOnCurrentSelection();
|
---|
[3704] | 377 | }
|
---|
| 378 |
|
---|
[6069] | 379 | /**
|
---|
[5818] | 380 | * Enable this action only if something is selected
|
---|
| 381 | *
|
---|
[15137] | 382 | * @param selection the current selection, gets tested for emptiness
|
---|
[5818] | 383 | */
|
---|
[7423] | 384 | @Override
|
---|
| 385 | protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
|
---|
[10382] | 386 | DataSet ds = getLayerManager().getEditDataSet();
|
---|
[15137] | 387 | if (ds == null || selection.isEmpty()) {
|
---|
[8385] | 388 | setEnabled(false);
|
---|
| 389 | } else if (update) {
|
---|
[15137] | 390 | setEnabled(getSelectedMultipolygonRelation(ds.getSelectedWays(), ds.getSelectedRelations()) != null);
|
---|
[6597] | 391 | } else {
|
---|
[15137] | 392 | setEnabled(!ds.getSelectedWays().isEmpty());
|
---|
[6597] | 393 | }
|
---|
[3704] | 394 | }
|
---|
| 395 |
|
---|
[8533] | 396 | private static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
|
---|
[5225] | 397 |
|
---|
[3704] | 398 | /**
|
---|
[17358] | 399 | * This method removes tags/value pairs from inner and outer ways and put them on relation if necessary.
|
---|
[6069] | 400 | * Function was extended in reltoolbox plugin by Zverikk and copied back to the core
|
---|
[17358] | 401 | * @param relation the multipolygon style relation to process. If it is new, the tags might be
|
---|
| 402 | * modified, else the list of commands will contain a command to modify its tags
|
---|
[5818] | 403 | * @return a list of commands to execute
|
---|
[3704] | 404 | */
|
---|
[7423] | 405 | public static List<Command> removeTagsFromWaysIfNeeded(Relation relation) {
|
---|
[7005] | 406 | Map<String, String> values = new HashMap<>(relation.getKeys());
|
---|
[3704] | 407 |
|
---|
[7005] | 408 | List<Way> innerWays = new ArrayList<>();
|
---|
| 409 | List<Way> outerWays = new ArrayList<>();
|
---|
[3704] | 410 |
|
---|
[7005] | 411 | Set<String> conflictingKeys = new TreeSet<>();
|
---|
[3704] | 412 |
|
---|
[8443] | 413 | for (RelationMember m : relation.getMembers()) {
|
---|
[3704] | 414 |
|
---|
[8443] | 415 | if (m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
|
---|
[5818] | 416 | innerWays.add(m.getWay());
|
---|
| 417 | }
|
---|
[3704] | 418 |
|
---|
[8443] | 419 | if (m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
|
---|
[5818] | 420 | Way way = m.getWay();
|
---|
| 421 | outerWays.add(way);
|
---|
[6069] | 422 |
|
---|
[7423] | 423 | for (String key : way.keySet()) {
|
---|
| 424 | if (!values.containsKey(key)) { //relation values take precedence
|
---|
[5818] | 425 | values.put(key, way.get(key));
|
---|
[17343] | 426 | } else if (!values.get(key).equals(way.get(key))) {
|
---|
[5818] | 427 | conflictingKeys.add(key);
|
---|
| 428 | }
|
---|
| 429 | }
|
---|
| 430 | }
|
---|
| 431 | }
|
---|
[3704] | 432 |
|
---|
[5818] | 433 | // filter out empty key conflicts - we need second iteration
|
---|
[12846] | 434 | if (!Config.getPref().getBoolean("multipoly.alltags", false)) {
|
---|
[8513] | 435 | for (RelationMember m : relation.getMembers()) {
|
---|
| 436 | if (m.hasRole() && "outer".equals(m.getRole()) && m.isWay()) {
|
---|
| 437 | for (String key : values.keySet()) {
|
---|
| 438 | if (!m.getWay().hasKey(key) && !relation.hasKey(key)) {
|
---|
[5818] | 439 | conflictingKeys.add(key);
|
---|
[8513] | 440 | }
|
---|
| 441 | }
|
---|
| 442 | }
|
---|
| 443 | }
|
---|
| 444 | }
|
---|
[3704] | 445 |
|
---|
[8513] | 446 | for (String key : conflictingKeys) {
|
---|
[5818] | 447 | values.remove(key);
|
---|
[8513] | 448 | }
|
---|
[3704] | 449 |
|
---|
[12846] | 450 | for (String linearTag : Config.getPref().getList("multipoly.lineartagstokeep", DEFAULT_LINEAR_TAGS)) {
|
---|
[5818] | 451 | values.remove(linearTag);
|
---|
[8513] | 452 | }
|
---|
[3704] | 453 |
|
---|
[6765] | 454 | if ("coastline".equals(values.get("natural")))
|
---|
[5818] | 455 | values.remove("natural");
|
---|
[5225] | 456 |
|
---|
[12188] | 457 | values.put("area", OsmUtils.TRUE_VALUE);
|
---|
[5225] | 458 |
|
---|
[7005] | 459 | List<Command> commands = new ArrayList<>();
|
---|
[12846] | 460 | boolean moveTags = Config.getPref().getBoolean("multipoly.movetags", true);
|
---|
[5225] | 461 |
|
---|
[6258] | 462 | for (Entry<String, String> entry : values.entrySet()) {
|
---|
| 463 | String key = entry.getKey();
|
---|
| 464 | String value = entry.getValue();
|
---|
[16438] | 465 | List<OsmPrimitive> affectedWays = innerWays.stream().filter(way -> value.equals(way.get(key))).collect(Collectors.toList());
|
---|
[5225] | 466 |
|
---|
[6258] | 467 | if (moveTags) {
|
---|
[5818] | 468 | // remove duplicated tags from outer ways
|
---|
[8443] | 469 | for (Way way : outerWays) {
|
---|
| 470 | if (way.hasKey(key)) {
|
---|
[5818] | 471 | affectedWays.add(way);
|
---|
| 472 | }
|
---|
| 473 | }
|
---|
| 474 | }
|
---|
[5225] | 475 |
|
---|
[6258] | 476 | if (!affectedWays.isEmpty()) {
|
---|
[5225] | 477 | // reset key tag on affected ways
|
---|
[5818] | 478 | commands.add(new ChangePropertyCommand(affectedWays, key, null));
|
---|
| 479 | }
|
---|
| 480 | }
|
---|
[5225] | 481 |
|
---|
[17358] | 482 | values.remove("area");
|
---|
| 483 | if (moveTags && !values.isEmpty()) {
|
---|
[5818] | 484 | // add those tag values to the relation
|
---|
| 485 | boolean fixed = false;
|
---|
[17358] | 486 | Map<String, String> tagsToAdd = new HashMap<>();
|
---|
[6258] | 487 | for (Entry<String, String> entry : values.entrySet()) {
|
---|
| 488 | String key = entry.getKey();
|
---|
[17358] | 489 | if (!relation.hasKey(key)) {
|
---|
[6258] | 490 | if (relation.isNew())
|
---|
| 491 | relation.put(key, entry.getValue());
|
---|
[5818] | 492 | else
|
---|
[17358] | 493 | tagsToAdd.put(key, entry.getValue());
|
---|
[5818] | 494 | fixed = true;
|
---|
| 495 | }
|
---|
| 496 | }
|
---|
[13067] | 497 | if (fixed && !relation.isNew()) {
|
---|
| 498 | DataSet ds = relation.getDataSet();
|
---|
| 499 | if (ds == null) {
|
---|
| 500 | ds = MainApplication.getLayerManager().getEditDataSet();
|
---|
| 501 | }
|
---|
[17358] | 502 | commands.add(new ChangePropertyCommand(ds, Collections.singleton(relation), tagsToAdd));
|
---|
[13067] | 503 | }
|
---|
[5818] | 504 | }
|
---|
[5225] | 505 |
|
---|
[5818] | 506 | return commands;
|
---|
[3704] | 507 | }
|
---|
| 508 | }
|
---|