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

Revision 5225, 9.7 KB checked in by akks, 12 days ago (diff)

see #7458 : enable tags moving when creating multipolygon [improved code from Zverik's Relation Toolbox plugin copied back to core ]

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