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

Last change on this file since 6099 was 6093, checked in by akks, 11 years ago

see #8902 - collection size ==/!= 0 -> isEmpty()/!isEmpty() (patch by shinigami)

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