source: subversion/applications/editors/josm/plugins/reltoolbox/src/relcontext/actions/CreateMultipolygonAction.java

Last change on this file was 34551, checked in by donvip, 8 months ago

update to JOSM 14153

File size: 17.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package relcontext.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Dialog.ModalityType;
7import java.awt.GridBagLayout;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
10import java.awt.event.KeyEvent;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.TreeSet;
20
21import javax.swing.Box;
22import javax.swing.JDialog;
23import javax.swing.JLabel;
24import javax.swing.JOptionPane;
25import javax.swing.JPanel;
26import javax.swing.JTextField;
27
28import org.openstreetmap.josm.actions.JosmAction;
29import org.openstreetmap.josm.command.AddCommand;
30import org.openstreetmap.josm.command.ChangeCommand;
31import org.openstreetmap.josm.command.ChangePropertyCommand;
32import org.openstreetmap.josm.command.Command;
33import org.openstreetmap.josm.command.SequenceCommand;
34import org.openstreetmap.josm.data.UndoRedoHandler;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
37import org.openstreetmap.josm.data.osm.Node;
38import org.openstreetmap.josm.data.osm.OsmPrimitive;
39import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
40import org.openstreetmap.josm.data.osm.Relation;
41import org.openstreetmap.josm.data.osm.RelationMember;
42import org.openstreetmap.josm.data.osm.Way;
43import org.openstreetmap.josm.gui.MainApplication;
44import org.openstreetmap.josm.spi.preferences.Config;
45import org.openstreetmap.josm.tools.GBC;
46import org.openstreetmap.josm.tools.Shortcut;
47
48import relcontext.ChosenRelation;
49
50/**
51 * Creates new multipolygon from selected ways.
52 * Choose relation afterwards.
53 *
54 * @author Zverik
55 */
56public class CreateMultipolygonAction extends JosmAction {
57    private static final String PREF_MULTIPOLY = "reltoolbox.multipolygon.";
58    protected ChosenRelation chRel;
59
60    public CreateMultipolygonAction(ChosenRelation chRel) {
61        super("Multi", "data/multipolygon", tr("Create a multipolygon from selected objects"),
62                Shortcut.registerShortcut("reltoolbox:multipolygon", tr("Relation Toolbox: {0}", tr("Create multipolygon")),
63                        KeyEvent.VK_A, Shortcut.ALT_CTRL), false);
64        this.chRel = chRel;
65        updateEnabledState();
66    }
67
68    public CreateMultipolygonAction() {
69        this(null);
70    }
71
72    public static boolean getDefaultPropertyValue(String property) {
73        if (property.equals("boundary"))
74            return false;
75        else if (property.equals("boundaryways"))
76            return true;
77        else if (property.equals("tags"))
78            return true;
79        else if (property.equals("alltags"))
80            return false;
81        else if (property.equals("single"))
82            return true;
83        else if (property.equals("allowsplit"))
84            return false;
85        throw new IllegalArgumentException(property);
86    }
87
88    private boolean getPref(String property) {
89        return Config.getPref().getBoolean(PREF_MULTIPOLY + property, getDefaultPropertyValue(property));
90    }
91
92    @Override
93    public void actionPerformed(ActionEvent e) {
94        boolean isBoundary = getPref("boundary");
95        DataSet ds = getLayerManager().getEditDataSet();
96        Collection<Way> selectedWays = ds.getSelectedWays();
97        if (!isBoundary && getPref("tags")) {
98            List<Relation> rels = null;
99            if (getPref("allowsplit") || selectedWays.size() == 1) {
100                if (SplittingMultipolygons.canProcess(selectedWays)) {
101                    rels = SplittingMultipolygons.process(ds.getSelectedWays());
102                }
103            } else {
104                if (TheRing.areAllOfThoseRings(selectedWays)) {
105                    List<Command> commands = new ArrayList<>();
106                    rels = TheRing.makeManySimpleMultipolygons(ds.getSelectedWays(), commands);
107                    if (!commands.isEmpty()) {
108                        UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Create multipolygons from rings"), commands));
109                    }
110                }
111            }
112            if (rels != null && !rels.isEmpty()) {
113                if (chRel != null) {
114                    chRel.set(rels.size() == 1 ? rels.get(0) : null);
115                }
116                if (rels.size() == 1) {
117                    ds.setSelected(rels);
118                } else {
119                    ds.clearSelection();
120                }
121                return;
122            }
123        }
124
125        // for now, just copying standard action
126        MultipolygonBuilder mpc = new MultipolygonBuilder();
127        String error = mpc.makeFromWays(ds.getSelectedWays());
128        if (error != null) {
129            JOptionPane.showMessageDialog(MainApplication.getMainFrame(), error);
130            return;
131        }
132        Relation rel = new Relation();
133        if (isBoundary) {
134            rel.put("type", "boundary");
135            rel.put("boundary", "administrative");
136        } else {
137            rel.put("type", "multipolygon");
138        }
139        for (MultipolygonBuilder.JoinedPolygon poly : mpc.outerWays) {
140            for (Way w : poly.ways) {
141                rel.addMember(new RelationMember("outer", w));
142            }
143        }
144        for (MultipolygonBuilder.JoinedPolygon poly : mpc.innerWays) {
145            for (Way w : poly.ways) {
146                rel.addMember(new RelationMember("inner", w));
147            }
148        }
149        List<Command> list = removeTagsFromInnerWays(rel);
150        if (!list.isEmpty() && isBoundary) {
151            UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Move tags from ways to relation"), list));
152            list = new ArrayList<>();
153        }
154        if (isBoundary) {
155            if (!askForAdminLevelAndName(rel))
156                return;
157            addBoundaryMembers(rel);
158            if (getPref("boundaryways")) {
159                list.addAll(fixWayTagsForBoundary(rel));
160            }
161        }
162        list.add(new AddCommand(ds, rel));
163        UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Create multipolygon"), list));
164
165        if (chRel != null) {
166            chRel.set(rel);
167        }
168
169        ds.setSelected(rel);
170    }
171
172    @Override
173    protected void updateEnabledState() {
174        if (getLayerManager().getEditDataSet() == null) {
175            setEnabled(false);
176        } else {
177            updateEnabledState(getLayerManager().getEditDataSet().getSelected());
178        }
179    }
180
181    @Override
182    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
183        boolean isEnabled = true;
184        if (selection == null || selection.isEmpty()) {
185            isEnabled = false;
186        } else {
187            if (!getPref("boundary")) {
188                for (OsmPrimitive p : selection) {
189                    if (!(p instanceof Way)) {
190                        isEnabled = false;
191                        break;
192                    }
193                }
194            }
195        }
196        setEnabled(isEnabled);
197    }
198
199    /**
200     * Add selected nodes and relations with corresponding roles.
201     */
202    private void addBoundaryMembers(Relation rel) {
203        for (OsmPrimitive p : getLayerManager().getEditDataSet().getSelected()) {
204            String role = null;
205            if (p.getType().equals(OsmPrimitiveType.RELATION)) {
206                role = "subarea";
207            } else if (p.getType().equals(OsmPrimitiveType.NODE)) {
208                Node n = (Node) p;
209                if (!n.isIncomplete()) {
210                    if (n.hasKey("place")) {
211                        role = "admin_centre";
212                    } else {
213                        role = "label";
214                    }
215                }
216            }
217            if (role != null) {
218                rel.addMember(new RelationMember(role, p));
219            }
220        }
221    }
222
223    /**
224     * For all untagged ways in relation, add tags boundary and admin_level.
225     */
226    private List<Command> fixWayTagsForBoundary(Relation rel) {
227        List<Command> commands = new ArrayList<>();
228        if (!rel.hasKey("boundary") || !rel.hasKey("admin_level"))
229            return commands;
230        String adminLevelStr = rel.get("admin_level");
231        int adminLevel = 0;
232        try {
233            adminLevel = Integer.parseInt(adminLevelStr);
234        } catch (NumberFormatException e) {
235            return commands;
236        }
237        Set<OsmPrimitive> waysBoundary = new HashSet<>();
238        Set<OsmPrimitive> waysAdminLevel = new HashSet<>();
239        for (OsmPrimitive p : rel.getMemberPrimitives()) {
240            if (p instanceof Way) {
241                int count = 0;
242                if (p.hasKey("boundary") && p.get("boundary").equals("administrative")) {
243                    count++;
244                }
245                if (p.hasKey("admin_level")) {
246                    count++;
247                }
248                if (p.keySet().size() - count == 0) {
249                    if (!p.hasKey("boundary")) {
250                        waysBoundary.add(p);
251                    }
252                    if (!p.hasKey("admin_level")) {
253                        waysAdminLevel.add(p);
254                    } else {
255                        try {
256                            int oldAdminLevel = Integer.parseInt(p.get("admin_level"));
257                            if (oldAdminLevel > adminLevel) {
258                                waysAdminLevel.add(p);
259                            }
260                        } catch (NumberFormatException e) {
261                            waysAdminLevel.add(p); // some garbage, replace it
262                        }
263                    }
264                }
265            }
266        }
267        if (!waysBoundary.isEmpty()) {
268            commands.add(new ChangePropertyCommand(waysBoundary, "boundary", "administrative"));
269        }
270        if (!waysAdminLevel.isEmpty()) {
271            commands.add(new ChangePropertyCommand(waysAdminLevel, "admin_level", adminLevelStr));
272        }
273        return commands;
274    }
275
276    public static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList(new String[] {"barrier", "source"});
277
278    private static final Set<String> REMOVE_FROM_BOUNDARY_TAGS = new TreeSet<>(Arrays.asList(new String[] {
279            "boundary", "boundary_type", "type", "admin_level"
280    }));
281
282    /**
283     * This method removes tags/value pairs from inner ways that are present in relation or outer ways.
284     * It was copypasted from the standard {@link org.openstreetmap.josm.actions.CreateMultipolygonAction}.
285     * Todo: rewrite it.
286     */
287    private List<Command> removeTagsFromInnerWays(Relation relation) {
288        Map<String, String> values = new HashMap<>();
289
290        if (relation.hasKeys()) {
291            for (String key : relation.keySet()) {
292                values.put(key, relation.get(key));
293            }
294        }
295
296        List<Way> innerWays = new ArrayList<>();
297        List<Way> outerWays = new ArrayList<>();
298
299        Set<String> conflictingKeys = new TreeSet<>();
300
301        for (RelationMember m : relation.getMembers()) {
302
303            if (m.hasRole() && "inner".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
304                innerWays.add(m.getWay());
305            }
306
307            if (m.hasRole() && "outer".equals(m.getRole()) && m.isWay() && m.getWay().hasKeys()) {
308                Way way = m.getWay();
309                outerWays.add(way);
310                for (String key : way.keySet()) {
311                    if (!values.containsKey(key)) { //relation values take precedence
312                        values.put(key, way.get(key));
313                    } else if (!relation.hasKey(key) && !values.get(key).equals(way.get(key))) {
314                        conflictingKeys.add(key);
315                    }
316                }
317            }
318        }
319
320        // filter out empty key conflicts - we need second iteration
321        boolean isBoundary = getPref("boundary");
322        if (isBoundary || !getPref("alltags")) {
323            for (RelationMember m : relation.getMembers()) {
324                if (m.hasRole() && m.getRole().equals("outer") && m.isWay()) {
325                    for (String key : values.keySet()) {
326                        if (!m.getWay().hasKey(key) && !relation.hasKey(key)) {
327                            conflictingKeys.add(key);
328                        }
329                    }
330                }
331            }
332        }
333
334        for (String key : conflictingKeys) {
335            values.remove(key);
336        }
337
338        for (String linearTag : Config.getPref().getList(PREF_MULTIPOLY + "lineartags", DEFAULT_LINEAR_TAGS)) {
339            values.remove(linearTag);
340        }
341
342        if (values.containsKey("natural") && values.get("natural").equals("coastline")) {
343            values.remove("natural");
344        }
345
346        String name = values.get("name");
347        if (isBoundary) {
348            Set<String> keySet = new TreeSet<>(values.keySet());
349            for (String key : keySet) {
350                if (!REMOVE_FROM_BOUNDARY_TAGS.contains(key)) {
351                    values.remove(key);
352                }
353            }
354        }
355
356        values.put("area", "yes");
357
358        List<Command> commands = new ArrayList<>();
359        boolean moveTags = getPref("tags");
360
361        for (String key : values.keySet()) {
362            List<OsmPrimitive> affectedWays = new ArrayList<>();
363            String value = values.get(key);
364
365            for (Way way : innerWays) {
366                if (way.hasKey(key) && (isBoundary || value.equals(way.get(key)))) {
367                    affectedWays.add(way);
368                }
369            }
370
371            if (moveTags) {
372                // remove duplicated tags from outer ways
373                for (Way way : outerWays) {
374                    if (way.hasKey(key)) {
375                        affectedWays.add(way);
376                    }
377                }
378            }
379
380            if (affectedWays.size() > 0) {
381                commands.add(new ChangePropertyCommand(affectedWays, key, null));
382            }
383        }
384
385        if (moveTags) {
386            // add those tag values to the relation
387            if (isBoundary) {
388                values.put("name", name);
389            }
390            boolean fixed = false;
391            Relation r2 = new Relation(relation);
392            for (String key : values.keySet()) {
393                if (!r2.hasKey(key) && !key.equals("area")
394                        && (!isBoundary || key.equals("admin_level") || key.equals("name"))) {
395                    if (relation.isNew()) {
396                        relation.put(key, values.get(key));
397                    } else {
398                        r2.put(key, values.get(key));
399                    }
400                    fixed = true;
401                }
402            }
403            if (fixed && !relation.isNew()) {
404                commands.add(new ChangeCommand(relation, r2));
405            }
406        }
407
408        return commands;
409    }
410
411    /**
412     *
413     * @param rel relation
414     * @return false if user pressed "cancel".
415     */
416    private boolean askForAdminLevelAndName(Relation rel) {
417        String relAL = rel.get("admin_level");
418        String relName = rel.get("name");
419        if (relAL != null && relName != null)
420            return true;
421
422        JPanel panel = new JPanel(new GridBagLayout());
423        panel.add(new JLabel(tr("Enter admin level and name for the border relation:")), GBC.eol().insets(0, 0, 0, 5));
424
425        final JTextField admin = new JTextField();
426        admin.setText(relAL != null ? relAL : Config.getPref().get(PREF_MULTIPOLY + "lastadmin", ""));
427        panel.add(new JLabel(tr("Admin level")), GBC.std());
428        panel.add(Box.createHorizontalStrut(10), GBC.std());
429        panel.add(admin, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 5));
430
431        final JTextField name = new JTextField();
432        if (relName != null) {
433            name.setText(relName);
434        }
435        panel.add(new JLabel(tr("Name")), GBC.std());
436        panel.add(Box.createHorizontalStrut(10), GBC.std());
437        panel.add(name, GBC.eol().fill(GBC.HORIZONTAL));
438
439        final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
440            @Override
441            public void selectInitialValue() {
442                admin.requestFocusInWindow();
443                admin.selectAll();
444            }
445        };
446        final JDialog dlg = optionPane.createDialog(MainApplication.getMainFrame(), tr("Create a new relation"));
447        dlg.setModalityType(ModalityType.DOCUMENT_MODAL);
448
449        name.addActionListener(new ActionListener() {
450            @Override
451            public void actionPerformed(ActionEvent e) {
452                dlg.setVisible(false);
453                optionPane.setValue(JOptionPane.OK_OPTION);
454            }
455        });
456
457        dlg.setVisible(true);
458
459        Object answer = optionPane.getValue();
460        if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE
461                || (answer instanceof Integer && (Integer) answer != JOptionPane.OK_OPTION))
462            return false;
463
464        String admin_level = admin.getText().trim();
465        String new_name = name.getText().trim();
466        if (admin_level.equals("10") || (admin_level.length() == 1 && Character.isDigit(admin_level.charAt(0)))) {
467            rel.put("admin_level", admin_level);
468            Config.getPref().put(PREF_MULTIPOLY + "lastadmin", admin_level);
469        }
470        if (new_name.length() > 0) {
471            rel.put("name", new_name);
472        }
473        return true;
474    }
475}
Note: See TracBrowser for help on using the repository browser.