source: josm/trunk/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java@ 7206

Last change on this file since 7206 was 7012, checked in by Don-vip, 10 years ago

see #8465 - use String switch/case where applicable

  • Property svn:eol-style set to native
File size: 16.8 KB
RevLine 
[3704]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.util.ArrayList;
[5225]9import java.util.Arrays;
[3704]10import java.util.Collection;
[6600]11import java.util.Collections;
[3704]12import java.util.HashMap;
[6564]13import java.util.HashSet;
[3704]14import java.util.List;
15import java.util.Map;
[6258]16import java.util.Map.Entry;
[5225]17import java.util.Set;
18import java.util.TreeSet;
[6258]19
[3704]20import javax.swing.JOptionPane;
[6258]21import javax.swing.SwingUtilities;
[3704]22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
[5225]25import org.openstreetmap.josm.command.ChangeCommand;
[3704]26import org.openstreetmap.josm.command.ChangePropertyCommand;
27import org.openstreetmap.josm.command.Command;
28import org.openstreetmap.josm.command.SequenceCommand;
29import org.openstreetmap.josm.data.osm.MultipolygonCreate;
30import org.openstreetmap.josm.data.osm.MultipolygonCreate.JoinedPolygon;
31import org.openstreetmap.josm.data.osm.OsmPrimitive;
32import org.openstreetmap.josm.data.osm.Relation;
33import org.openstreetmap.josm.data.osm.RelationMember;
34import org.openstreetmap.josm.data.osm.Way;
[6130]35import org.openstreetmap.josm.gui.Notification;
[6600]36import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationTask;
[3704]37import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
[6564]38import org.openstreetmap.josm.tools.Pair;
[3704]39import org.openstreetmap.josm.tools.Shortcut;
[6597]40import org.openstreetmap.josm.tools.Utils;
[3704]41
42/**
43 * Create multipolygon from selected ways automatically.
44 *
45 * New relation with type=multipolygon is created
46 *
47 * If one or more of ways is already in relation with type=multipolygon or the
48 * way is not closed, then error is reported and no relation is created
49 *
50 * The "inner" and "outer" roles are guessed automatically. First, bbox is
51 * calculated for each way. then the largest area is assumed to be outside and
52 * the rest inside. In cases with one "outside" area and several cut-ins, the
53 * guess should be always good ... In more complex (multiple outer areas) or
54 * buggy (inner and outer ways intersect) scenarios the result is likely to be
55 * wrong.
56 */
57public class CreateMultipolygonAction extends JosmAction {
58
[6597]59 private final boolean update;
60
[6258]61 /**
62 * Constructs a new {@code CreateMultipolygonAction}.
[6623]63 * @param update {@code true} if the multipolygon must be updated, {@code false} if it must be created
[6258]64 */
[6597]65 public CreateMultipolygonAction(final boolean update) {
66 super(getName(update), "multipoly_create", getName(update),
[6721]67 update ? Shortcut.registerShortcut("tools:multipoly_update", tr("Tool: {0}", getName(true)), KeyEvent.VK_B, Shortcut.CTRL_SHIFT)
68 : Shortcut.registerShortcut("tools:multipoly_create", tr("Tool: {0}", getName(false)), KeyEvent.VK_B, Shortcut.CTRL),
[6597]69 true, update ? "multipoly_update" : "multipoly_create", true);
70 this.update = update;
[3704]71 }
[6597]72
73 private static String getName(boolean update) {
74 return update ? tr("Update multipolygon") : tr("Create multipolygon");
75 }
[6792]76
77 private static class CreateUpdateMultipolygonTask implements Runnable {
[6623]78 private final Collection<Way> selectedWays;
79 private final Relation multipolygonRelation;
[6597]80
[6623]81 public CreateUpdateMultipolygonTask(Collection<Way> selectedWays, Relation multipolygonRelation) {
82 this.selectedWays = selectedWays;
83 this.multipolygonRelation = multipolygonRelation;
84 }
85
86 @Override
87 public void run() {
88 final Pair<SequenceCommand, Relation> commandAndRelation = createMultipolygonCommand(selectedWays, multipolygonRelation);
89 if (commandAndRelation == null) {
90 return;
91 }
92 final Command command = commandAndRelation.a;
93 final Relation relation = commandAndRelation.b;
94
95
96 // to avoid EDT violations
97 SwingUtilities.invokeLater(new Runnable() {
98 @Override
99 public void run() {
100 Main.main.undoRedo.add(command);
101
[6807]102 // Use 'SwingUtilities.invokeLater' to make sure the relationListDialog
103 // knows about the new relation before we try to select it.
104 // (Yes, we are already in event dispatch thread. But DatasetEventManager
105 // uses 'SwingUtilities.invokeLater' to fire events so we have to do
106 // the same.)
107 SwingUtilities.invokeLater(new Runnable() {
108 @Override
109 public void run() {
110 Main.map.relationListDialog.selectRelation(relation);
111 if (Main.pref.getBoolean("multipoly.show-relation-editor", false)) {
112 //Open relation edit window, if set up in preferences
113 RelationEditor editor = RelationEditor.getEditor(Main.main.getEditLayer(), relation, null);
[6623]114
[6807]115 editor.setModal(true);
116 editor.setVisible(true);
117 }
118 }
119 });
[6623]120 }
121 });
122 }
123 }
124
[3704]125 /**
126 * The action button has been clicked
127 *
128 * @param e Action Event
129 */
[6084]130 @Override
[3704]131 public void actionPerformed(ActionEvent e) {
[6336]132 if (!Main.main.hasEditLayer()) {
[6130]133 new Notification(
134 tr("No data loaded."))
135 .setIcon(JOptionPane.WARNING_MESSAGE)
136 .setDuration(Notification.TIME_SHORT)
137 .show();
[3704]138 return;
139 }
140
[6564]141 final Collection<Way> selectedWays = Main.main.getCurrentDataSet().getSelectedWays();
142 final Collection<Relation> selectedRelations = Main.main.getCurrentDataSet().getSelectedRelations();
[3704]143
144 if (selectedWays.size() < 1) {
145 // Sometimes it make sense creating multipoly of only one way (so it will form outer way)
146 // and then splitting the way later (so there are multiple ways forming outer way)
[6130]147 new Notification(
148 tr("You must select at least one way."))
149 .setIcon(JOptionPane.INFORMATION_MESSAGE)
150 .setDuration(Notification.TIME_SHORT)
151 .show();
[3704]152 return;
153 }
154
[6597]155 final Relation multipolygonRelation = update
156 ? getSelectedMultipolygonRelation(selectedWays, selectedRelations)
157 : null;
[6569]158
[6600]159 // download incomplete relation if necessary
160 if (multipolygonRelation != null && (multipolygonRelation.isIncomplete() || multipolygonRelation.hasIncompleteMembers())) {
161 Main.worker.submit(new DownloadRelationTask(Collections.singleton(multipolygonRelation), Main.main.getEditLayer()));
162 }
163 // create/update multipolygon relation
[6623]164 Main.worker.submit(new CreateUpdateMultipolygonTask(selectedWays, multipolygonRelation));
[6600]165
[6564]166 }
[3704]167
[6597]168 private Relation getSelectedMultipolygonRelation() {
169 return getSelectedMultipolygonRelation(getCurrentDataSet().getSelectedWays(), getCurrentDataSet().getSelectedRelations());
[6564]170 }
[3704]171
[6597]172 private static Relation getSelectedMultipolygonRelation(Collection<Way> selectedWays, Collection<Relation> selectedRelations) {
173 if (selectedRelations.size() == 1 && "multipolygon".equals(selectedRelations.iterator().next().get("type"))) {
174 return selectedRelations.iterator().next();
175 } else {
[7005]176 final HashSet<Relation> relatedRelations = new HashSet<>();
[6597]177 for (final Way w : selectedWays) {
178 relatedRelations.addAll(Utils.filteredCollection(w.getReferrers(), Relation.class));
179 }
180 return relatedRelations.size() == 1 ? relatedRelations.iterator().next() : null;
181 }
182 }
183
[6564]184 /**
185 * Returns a {@link Pair} of the old multipolygon {@link Relation} (or null) and the newly created/modified multipolygon {@link Relation}.
186 */
[6597]187 public static Pair<Relation, Relation> updateMultipolygonRelation(Collection<Way> selectedWays, Relation selectedMultipolygonRelation) {
[3704]188
[6597]189 // add ways of existing relation to include them in polygon analysis
[7005]190 Set<Way> ways = new HashSet<>(selectedWays);
[6623]191 ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
[6564]192
[6623]193 final MultipolygonCreate polygon = analyzeWays(ways, true);
[6564]194 if (polygon == null) {
195 return null; //could not make multipolygon.
[6597]196 } else {
197 return Pair.create(selectedMultipolygonRelation, createRelation(polygon, new Relation(selectedMultipolygonRelation)));
[6564]198 }
[6597]199 }
[6564]200
[6597]201 /**
202 * Returns a {@link Pair} null and the newly created/modified multipolygon {@link Relation}.
203 */
[6622]204 public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
[6597]205
[6622]206 final MultipolygonCreate polygon = analyzeWays(selectedWays, showNotif);
[6597]207 if (polygon == null) {
208 return null; //could not make multipolygon.
[3704]209 } else {
[6564]210 return Pair.create(null, createRelation(polygon, new Relation()));
[3704]211 }
[6564]212 }
[3704]213
[6564]214 /**
[6623]215 * Returns a {@link Pair} of a multipolygon creating/modifying {@link Command} as well as the multipolygon {@link Relation}.
[6564]216 */
[6597]217 public static Pair<SequenceCommand, Relation> createMultipolygonCommand(Collection<Way> selectedWays, Relation selectedMultipolygonRelation) {
[3704]218
[6597]219 final Pair<Relation, Relation> rr = selectedMultipolygonRelation == null
[6622]220 ? createMultipolygonRelation(selectedWays, true)
[6597]221 : updateMultipolygonRelation(selectedWays, selectedMultipolygonRelation);
[6564]222 if (rr == null) {
223 return null;
224 }
225 final Relation existingRelation = rr.a;
226 final Relation relation = rr.b;
227
228 final List<Command> list = removeTagsFromWaysIfNeeded(relation);
229 final String commandName;
230 if (existingRelation == null) {
231 list.add(new AddCommand(relation));
[6597]232 commandName = getName(false);
[6564]233 } else {
234 list.add(new ChangeCommand(existingRelation, relation));
[6597]235 commandName = getName(true);
[6564]236 }
237 return Pair.create(new SequenceCommand(commandName, list), relation);
[3704]238 }
239
240 /** Enable this action only if something is selected */
241 @Override protected void updateEnabledState() {
242 if (getCurrentDataSet() == null) {
243 setEnabled(false);
244 } else {
245 updateEnabledState(getCurrentDataSet().getSelected());
246 }
247 }
248
[6069]249 /**
[5818]250 * Enable this action only if something is selected
251 *
252 * @param selection the current selection, gets tested for emptyness
253 */
[3704]254 @Override protected void updateEnabledState(Collection < ? extends OsmPrimitive > selection) {
[6597]255 if (update) {
256 setEnabled(getSelectedMultipolygonRelation() != null);
257 } else {
258 setEnabled(!getCurrentDataSet().getSelectedWays().isEmpty());
259 }
[3704]260 }
261
262 /**
263 * This method analyzes ways and creates multipolygon.
[5818]264 * @param selectedWays list of selected ways
265 * @return <code>null</code>, if there was a problem with the ways.
[3704]266 */
[6622]267 private static MultipolygonCreate analyzeWays(Collection < Way > selectedWays, boolean showNotif) {
[3704]268
269 MultipolygonCreate pol = new MultipolygonCreate();
270 String error = pol.makeFromWays(selectedWays);
271
272 if (error != null) {
[6622]273 if (showNotif) {
274 new Notification(error)
275 .setIcon(JOptionPane.INFORMATION_MESSAGE)
276 .show();
277 }
[3704]278 return null;
279 } else {
280 return pol;
281 }
282 }
283
284 /**
285 * Builds a relation from polygon ways.
[5818]286 * @param pol data storage class containing polygon information
287 * @return multipolygon relation
[3704]288 */
[6564]289 private static Relation createRelation(MultipolygonCreate pol, final Relation rel) {
[3704]290 // Create new relation
291 rel.put("type", "multipolygon");
292 // Add ways to it
293 for (JoinedPolygon jway:pol.outerWays) {
[6564]294 addMembers(jway, rel, "outer");
[3704]295 }
296
297 for (JoinedPolygon jway:pol.innerWays) {
[6564]298 addMembers(jway, rel, "inner");
[3704]299 }
300 return rel;
301 }
302
[6564]303 private static void addMembers(JoinedPolygon polygon, Relation rel, String role) {
304 final int count = rel.getMembersCount();
[7005]305 final HashSet<Way> ways = new HashSet<>(polygon.ways);
[6564]306 for (int i = 0; i < count; i++) {
307 final RelationMember m = rel.getMember(i);
308 if (ways.contains(m.getMember()) && !role.equals(m.getRole())) {
309 rel.setMember(i, new RelationMember(role, m.getMember()));
310 }
311 }
312 ways.removeAll(rel.getMemberPrimitives());
313 for (final Way way : ways) {
314 rel.addMember(new RelationMember(role, way));
315 }
316 }
317
[6765]318 public static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
[5225]319
[3704]320 /**
[5225]321 * This method removes tags/value pairs from inner and outer ways and put them on relation if necessary
[6069]322 * Function was extended in reltoolbox plugin by Zverikk and copied back to the core
[5818]323 * @param relation the multipolygon style relation to process
324 * @return a list of commands to execute
[3704]325 */
[6913]326 public static List<Command> removeTagsFromWaysIfNeeded( Relation relation ) {
[7005]327 Map<String, String> values = new HashMap<>(relation.getKeys());
[3704]328
[7005]329 List<Way> innerWays = new ArrayList<>();
330 List<Way> outerWays = new ArrayList<>();
[3704]331
[7005]332 Set<String> conflictingKeys = new TreeSet<>();
[3704]333
[5818]334 for( RelationMember m : relation.getMembers() ) {
[3704]335
[5818]336 if( m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) {
337 innerWays.add(m.getWay());
338 }
[3704]339
[5818]340 if( m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) {
341 Way way = m.getWay();
342 outerWays.add(way);
[6069]343
[5818]344 for( String key : way.keySet() ) {
345 if( !values.containsKey(key) ) { //relation values take precedence
346 values.put(key, way.get(key));
347 } else if( !relation.hasKey(key) && !values.get(key).equals(way.get(key)) ) {
348 conflictingKeys.add(key);
349 }
350 }
351 }
352 }
[3704]353
[5818]354 // filter out empty key conflicts - we need second iteration
355 if( !Main.pref.getBoolean("multipoly.alltags", false) )
356 for( RelationMember m : relation.getMembers() )
[7012]357 if( m.hasRole() && "outer".equals(m.getRole()) && m.isWay() )
[5818]358 for( String key : values.keySet() )
359 if( !m.getWay().hasKey(key) && !relation.hasKey(key) )
360 conflictingKeys.add(key);
[3704]361
[5818]362 for( String key : conflictingKeys )
363 values.remove(key);
[3704]364
[5818]365 for( String linearTag : Main.pref.getCollection("multipoly.lineartagstokeep", DEFAULT_LINEAR_TAGS) )
366 values.remove(linearTag);
[3704]367
[6765]368 if ("coastline".equals(values.get("natural")))
[5818]369 values.remove("natural");
[5225]370
[5818]371 values.put("area", "yes");
[5225]372
[7005]373 List<Command> commands = new ArrayList<>();
[5818]374 boolean moveTags = Main.pref.getBoolean("multipoly.movetags", true);
[5225]375
[6258]376 for (Entry<String, String> entry : values.entrySet()) {
[7005]377 List<OsmPrimitive> affectedWays = new ArrayList<>();
[6258]378 String key = entry.getKey();
379 String value = entry.getValue();
[5225]380
[6258]381 for (Way way : innerWays) {
[6765]382 if (value.equals(way.get(key))) {
[5818]383 affectedWays.add(way);
384 }
385 }
[5225]386
[6258]387 if (moveTags) {
[5818]388 // remove duplicated tags from outer ways
389 for( Way way : outerWays ) {
390 if( way.hasKey(key) ) {
391 affectedWays.add(way);
392 }
393 }
394 }
[5225]395
[6258]396 if (!affectedWays.isEmpty()) {
[5225]397 // reset key tag on affected ways
[5818]398 commands.add(new ChangePropertyCommand(affectedWays, key, null));
399 }
400 }
[5225]401
[6258]402 if (moveTags) {
[5818]403 // add those tag values to the relation
[5225]404
[5818]405 boolean fixed = false;
406 Relation r2 = new Relation(relation);
[6258]407 for (Entry<String, String> entry : values.entrySet()) {
408 String key = entry.getKey();
[7012]409 if (!r2.hasKey(key) && !"area".equals(key) ) {
[6258]410 if (relation.isNew())
411 relation.put(key, entry.getValue());
[5818]412 else
[6258]413 r2.put(key, entry.getValue());
[5818]414 fixed = true;
415 }
416 }
[6258]417 if (fixed && !relation.isNew())
[5818]418 commands.add(new ChangeCommand(relation, r2));
419 }
[5225]420
[5818]421 return commands;
[3704]422 }
423}
Note: See TracBrowser for help on using the repository browser.