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

Last change on this file since 6227 was 6130, checked in by bastiK, 11 years ago

see #6963 - converted popups to notifications for all actions in the Tools menu (TODO: Simplify Way)

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