source: osm/applications/editors/josm/plugins/reltoolbox/src/relcontext/actions/ReconstructPolygonAction.java@ 36134

Last change on this file since 36134 was 36134, checked in by taylor.smock, 22 months ago

Use DeleteCommand.delete where possible and fix some potential NPEs from its usage

File size: 8.8 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.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.util.ArrayList;
9import java.util.Arrays;
10import java.util.Collections;
11import java.util.HashSet;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15
16import javax.swing.JOptionPane;
17
18import org.openstreetmap.josm.actions.JosmAction;
19import org.openstreetmap.josm.command.AddCommand;
20import org.openstreetmap.josm.command.ChangeCommand;
21import org.openstreetmap.josm.command.Command;
22import org.openstreetmap.josm.command.DeleteCommand;
23import org.openstreetmap.josm.command.SequenceCommand;
24import org.openstreetmap.josm.data.UndoRedoHandler;
25import org.openstreetmap.josm.data.coor.EastNorth;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
28import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
29import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.data.osm.Relation;
32import org.openstreetmap.josm.data.osm.RelationMember;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.gui.MainApplication;
35import org.openstreetmap.josm.tools.Shortcut;
36
37import relcontext.ChosenRelation;
38import relcontext.ChosenRelationListener;
39
40/**
41 * Make a single polygon out of the multipolygon relation. The relation must have only outer members.
42 * @author Zverik
43 */
44public class ReconstructPolygonAction extends JosmAction implements ChosenRelationListener {
45 private final ChosenRelation rel;
46
47 private static final List<String> IRRELEVANT_KEYS = Arrays.asList("source", "created_by", "note");
48
49 public ReconstructPolygonAction(ChosenRelation rel) {
50 super(tr("Reconstruct polygon"), "dialogs/filter", tr("Reconstruct polygon from multipolygon relation"),
51 Shortcut.registerShortcut("reltoolbox:reconstructpoly", tr("Relation Toolbox: {0}",
52 tr("Reconstruct polygon from multipolygon relation")),
53 KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), false);
54 this.rel = rel;
55 rel.addChosenRelationListener(this);
56 setEnabled(isSuitableRelation(rel.get()));
57 }
58
59 @Override
60 public void actionPerformed(ActionEvent e) {
61 Relation r = rel.get();
62 boolean relationReused = false;
63 List<Way> ways = new ArrayList<>();
64 boolean wont = false;
65 for (RelationMember m : r.getMembers()) {
66 if (m.isWay()) {
67 ways.add(m.getWay());
68 } else {
69 wont = true;
70 }
71 }
72 if (wont) {
73 JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
74 tr("Multipolygon must consist only of ways"), tr("Reconstruct polygon"), JOptionPane.ERROR_MESSAGE);
75 return;
76 }
77
78 MultipolygonBuilder mpc = new MultipolygonBuilder();
79 String error = mpc.makeFromWays(ways);
80 if (error != null) {
81 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), error);
82 return;
83 }
84
85 rel.clear();
86 List<OsmPrimitive> newSelection = new ArrayList<>();
87 List<Command> commands = new ArrayList<>();
88 Command relationDeleteCommand = DeleteCommand.delete(Collections.singleton(r), true, true);
89 if (relationDeleteCommand == null)
90 return;
91
92 DataSet ds = MainApplication.getLayerManager().getEditDataSet();
93 for (JoinedPolygon p : mpc.outerWays) {
94
95 ArrayList<JoinedPolygon> myInnerWays = new ArrayList<>();
96 for (JoinedPolygon i : mpc.innerWays) {
97 // if the first point of any inner ring is contained in this
98 // outer ring, then this inner ring belongs to us. This
99 // assumption only works if multipolygons have valid geometries
100 EastNorth en = i.ways.get(0).firstNode().getEastNorth();
101 if (p.area.contains(en.east(), en.north())) {
102 myInnerWays.add(i);
103 }
104 }
105
106 if (!myInnerWays.isEmpty()) {
107 // this ring has inner rings, so we leave a multipolygon in
108 // place and don't reconstruct the rings.
109 Relation n;
110 if (relationReused) {
111 n = new Relation();
112 n.setKeys(r.getKeys());
113 } else {
114 n = new Relation(r);
115 n.setMembers(null);
116 }
117 for (Way w : p.ways) {
118 n.addMember(new RelationMember("outer", w));
119 }
120 for (JoinedPolygon i : myInnerWays) {
121 for (Way w : i.ways) {
122 n.addMember(new RelationMember("inner", w));
123 }
124 }
125 if (relationReused) {
126 commands.add(new AddCommand(ds, n));
127 } else {
128 relationReused = true;
129 commands.add(new ChangeCommand(r, n));
130 }
131 newSelection.add(n);
132 continue;
133 }
134
135 // move all tags from relation and common tags from ways
136 // start with all tags from first way but only if area tags are present
137 Map<String, String> tags = p.ways.get(0).getKeys();
138 if (!p.ways.get(0).hasAreaTags()) {
139 tags.clear();
140 }
141 List<OsmPrimitive> relations = p.ways.get(0).getReferrers();
142 Set<String> noTags = new HashSet<>(r.keySet());
143 for (int i = 1; i < p.ways.size(); i++) {
144 Way w = p.ways.get(i);
145 for (String key : w.keySet()) {
146 String value = w.get(key);
147 if (!noTags.contains(key) && tags.containsKey(key) && !tags.get(key).equals(value)) {
148 tags.remove(key);
149 noTags.add(key);
150 }
151 }
152 List<OsmPrimitive> referrers = w.getReferrers();
153 relations.removeIf(osmPrimitive -> !referrers.contains(osmPrimitive));
154 }
155 tags.putAll(r.getKeys());
156 tags.remove("type");
157
158 // then delete ways that are not relevant (do not take part in other relations of have strange tags)
159 Way candidateWay = null;
160 for (Way w : p.ways) {
161 if (w.getReferrers().equals(relations)) {
162 // check tags that remain
163 Set<String> keys = new HashSet<>(w.keySet());
164 keys.removeAll(tags.keySet());
165 IRRELEVANT_KEYS.forEach(keys::remove);
166 if (keys.isEmpty()) {
167 if (candidateWay == null) {
168 candidateWay = w;
169 } else {
170 if (candidateWay.isNew() && !w.isNew()) {
171 // prefer ways that are already in the database
172 Way tmp = w;
173 w = candidateWay;
174 candidateWay = tmp;
175 }
176 final Command deleteCommand = DeleteCommand.delete(Collections.singleton(w));
177 if (deleteCommand != null) {
178 commands.add(deleteCommand);
179 }
180 }
181 }
182 }
183 }
184
185 // take the first way, put all nodes into it, making it a closed polygon
186 Way result = candidateWay == null ? new Way() : new Way(candidateWay);
187 result.setNodes(p.nodes);
188 result.addNode(result.firstNode());
189 result.setKeys(tags);
190 newSelection.add(candidateWay == null ? result : candidateWay);
191 commands.add(candidateWay == null ? new AddCommand(ds, result) : new ChangeCommand(candidateWay, result));
192 }
193
194 // only delete the relation if it hasn't been re-used
195 if (!relationReused) {
196 commands.add(relationDeleteCommand);
197 }
198
199 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Reconstruct polygons from relation {0}",
200 r.getDisplayName(DefaultNameFormatter.getInstance())), commands));
201 ds.setSelected(newSelection);
202 }
203
204 @Override
205 public void chosenRelationChanged(Relation oldRelation, Relation newRelation) {
206 setEnabled(isSuitableRelation(newRelation));
207 }
208
209 private static boolean isSuitableRelation(Relation newRelation) {
210 return newRelation != null && "multipolygon".equals(newRelation.get("type")) && newRelation.getMembersCount() != 0;
211 }
212}
Note: See TracBrowser for help on using the repository browser.