source: josm/trunk/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java@ 19076

Last change on this file since 19076 was 17358, checked in by GerdP, 4 years ago

see #19885: memory leak with "temporary" objects in validator and actions

  • (hopefully) fix memory leaks in complex actions
  • handle complex cases with presets and RelationEditor

I hope these changes don't break plugins which extend or overwrite RelationEditor

  • Property svn:eol-style set to native
File size: 8.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.HashSet;
11import java.util.List;
12import java.util.Map;
13import java.util.Set;
14import java.util.stream.Collectors;
15
16import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
17import org.openstreetmap.josm.tools.CheckParameterUtil;
18import org.openstreetmap.josm.tools.Geometry;
19import org.openstreetmap.josm.tools.MultiMap;
20import org.openstreetmap.josm.tools.Pair;
21
22/**
23 * Helper class to build multipolygons from multiple ways.
24 * @author viesturs
25 * @since 7392 (rename)
26 * @since 3704
27 */
28public class MultipolygonBuilder {
29
30 /**
31 * Represents one polygon that consists of multiple ways.
32 */
33 public static class JoinedPolygon {
34 /** list of ways building this polygon */
35 public final List<Way> ways;
36 /** list of flags that indicate if the nodes of the way in the same position where reversed */
37 public final List<Boolean> reversed;
38 /** the nodes of the polygon, first node is not duplicated as last node. */
39 public final List<Node> nodes;
40 /** the area in east/north space */
41 public final Area area;
42
43 /**
44 * Constructs a new {@code JoinedPolygon} from given list of ways.
45 * @param ways The ways used to build joined polygon
46 * @param reversed list of reversed states
47 */
48 public JoinedPolygon(List<Way> ways, List<Boolean> reversed) {
49 this.ways = ways;
50 this.reversed = reversed;
51 this.nodes = this.getNodes();
52 this.area = Geometry.getArea(nodes);
53 }
54
55 /**
56 * Creates a polygon from single way.
57 * @param way the way to form the polygon
58 */
59 public JoinedPolygon(Way way) {
60 this(Collections.singletonList(way), Collections.singletonList(Boolean.FALSE));
61 }
62
63 /**
64 * Builds a list of nodes for this polygon. First node is not duplicated as last node.
65 * @return list of nodes
66 */
67 public List<Node> getNodes() {
68 List<Node> ringNodes = new ArrayList<>();
69
70 for (int waypos = 0; waypos < this.ways.size(); waypos++) {
71 Way way = this.ways.get(waypos);
72
73 if (!this.reversed.get(waypos)) {
74 for (int pos = 0; pos < way.getNodesCount() - 1; pos++) {
75 ringNodes.add(way.getNode(pos));
76 }
77 } else {
78 for (int pos = way.getNodesCount() - 1; pos > 0; pos--) {
79 ringNodes.add(way.getNode(pos));
80 }
81 }
82 }
83
84 return ringNodes;
85 }
86 }
87
88 /** List of outer ways **/
89 public final List<JoinedPolygon> outerWays;
90 /** List of inner ways **/
91 public final List<JoinedPolygon> innerWays;
92
93 /**
94 * Constructs a new {@code MultipolygonBuilder} initialized with given ways.
95 * @param outerWays The outer ways
96 * @param innerWays The inner ways
97 */
98 public MultipolygonBuilder(List<JoinedPolygon> outerWays, List<JoinedPolygon> innerWays) {
99 this.outerWays = outerWays;
100 this.innerWays = innerWays;
101 }
102
103 /**
104 * Constructs a new empty {@code MultipolygonBuilder}.
105 */
106 public MultipolygonBuilder() {
107 this.outerWays = new ArrayList<>(0);
108 this.innerWays = new ArrayList<>(0);
109 }
110
111 /**
112 * Splits ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
113 * Calculation is done in {@link MultipolygonTest#makeFromWays(Collection)} to ensure that the result is a valid multipolygon.
114 * @param ways ways to analyze
115 * @return error description if the ways cannot be split, {@code null} if all fine.
116 */
117 public String makeFromWays(Collection<Way> ways) {
118 MultipolygonTest mpTest = new MultipolygonTest();
119 Relation calculated = mpTest.makeFromWays(ways);
120 try {
121 if (!mpTest.getErrors().isEmpty()) {
122 return mpTest.getErrors().iterator().next().getMessage();
123 }
124 Pair<List<JoinedPolygon>, List<JoinedPolygon>> outerInner = joinWays(calculated);
125 this.outerWays.clear();
126 this.innerWays.clear();
127 this.outerWays.addAll(outerInner.a);
128 this.innerWays.addAll(outerInner.b);
129 return null;
130 } finally {
131 calculated.setMembers(null); // see #19885
132 }
133 }
134
135 /**
136 * An exception indicating an error while joining ways to multipolygon rings.
137 */
138 public static class JoinedPolygonCreationException extends RuntimeException {
139 /**
140 * Constructs a new {@code JoinedPolygonCreationException}.
141 * @param message the detail message. The detail message is saved for
142 * later retrieval by the {@link #getMessage()} method
143 */
144 public JoinedPolygonCreationException(String message) {
145 super(message);
146 }
147 }
148
149 /**
150 * Joins the given {@code multipolygon} to a pair of outer and inner multipolygon rings.
151 *
152 * @param multipolygon the multipolygon to join.
153 * @return a pair of outer and inner multipolygon rings.
154 * @throws JoinedPolygonCreationException if the creation fails.
155 */
156 public static Pair<List<JoinedPolygon>, List<JoinedPolygon>> joinWays(Relation multipolygon) {
157 CheckParameterUtil.ensureThat(multipolygon.isMultipolygon(), "multipolygon.isMultipolygon");
158 final Map<String, Set<Way>> members = multipolygon.getMembers().stream()
159 .filter(RelationMember::isWay)
160 .collect(Collectors.groupingBy(RelationMember::getRole, Collectors.mapping(RelationMember::getWay, Collectors.toSet())));
161 final List<JoinedPolygon> outerRings = joinWays(members.getOrDefault("outer", Collections.emptySet()));
162 final List<JoinedPolygon> innerRings = joinWays(members.getOrDefault("inner", Collections.emptySet()));
163 return Pair.create(outerRings, innerRings);
164 }
165
166 /**
167 * Joins the given {@code ways} to multipolygon rings.
168 * @param ways the ways to join.
169 * @return a list of multipolygon rings.
170 * @throws JoinedPolygonCreationException if the creation fails.
171 */
172 public static List<JoinedPolygon> joinWays(Collection<Way> ways) {
173 List<JoinedPolygon> joinedWays = new ArrayList<>();
174
175 //collect ways connecting to each node.
176 MultiMap<Node, Way> nodesWithConnectedWays = new MultiMap<>();
177 Set<Way> usedWays = new HashSet<>();
178
179 for (Way w: ways) {
180 if (w.getNodesCount() < 2) {
181 throw new JoinedPolygonCreationException(tr("Cannot add a way with only {0} nodes.", w.getNodesCount()));
182 }
183
184 if (w.isClosed()) {
185 //closed way, add as is.
186 JoinedPolygon jw = new JoinedPolygon(w);
187 joinedWays.add(jw);
188 usedWays.add(w);
189 } else {
190 nodesWithConnectedWays.put(w.lastNode(), w);
191 nodesWithConnectedWays.put(w.firstNode(), w);
192 }
193 }
194
195 //process unclosed ways
196 for (Way startWay: ways) {
197 if (usedWays.contains(startWay)) {
198 continue;
199 }
200
201 Node startNode = startWay.firstNode();
202 List<Way> collectedWays = new ArrayList<>();
203 List<Boolean> collectedWaysReverse = new ArrayList<>();
204 Way curWay = startWay;
205 Node prevNode = startNode;
206
207 //find polygon ways
208 while (true) {
209 boolean curWayReverse = prevNode == curWay.lastNode();
210 Node nextNode = curWayReverse ? curWay.firstNode() : curWay.lastNode();
211
212 //add cur way to the list
213 collectedWays.add(curWay);
214 collectedWaysReverse.add(Boolean.valueOf(curWayReverse));
215
216 if (nextNode == startNode) {
217 //way finished
218 break;
219 }
220
221 //find next way
222 Collection<Way> adjacentWays = nodesWithConnectedWays.get(nextNode);
223
224 if (adjacentWays.size() != 2) {
225 throw new JoinedPolygonCreationException(tr("Each node must connect exactly 2 ways"));
226 }
227
228 Way nextWay = null;
229 for (Way way: adjacentWays) {
230 if (way != curWay) {
231 nextWay = way;
232 }
233 }
234
235 //move to the next way
236 curWay = nextWay;
237 prevNode = nextNode;
238 }
239
240 usedWays.addAll(collectedWays);
241 joinedWays.add(new JoinedPolygon(collectedWays, collectedWaysReverse));
242 }
243
244 return joinedWays;
245 }
246}
Note: See TracBrowser for help on using the repository browser.