[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;
|
---|
[5225] | 28 | import org.openstreetmap.josm.command.ChangeCommand;
|
---|
[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(() -> {
|
---|
[16557] | 118 | UndoRedoHandler.getInstance().add(command);
|
---|
[16554] | 119 | final Relation relation = (Relation) MainApplication.getLayerManager().getEditDataSet()
|
---|
| 120 | .getPrimitiveById(commandAndRelation.b);
|
---|
[16557] | 121 | if (relation == null || relation.getDataSet() == null)
|
---|
| 122 | return; // should not happen
|
---|
[6623] | 123 |
|
---|
[16554] | 124 | // Use 'SwingUtilities.invokeLater' to make sure the relationListDialog
|
---|
| 125 | // knows about the new relation before we try to select it.
|
---|
| 126 | // (Yes, we are already in event dispatch thread. But DatasetEventManager
|
---|
| 127 | // uses 'SwingUtilities.invokeLater' to fire events so we have to do the same.)
|
---|
| 128 | SwingUtilities.invokeLater(() -> {
|
---|
| 129 | MainApplication.getMap().relationListDialog.selectRelation(relation);
|
---|
| 130 | if (Config.getPref().getBoolean("multipoly.show-relation-editor", false)) {
|
---|
| 131 | //Open relation edit window, if set up in preferences
|
---|
[16557] | 132 | // see #19346 un-select updated multipolygon
|
---|
| 133 | MainApplication.getLayerManager().getEditDataSet().clearSelection(relation);
|
---|
[16554] | 134 | RelationEditor editor = RelationEditor
|
---|
| 135 | .getEditor(MainApplication.getLayerManager().getEditLayer(), relation, null);
|
---|
| 136 | editor.setModal(true);
|
---|
| 137 | editor.setVisible(true);
|
---|
| 138 | } else {
|
---|
| 139 | MainApplication.getLayerManager().getEditLayer().setRecentRelation(relation);
|
---|
[16557] | 140 | if (multipolygonRelation == null) {
|
---|
| 141 | // see #19346 select new multipolygon
|
---|
| 142 | MainApplication.getLayerManager().getEditDataSet().setSelected(relation);
|
---|
| 143 | }
|
---|
[16554] | 144 | }
|
---|
| 145 | });
|
---|
[6623] | 146 | });
|
---|
| 147 | }
|
---|
| 148 | }
|
---|
| 149 |
|
---|
[6084] | 150 | @Override
|
---|
[3704] | 151 | public void actionPerformed(ActionEvent e) {
|
---|
[12636] | 152 | DataSet dataSet = getLayerManager().getEditDataSet();
|
---|
[10453] | 153 | if (dataSet == null) {
|
---|
[6130] | 154 | new Notification(
|
---|
| 155 | tr("No data loaded."))
|
---|
| 156 | .setIcon(JOptionPane.WARNING_MESSAGE)
|
---|
| 157 | .setDuration(Notification.TIME_SHORT)
|
---|
| 158 | .show();
|
---|
[3704] | 159 | return;
|
---|
| 160 | }
|
---|
| 161 |
|
---|
[10453] | 162 | final Collection<Way> selectedWays = dataSet.getSelectedWays();
|
---|
[3704] | 163 |
|
---|
[7945] | 164 | if (selectedWays.isEmpty()) {
|
---|
[3704] | 165 | // Sometimes it make sense creating multipoly of only one way (so it will form outer way)
|
---|
| 166 | // and then splitting the way later (so there are multiple ways forming outer way)
|
---|
[6130] | 167 | new Notification(
|
---|
| 168 | tr("You must select at least one way."))
|
---|
| 169 | .setIcon(JOptionPane.INFORMATION_MESSAGE)
|
---|
| 170 | .setDuration(Notification.TIME_SHORT)
|
---|
| 171 | .show();
|
---|
[3704] | 172 | return;
|
---|
| 173 | }
|
---|
| 174 |
|
---|
[10453] | 175 | final Collection<Relation> selectedRelations = dataSet.getSelectedRelations();
|
---|
[6597] | 176 | final Relation multipolygonRelation = update
|
---|
| 177 | ? getSelectedMultipolygonRelation(selectedWays, selectedRelations)
|
---|
| 178 | : null;
|
---|
[6569] | 179 |
|
---|
[15143] | 180 | if (update && multipolygonRelation == null)
|
---|
| 181 | return;
|
---|
[7946] | 182 | // download incomplete relation or incomplete members if necessary
|
---|
[13486] | 183 | OsmDataLayer editLayer = getLayerManager().getEditLayer();
|
---|
| 184 | if (multipolygonRelation != null && editLayer != null && editLayer.isDownloadable()) {
|
---|
[7946] | 185 | if (!multipolygonRelation.isNew() && multipolygonRelation.isIncomplete()) {
|
---|
[15531] | 186 | MainApplication.worker
|
---|
| 187 | .submit(new DownloadRelationTask(Collections.singleton(multipolygonRelation), editLayer));
|
---|
[7946] | 188 | } else if (multipolygonRelation.hasIncompleteMembers()) {
|
---|
[15531] | 189 | // with complex relations the download of the full relation is much faster than download of almost all members, see #18341
|
---|
| 190 | SubclassFilteredCollection<IPrimitive, OsmPrimitive> incompleteMembers = Utils
|
---|
| 191 | .filteredCollection(DownloadSelectedIncompleteMembersAction.buildSetOfIncompleteMembers(
|
---|
| 192 | Collections.singleton(multipolygonRelation)), OsmPrimitive.class);
|
---|
| 193 |
|
---|
| 194 | if (incompleteMembers.size() <= MAX_MEMBERS_TO_DOWNLOAD) {
|
---|
| 195 | MainApplication.worker
|
---|
| 196 | .submit(new DownloadRelationMemberTask(multipolygonRelation, incompleteMembers, editLayer));
|
---|
| 197 | } else {
|
---|
| 198 | MainApplication.worker
|
---|
| 199 | .submit(new DownloadRelationTask(Collections.singleton(multipolygonRelation), editLayer));
|
---|
| 200 |
|
---|
| 201 | }
|
---|
[7946] | 202 | }
|
---|
[6600] | 203 | }
|
---|
| 204 | // create/update multipolygon relation
|
---|
[12634] | 205 | MainApplication.worker.submit(new CreateUpdateMultipolygonTask(selectedWays, multipolygonRelation));
|
---|
[6564] | 206 | }
|
---|
[3704] | 207 |
|
---|
[6597] | 208 | private static Relation getSelectedMultipolygonRelation(Collection<Way> selectedWays, Collection<Relation> selectedRelations) {
|
---|
[15137] | 209 | Relation candidate = null;
|
---|
| 210 | if (selectedRelations.size() == 1) {
|
---|
| 211 | candidate = selectedRelations.iterator().next();
|
---|
| 212 | if (!candidate.hasTag("type", "multipolygon"))
|
---|
| 213 | candidate = null;
|
---|
| 214 | } else if (!selectedWays.isEmpty()) {
|
---|
[6597] | 215 | for (final Way w : selectedWays) {
|
---|
[15137] | 216 | for (OsmPrimitive r : w.getReferrers()) {
|
---|
[15143] | 217 | if (r != candidate && !r.isDisabled() && r instanceof Relation && r.hasTag("type", "multipolygon")) {
|
---|
[15137] | 218 | if (candidate != null)
|
---|
| 219 | return null; // found another multipolygon relation
|
---|
| 220 | candidate = (Relation) r;
|
---|
| 221 | }
|
---|
| 222 | }
|
---|
[6597] | 223 | }
|
---|
| 224 | }
|
---|
[15137] | 225 | return candidate;
|
---|
[6597] | 226 | }
|
---|
| 227 |
|
---|
[6564] | 228 | /**
|
---|
| 229 | * Returns a {@link Pair} of the old multipolygon {@link Relation} (or null) and the newly created/modified multipolygon {@link Relation}.
|
---|
[8795] | 230 | * @param selectedWays selected ways
|
---|
| 231 | * @param selectedMultipolygonRelation selected multipolygon relation
|
---|
[15160] | 232 | * @return pair of old and new multipolygon relation if a difference was found, else the pair contains the old relation twice
|
---|
[6564] | 233 | */
|
---|
[6597] | 234 | public static Pair<Relation, Relation> updateMultipolygonRelation(Collection<Way> selectedWays, Relation selectedMultipolygonRelation) {
|
---|
[3704] | 235 |
|
---|
[6597] | 236 | // add ways of existing relation to include them in polygon analysis
|
---|
[7005] | 237 | Set<Way> ways = new HashSet<>(selectedWays);
|
---|
[6623] | 238 | ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
|
---|
[6564] | 239 |
|
---|
[15160] | 240 | // even if no way was added the inner/outer roles might be different
|
---|
| 241 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 242 | Relation calculated = mpTest.makeFromWays(ways);
|
---|
| 243 | if (mpTest.getErrors().isEmpty()) {
|
---|
| 244 | return mergeRelationsMembers(selectedMultipolygonRelation, calculated);
|
---|
[6564] | 245 | }
|
---|
[15160] | 246 | showErrors(mpTest.getErrors());
|
---|
| 247 | return null; //could not make multipolygon.
|
---|
[6597] | 248 | }
|
---|
[6564] | 249 |
|
---|
[6597] | 250 | /**
|
---|
[15160] | 251 | * Merge members of multipolygon relation. Maintains the order of the old relation. May change roles,
|
---|
| 252 | * removes duplicate and non-way members and adds new members found in {@code calculated}.
|
---|
| 253 | * @param old old multipolygon relation
|
---|
| 254 | * @param calculated calculated multipolygon relation
|
---|
| 255 | * @return pair of old and new multipolygon relation if a difference was found, else the pair contains the old relation twice
|
---|
| 256 | */
|
---|
| 257 | private static Pair<Relation, Relation> mergeRelationsMembers(Relation old, Relation calculated) {
|
---|
| 258 | Set<RelationMember> merged = new LinkedHashSet<>();
|
---|
| 259 | boolean foundDiff = false;
|
---|
| 260 | int nonWayMember = 0;
|
---|
| 261 | // maintain order of members in updated relation
|
---|
| 262 | for (RelationMember oldMem :old.getMembers()) {
|
---|
| 263 | if (oldMem.isNode() || oldMem.isRelation()) {
|
---|
| 264 | nonWayMember++;
|
---|
| 265 | continue;
|
---|
| 266 | }
|
---|
| 267 | for (RelationMember newMem : calculated.getMembers()) {
|
---|
| 268 | if (newMem.getMember().equals(oldMem.getMember())) {
|
---|
| 269 | if (!newMem.getRole().equals(oldMem.getRole())) {
|
---|
| 270 | foundDiff = true;
|
---|
| 271 | }
|
---|
| 272 | foundDiff |= !merged.add(newMem); // detect duplicate members in old relation
|
---|
| 273 | break;
|
---|
| 274 | }
|
---|
| 275 | }
|
---|
| 276 | }
|
---|
| 277 | if (nonWayMember > 0) {
|
---|
| 278 | foundDiff = true;
|
---|
| 279 | String msg = trn("Non-Way member removed from multipolygon", "Non-Way members removed from multipolygon", nonWayMember);
|
---|
| 280 | GuiHelper.runInEDT(() -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
|
---|
| 281 | }
|
---|
| 282 | foundDiff |= merged.addAll(calculated.getMembers());
|
---|
| 283 | if (!foundDiff) {
|
---|
| 284 | return Pair.create(old, old); // unchanged
|
---|
| 285 | }
|
---|
| 286 | Relation toModify = new Relation(old);
|
---|
| 287 | toModify.setMembers(new ArrayList<>(merged));
|
---|
| 288 | return Pair.create(old, toModify);
|
---|
| 289 | }
|
---|
| 290 |
|
---|
| 291 | /**
|
---|
[6597] | 292 | * Returns a {@link Pair} null and the newly created/modified multipolygon {@link Relation}.
|
---|
[8795] | 293 | * @param selectedWays selected ways
|
---|
| 294 | * @param showNotif if {@code true}, shows a notification if an error occurs
|
---|
| 295 | * @return pair of null and new multipolygon relation
|
---|
[6597] | 296 | */
|
---|
[6622] | 297 | public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
|
---|
[15160] | 298 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 299 | Relation calculated = mpTest.makeFromWays(selectedWays);
|
---|
| 300 | calculated.setMembers(RelationSorter.sortMembersByConnectivity(calculated.getMembers()));
|
---|
| 301 | if (mpTest.getErrors().isEmpty())
|
---|
| 302 | return Pair.create(null, calculated);
|
---|
| 303 | if (showNotif) {
|
---|
| 304 | showErrors(mpTest.getErrors());
|
---|
| 305 | }
|
---|
[17219] | 306 | calculated.setMembers(null); // see #19885
|
---|
[15160] | 307 | return null; //could not make multipolygon.
|
---|
| 308 | }
|
---|
[6597] | 309 |
|
---|
[15160] | 310 | private static void showErrors(List<TestError> errors) {
|
---|
| 311 | if (!errors.isEmpty()) {
|
---|
[17003] | 312 | String errorMessages = errors.stream()
|
---|
| 313 | .map(TestError::getMessage)
|
---|
| 314 | .distinct()
|
---|
| 315 | .collect(Collectors.joining("\n"));
|
---|
| 316 | GuiHelper.runInEDT(() -> new Notification(errorMessages).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
|
---|
[3704] | 317 | }
|
---|
[6564] | 318 | }
|
---|
[3704] | 319 |
|
---|
[6564] | 320 | /**
|
---|
[6623] | 321 | * Returns a {@link Pair} of a multipolygon creating/modifying {@link Command} as well as the multipolygon {@link Relation}.
|
---|
[8795] | 322 | * @param selectedWays selected ways
|
---|
| 323 | * @param selectedMultipolygonRelation selected multipolygon relation
|
---|
| 324 | * @return pair of command and multipolygon relation
|
---|
[6564] | 325 | */
|
---|
[8540] | 326 | public static Pair<SequenceCommand, Relation> createMultipolygonCommand(Collection<Way> selectedWays,
|
---|
| 327 | Relation selectedMultipolygonRelation) {
|
---|
[3704] | 328 |
|
---|
[6597] | 329 | final Pair<Relation, Relation> rr = selectedMultipolygonRelation == null
|
---|
[6622] | 330 | ? createMultipolygonRelation(selectedWays, true)
|
---|
[6597] | 331 | : updateMultipolygonRelation(selectedWays, selectedMultipolygonRelation);
|
---|
[6564] | 332 | if (rr == null) {
|
---|
| 333 | return null;
|
---|
| 334 | }
|
---|
[15160] | 335 | boolean unchanged = rr.a == rr.b;
|
---|
[6564] | 336 | final Relation existingRelation = rr.a;
|
---|
| 337 | final Relation relation = rr.b;
|
---|
| 338 |
|
---|
| 339 | final List<Command> list = removeTagsFromWaysIfNeeded(relation);
|
---|
| 340 | final String commandName;
|
---|
| 341 | if (existingRelation == null) {
|
---|
[12726] | 342 | list.add(new AddCommand(selectedWays.iterator().next().getDataSet(), relation));
|
---|
[6597] | 343 | commandName = getName(false);
|
---|
[6564] | 344 | } else {
|
---|
[15160] | 345 | if (!unchanged) {
|
---|
| 346 | list.add(new ChangeCommand(existingRelation, relation));
|
---|
| 347 | }
|
---|
| 348 | if (list.isEmpty()) {
|
---|
| 349 | if (unchanged) {
|
---|
| 350 | MultipolygonTest mpTest = new MultipolygonTest();
|
---|
| 351 | mpTest.visit(existingRelation);
|
---|
| 352 | if (!mpTest.getErrors().isEmpty()) {
|
---|
| 353 | showErrors(mpTest.getErrors());
|
---|
| 354 | return null;
|
---|
| 355 | }
|
---|
| 356 | }
|
---|
| 357 |
|
---|
[15161] | 358 | GuiHelper.runInEDT(() -> new Notification(tr("Nothing changed")).setDuration(Notification.TIME_SHORT)
|
---|
| 359 | .setIcon(JOptionPane.INFORMATION_MESSAGE).show());
|
---|
[15160] | 360 | return null;
|
---|
| 361 | }
|
---|
[6597] | 362 | commandName = getName(true);
|
---|
[6564] | 363 | }
|
---|
| 364 | return Pair.create(new SequenceCommand(commandName, list), relation);
|
---|
[3704] | 365 | }
|
---|
| 366 |
|
---|
| 367 | /** Enable this action only if something is selected */
|
---|
[7423] | 368 | @Override
|
---|
| 369 | protected void updateEnabledState() {
|
---|
[10548] | 370 | updateEnabledStateOnCurrentSelection();
|
---|
[3704] | 371 | }
|
---|
| 372 |
|
---|
[6069] | 373 | /**
|
---|
[5818] | 374 | * Enable this action only if something is selected
|
---|
| 375 | *
|
---|
[15137] | 376 | * @param selection the current selection, gets tested for emptiness
|
---|
[5818] | 377 | */
|
---|
[7423] | 378 | @Override
|
---|
| 379 | protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
|
---|
[10382] | 380 | DataSet ds = getLayerManager().getEditDataSet();
|
---|
[15137] | 381 | if (ds == null || selection.isEmpty()) {
|
---|
[8385] | 382 | setEnabled(false);
|
---|
| 383 | } else if (update) {
|
---|
[15137] | 384 | setEnabled(getSelectedMultipolygonRelation(ds.getSelectedWays(), ds.getSelectedRelations()) != null);
|
---|
[6597] | 385 | } else {
|
---|
[15137] | 386 | setEnabled(!ds.getSelectedWays().isEmpty());
|
---|
[6597] | 387 | }
|
---|
[3704] | 388 | }
|
---|
| 389 |
|
---|
[8533] | 390 | private static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
|
---|
[5225] | 391 |
|
---|
[3704] | 392 | /**
|
---|
[5225] | 393 | * This method removes tags/value pairs from inner and outer ways and put them on relation if necessary
|
---|
[6069] | 394 | * Function was extended in reltoolbox plugin by Zverikk and copied back to the core
|
---|
[5818] | 395 | * @param relation the multipolygon style relation to process
|
---|
| 396 | * @return a list of commands to execute
|
---|
[3704] | 397 | */
|
---|
[7423] | 398 | public static List<Command> removeTagsFromWaysIfNeeded(Relation relation) {
|
---|
[7005] | 399 | Map<String, String> values = new HashMap<>(relation.getKeys());
|
---|
[3704] | 400 |
|
---|
[7005] | 401 | List<Way> innerWays = new ArrayList<>();
|
---|
| 402 | List<Way> outerWays = new ArrayList<>();
|
---|
[3704] | 403 |
|
---|
[7005] | 404 | Set<String> conflictingKeys = new TreeSet<>();
|
---|
[3704] | 405 |
|
---|
[8443] | 406 | for (RelationMember m : relation.getMembers()) {
|
---|
[3704] | 407 |
|
---|
[8443] | 408 | if (m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
|
---|
[5818] | 409 | innerWays.add(m.getWay());
|
---|
| 410 | }
|
---|
[3704] | 411 |
|
---|
[8443] | 412 | if (m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
|
---|
[5818] | 413 | Way way = m.getWay();
|
---|
| 414 | outerWays.add(way);
|
---|
[6069] | 415 |
|
---|
[7423] | 416 | for (String key : way.keySet()) {
|
---|
| 417 | if (!values.containsKey(key)) { //relation values take precedence
|
---|
[5818] | 418 | values.put(key, way.get(key));
|
---|
[7423] | 419 | } else if (!relation.hasKey(key) && !values.get(key).equals(way.get(key))) {
|
---|
[5818] | 420 | conflictingKeys.add(key);
|
---|
| 421 | }
|
---|
| 422 | }
|
---|
| 423 | }
|
---|
| 424 | }
|
---|
[3704] | 425 |
|
---|
[5818] | 426 | // filter out empty key conflicts - we need second iteration
|
---|
[12846] | 427 | if (!Config.getPref().getBoolean("multipoly.alltags", false)) {
|
---|
[8513] | 428 | for (RelationMember m : relation.getMembers()) {
|
---|
| 429 | if (m.hasRole() && "outer".equals(m.getRole()) && m.isWay()) {
|
---|
| 430 | for (String key : values.keySet()) {
|
---|
| 431 | if (!m.getWay().hasKey(key) && !relation.hasKey(key)) {
|
---|
[5818] | 432 | conflictingKeys.add(key);
|
---|
[8513] | 433 | }
|
---|
| 434 | }
|
---|
| 435 | }
|
---|
| 436 | }
|
---|
| 437 | }
|
---|
[3704] | 438 |
|
---|
[8513] | 439 | for (String key : conflictingKeys) {
|
---|
[5818] | 440 | values.remove(key);
|
---|
[8513] | 441 | }
|
---|
[3704] | 442 |
|
---|
[12846] | 443 | for (String linearTag : Config.getPref().getList("multipoly.lineartagstokeep", DEFAULT_LINEAR_TAGS)) {
|
---|
[5818] | 444 | values.remove(linearTag);
|
---|
[8513] | 445 | }
|
---|
[3704] | 446 |
|
---|
[6765] | 447 | if ("coastline".equals(values.get("natural")))
|
---|
[5818] | 448 | values.remove("natural");
|
---|
[5225] | 449 |
|
---|
[12188] | 450 | values.put("area", OsmUtils.TRUE_VALUE);
|
---|
[5225] | 451 |
|
---|
[7005] | 452 | List<Command> commands = new ArrayList<>();
|
---|
[12846] | 453 | boolean moveTags = Config.getPref().getBoolean("multipoly.movetags", true);
|
---|
[5225] | 454 |
|
---|
[6258] | 455 | for (Entry<String, String> entry : values.entrySet()) {
|
---|
| 456 | String key = entry.getKey();
|
---|
| 457 | String value = entry.getValue();
|
---|
[16438] | 458 | List<OsmPrimitive> affectedWays = innerWays.stream().filter(way -> value.equals(way.get(key))).collect(Collectors.toList());
|
---|
[5225] | 459 |
|
---|
[6258] | 460 | if (moveTags) {
|
---|
[5818] | 461 | // remove duplicated tags from outer ways
|
---|
[8443] | 462 | for (Way way : outerWays) {
|
---|
| 463 | if (way.hasKey(key)) {
|
---|
[5818] | 464 | affectedWays.add(way);
|
---|
| 465 | }
|
---|
| 466 | }
|
---|
| 467 | }
|
---|
[5225] | 468 |
|
---|
[6258] | 469 | if (!affectedWays.isEmpty()) {
|
---|
[5225] | 470 | // reset key tag on affected ways
|
---|
[5818] | 471 | commands.add(new ChangePropertyCommand(affectedWays, key, null));
|
---|
| 472 | }
|
---|
| 473 | }
|
---|
[5225] | 474 |
|
---|
[6258] | 475 | if (moveTags) {
|
---|
[5818] | 476 | // add those tag values to the relation
|
---|
| 477 | boolean fixed = false;
|
---|
| 478 | Relation r2 = new Relation(relation);
|
---|
[6258] | 479 | for (Entry<String, String> entry : values.entrySet()) {
|
---|
| 480 | String key = entry.getKey();
|
---|
[8443] | 481 | if (!r2.hasKey(key) && !"area".equals(key)) {
|
---|
[6258] | 482 | if (relation.isNew())
|
---|
| 483 | relation.put(key, entry.getValue());
|
---|
[5818] | 484 | else
|
---|
[6258] | 485 | r2.put(key, entry.getValue());
|
---|
[5818] | 486 | fixed = true;
|
---|
| 487 | }
|
---|
| 488 | }
|
---|
[13067] | 489 | if (fixed && !relation.isNew()) {
|
---|
| 490 | DataSet ds = relation.getDataSet();
|
---|
| 491 | if (ds == null) {
|
---|
| 492 | ds = MainApplication.getLayerManager().getEditDataSet();
|
---|
| 493 | }
|
---|
| 494 | commands.add(new ChangeCommand(ds, relation, r2));
|
---|
[17214] | 495 | } else {
|
---|
| 496 | r2.setMembers(null); // see #19885
|
---|
[13067] | 497 | }
|
---|
[5818] | 498 | }
|
---|
[5225] | 499 |
|
---|
[5818] | 500 | return commands;
|
---|
[3704] | 501 | }
|
---|
| 502 | }
|
---|