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

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

code cleanup / robustness in edit layer handling

  • Property svn:eol-style set to native
File size: 11.4 KB
Line 
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;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16import java.util.TreeSet;
17
18import javax.swing.JOptionPane;
19import javax.swing.SwingUtilities;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.command.AddCommand;
23import org.openstreetmap.josm.command.ChangeCommand;
24import org.openstreetmap.josm.command.ChangePropertyCommand;
25import org.openstreetmap.josm.command.Command;
26import org.openstreetmap.josm.command.SequenceCommand;
27import org.openstreetmap.josm.data.osm.MultipolygonCreate;
28import org.openstreetmap.josm.data.osm.MultipolygonCreate.JoinedPolygon;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.Relation;
31import org.openstreetmap.josm.data.osm.RelationMember;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.gui.Notification;
34import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
35import org.openstreetmap.josm.tools.Shortcut;
36
37/**
38 * Create multipolygon from selected ways automatically.
39 *
40 * New relation with type=multipolygon is created
41 *
42 * If one or more of ways is already in relation with type=multipolygon or the
43 * way is not closed, then error is reported and no relation is created
44 *
45 * The "inner" and "outer" roles are guessed automatically. First, bbox is
46 * calculated for each way. then the largest area is assumed to be outside and
47 * the rest inside. In cases with one "outside" area and several cut-ins, the
48 * guess should be always good ... In more complex (multiple outer areas) or
49 * buggy (inner and outer ways intersect) scenarios the result is likely to be
50 * wrong.
51 */
52public class CreateMultipolygonAction extends JosmAction {
53
54 /**
55 * Constructs a new {@code CreateMultipolygonAction}.
56 */
57 public CreateMultipolygonAction() {
58 super(tr("Create multipolygon"), "multipoly_create", tr("Create multipolygon."),
59 Shortcut.registerShortcut("tools:multipoly", tr("Tool: {0}", tr("Create multipolygon")),
60 KeyEvent.VK_A, Shortcut.ALT_CTRL), true);
61 }
62 /**
63 * The action button has been clicked
64 *
65 * @param e Action Event
66 */
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 if (!Main.main.hasEditLayer()) {
70 new Notification(
71 tr("No data loaded."))
72 .setIcon(JOptionPane.WARNING_MESSAGE)
73 .setDuration(Notification.TIME_SHORT)
74 .show();
75 return;
76 }
77
78 Collection<Way> selectedWays = Main.main.getCurrentDataSet().getSelectedWays();
79
80 if (selectedWays.size() < 1) {
81 // Sometimes it make sense creating multipoly of only one way (so it will form outer way)
82 // and then splitting the way later (so there are multiple ways forming outer way)
83 new Notification(
84 tr("You must select at least one way."))
85 .setIcon(JOptionPane.INFORMATION_MESSAGE)
86 .setDuration(Notification.TIME_SHORT)
87 .show();
88 return;
89 }
90
91 MultipolygonCreate polygon = this.analyzeWays(selectedWays);
92
93 if (polygon == null)
94 return; //could not make multipolygon.
95
96 final Relation relation = this.createRelation(polygon);
97
98 if (Main.pref.getBoolean("multipoly.show-relation-editor", false)) {
99 //Open relation edit window, if set up in preferences
100 RelationEditor editor = RelationEditor.getEditor(Main.main.getEditLayer(), relation, null);
101
102 editor.setModal(true);
103 editor.setVisible(true);
104
105 //TODO: cannot get the resulting relation from RelationEditor :(.
106 /*
107 if (relationCountBefore < relationCountAfter) {
108 //relation saved, clean up the tags
109 List<Command> list = this.removeTagsFromInnerWays(relation);
110 if (list.size() > 0)
111 {
112 Main.main.undoRedo.add(new SequenceCommand(tr("Remove tags from multipolygon inner ways"), list));
113 }
114 }
115 */
116
117 } else {
118 //Just add the relation
119 List<Command> list = this.removeTagsFromWaysIfNeeded(relation);
120 list.add(new AddCommand(relation));
121 Main.main.undoRedo.add(new SequenceCommand(tr("Create multipolygon"), list));
122 // Use 'SwingUtilities.invokeLater' to make sure the relationListDialog
123 // knows about the new relation before we try to select it.
124 // (Yes, we are already in event dispatch thread. But DatasetEventManager
125 // uses 'SwingUtilities.invokeLater' to fire events so we have to do
126 // the same.)
127 SwingUtilities.invokeLater(new Runnable() {
128 @Override
129 public void run() {
130 Main.map.relationListDialog.selectRelation(relation);
131 }
132 });
133 }
134
135
136 }
137
138 /** Enable this action only if something is selected */
139 @Override protected void updateEnabledState() {
140 if (getCurrentDataSet() == null) {
141 setEnabled(false);
142 } else {
143 updateEnabledState(getCurrentDataSet().getSelected());
144 }
145 }
146
147 /**
148 * Enable this action only if something is selected
149 *
150 * @param selection the current selection, gets tested for emptyness
151 */
152 @Override protected void updateEnabledState(Collection < ? extends OsmPrimitive > selection) {
153 setEnabled(selection != null && !selection.isEmpty());
154 }
155
156 /**
157 * This method analyzes ways and creates multipolygon.
158 * @param selectedWays list of selected ways
159 * @return <code>null</code>, if there was a problem with the ways.
160 */
161 private MultipolygonCreate analyzeWays(Collection < Way > selectedWays) {
162
163 MultipolygonCreate pol = new MultipolygonCreate();
164 String error = pol.makeFromWays(selectedWays);
165
166 if (error != null) {
167 new Notification(error)
168 .setIcon(JOptionPane.INFORMATION_MESSAGE)
169 .show();
170 return null;
171 } else {
172 return pol;
173 }
174 }
175
176 /**
177 * Builds a relation from polygon ways.
178 * @param pol data storage class containing polygon information
179 * @return multipolygon relation
180 */
181 private Relation createRelation(MultipolygonCreate pol) {
182 // Create new relation
183 Relation rel = new Relation();
184 rel.put("type", "multipolygon");
185 // Add ways to it
186 for (JoinedPolygon jway:pol.outerWays) {
187 for (Way way:jway.ways) {
188 rel.addMember(new RelationMember("outer", way));
189 }
190 }
191
192 for (JoinedPolygon jway:pol.innerWays) {
193 for (Way way:jway.ways) {
194 rel.addMember(new RelationMember("inner", way));
195 }
196 }
197 return rel;
198 }
199
200 static public final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList(new String[] {"barrier", "source"});
201
202 /**
203 * This method removes tags/value pairs from inner and outer ways and put them on relation if necessary
204 * Function was extended in reltoolbox plugin by Zverikk and copied back to the core
205 * @param relation the multipolygon style relation to process
206 * @return a list of commands to execute
207 */
208 private List<Command> removeTagsFromWaysIfNeeded( Relation relation ) {
209 Map<String, String> values = new HashMap<String, String>();
210
211 if( relation.hasKeys() ) {
212 for( String key : relation.keySet() ) {
213 values.put(key, relation.get(key));
214 }
215 }
216
217 List<Way> innerWays = new ArrayList<Way>();
218 List<Way> outerWays = new ArrayList<Way>();
219
220 Set<String> conflictingKeys = new TreeSet<String>();
221
222 for( RelationMember m : relation.getMembers() ) {
223
224 if( m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) {
225 innerWays.add(m.getWay());
226 }
227
228 if( m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys() ) {
229 Way way = m.getWay();
230 outerWays.add(way);
231
232 for( String key : way.keySet() ) {
233 if( !values.containsKey(key) ) { //relation values take precedence
234 values.put(key, way.get(key));
235 } else if( !relation.hasKey(key) && !values.get(key).equals(way.get(key)) ) {
236 conflictingKeys.add(key);
237 }
238 }
239 }
240 }
241
242 // filter out empty key conflicts - we need second iteration
243 if( !Main.pref.getBoolean("multipoly.alltags", false) )
244 for( RelationMember m : relation.getMembers() )
245 if( m.hasRole() && m.getRole().equals("outer") && m.isWay() )
246 for( String key : values.keySet() )
247 if( !m.getWay().hasKey(key) && !relation.hasKey(key) )
248 conflictingKeys.add(key);
249
250 for( String key : conflictingKeys )
251 values.remove(key);
252
253 for( String linearTag : Main.pref.getCollection("multipoly.lineartagstokeep", DEFAULT_LINEAR_TAGS) )
254 values.remove(linearTag);
255
256 if( values.containsKey("natural") && values.get("natural").equals("coastline") )
257 values.remove("natural");
258
259 values.put("area", "yes");
260
261 List<Command> commands = new ArrayList<Command>();
262 boolean moveTags = Main.pref.getBoolean("multipoly.movetags", true);
263
264 for (Entry<String, String> entry : values.entrySet()) {
265 List<OsmPrimitive> affectedWays = new ArrayList<OsmPrimitive>();
266 String key = entry.getKey();
267 String value = entry.getValue();
268
269 for (Way way : innerWays) {
270 if (way.hasKey(key) && (value.equals(way.get(key)))) {
271 affectedWays.add(way);
272 }
273 }
274
275 if (moveTags) {
276 // remove duplicated tags from outer ways
277 for( Way way : outerWays ) {
278 if( way.hasKey(key) ) {
279 affectedWays.add(way);
280 }
281 }
282 }
283
284 if (!affectedWays.isEmpty()) {
285 // reset key tag on affected ways
286 commands.add(new ChangePropertyCommand(affectedWays, key, null));
287 }
288 }
289
290 if (moveTags) {
291 // add those tag values to the relation
292
293 boolean fixed = false;
294 Relation r2 = new Relation(relation);
295 for (Entry<String, String> entry : values.entrySet()) {
296 String key = entry.getKey();
297 if (!r2.hasKey(key) && !key.equals("area") ) {
298 if (relation.isNew())
299 relation.put(key, entry.getValue());
300 else
301 r2.put(key, entry.getValue());
302 fixed = true;
303 }
304 }
305 if (fixed && !relation.isNew())
306 commands.add(new ChangeCommand(relation, r2));
307 }
308
309 return commands;
310 }
311}
Note: See TracBrowser for help on using the repository browser.