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

Last change on this file since 7402 was 7402, checked in by Don-vip, 10 years ago

fix some Sonar issues

  • Property svn:eol-style set to native
File size: 10.5 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.Set;
13
14import org.openstreetmap.josm.tools.Geometry;
15import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
16import org.openstreetmap.josm.tools.MultiMap;
17
18/**
19 * Helper class to build multipolygons from multiple ways.
20 * @author viesturs
21 * @since 7392 (rename)
22 * @since 3704
23 */
24public class MultipolygonBuilder {
25
26 /**
27 * Represents one polygon that consists of multiple ways.
28 */
29 public static class JoinedPolygon {
30 public final List<Way> ways;
31 public final List<Boolean> reversed;
32 public final List<Node> nodes;
33 public final Area area;
34
35 /**
36 * Constructs a new {@code JoinedPolygon} from given list of ways.
37 * @param ways The ways used to build joined polygon
38 */
39 public JoinedPolygon(List<Way> ways, List<Boolean> reversed) {
40 this.ways = ways;
41 this.reversed = reversed;
42 this.nodes = this.getNodes();
43 this.area = Geometry.getArea(nodes);
44 }
45
46 /**
47 * Creates a polygon from single way.
48 * @param way the way to form the polygon
49 */
50 public JoinedPolygon(Way way) {
51 this(Collections.singletonList(way), Collections.singletonList(Boolean.FALSE));
52 }
53
54 /**
55 * Builds a list of nodes for this polygon. First node is not duplicated as last node.
56 * @return list of nodes
57 */
58 public List<Node> getNodes() {
59 List<Node> nodes = new ArrayList<>();
60
61 for (int waypos = 0; waypos < this.ways.size(); waypos ++) {
62 Way way = this.ways.get(waypos);
63 boolean reversed = this.reversed.get(waypos).booleanValue();
64
65 if (!reversed) {
66 for (int pos = 0; pos < way.getNodesCount() - 1; pos++) {
67 nodes.add(way.getNode(pos));
68 }
69 } else {
70 for (int pos = way.getNodesCount() - 1; pos > 0; pos--) {
71 nodes.add(way.getNode(pos));
72 }
73 }
74 }
75
76 return nodes;
77 }
78 }
79
80 /**
81 * Helper storage class for finding findOuterWays
82 */
83 static class PolygonLevel {
84 public final int level; //nesting level , even for outer, odd for inner polygons.
85 public final JoinedPolygon outerWay;
86
87 public List<JoinedPolygon> innerWays;
88
89 public PolygonLevel(JoinedPolygon pol, int level) {
90 this.outerWay = pol;
91 this.level = level;
92 this.innerWays = new ArrayList<>();
93 }
94 }
95
96 /** List of outer ways **/
97 public final List<JoinedPolygon> outerWays;
98 /** List of inner ways **/
99 public final List<JoinedPolygon> innerWays;
100
101 /**
102 * Constructs a new {@code MultipolygonBuilder} initialized with given ways.
103 * @param outerWays The outer ways
104 * @param innerWays The inner ways
105 */
106 public MultipolygonBuilder(List<JoinedPolygon> outerWays, List<JoinedPolygon> innerWays) {
107 this.outerWays = outerWays;
108 this.innerWays = innerWays;
109 }
110
111 /**
112 * Constructs a new empty {@code MultipolygonBuilder}.
113 */
114 public MultipolygonBuilder() {
115 this.outerWays = new ArrayList<>(0);
116 this.innerWays = new ArrayList<>(0);
117 }
118
119 /**
120 * Splits ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
121 * TODO: Currently cannot process touching polygons. See code in JoinAreasAction.
122 * @param ways ways to analyze
123 * @return error description if the ways cannot be split, {@code null} if all fine.
124 */
125 public String makeFromWays(Collection<Way> ways) {
126 try {
127 List<JoinedPolygon> joinedWays = joinWays(ways);
128 //analyze witch way is inside witch outside.
129 return makeFromPolygons(joinedWays);
130 } catch (JoinedPolygonCreationException ex) {
131 return ex.getMessage();
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 ways} to multipolygon rings.
151 * @param ways the ways to join.
152 * @return a list of multipolygon rings.
153 * @throws JoinedPolygonCreationException if the creation fails.
154 */
155 public static List<JoinedPolygon> joinWays(Collection<Way> ways) throws JoinedPolygonCreationException {
156 List<JoinedPolygon> joinedWays = new ArrayList<>();
157
158 //collect ways connecting to each node.
159 MultiMap<Node, Way> nodesWithConnectedWays = new MultiMap<>();
160 Set<Way> usedWays = new HashSet<>();
161
162 for (Way w: ways) {
163 if (w.getNodesCount() < 2) {
164 throw new JoinedPolygonCreationException(tr("Cannot add a way with only {0} nodes.", w.getNodesCount()));
165 }
166
167 if (w.isClosed()) {
168 //closed way, add as is.
169 JoinedPolygon jw = new JoinedPolygon(w);
170 joinedWays.add(jw);
171 usedWays.add(w);
172 } else {
173 nodesWithConnectedWays.put(w.lastNode(), w);
174 nodesWithConnectedWays.put(w.firstNode(), w);
175 }
176 }
177
178 //process unclosed ways
179 for (Way startWay: ways) {
180 if (usedWays.contains(startWay)) {
181 continue;
182 }
183
184 Node startNode = startWay.firstNode();
185 List<Way> collectedWays = new ArrayList<>();
186 List<Boolean> collectedWaysReverse = new ArrayList<>();
187 Way curWay = startWay;
188 Node prevNode = startNode;
189
190 //find polygon ways
191 while (true) {
192 boolean curWayReverse = prevNode == curWay.lastNode();
193 Node nextNode = (curWayReverse) ? curWay.firstNode(): curWay.lastNode();
194
195 //add cur way to the list
196 collectedWays.add(curWay);
197 collectedWaysReverse.add(Boolean.valueOf(curWayReverse));
198
199 if (nextNode == startNode) {
200 //way finished
201 break;
202 }
203
204 //find next way
205 Collection<Way> adjacentWays = nodesWithConnectedWays.get(nextNode);
206
207 if (adjacentWays.size() != 2) {
208 throw new JoinedPolygonCreationException(tr("Each node must connect exactly 2 ways"));
209 }
210
211 Way nextWay = null;
212 for(Way way: adjacentWays) {
213 if (way != curWay) {
214 nextWay = way;
215 }
216 }
217
218 //move to the next way
219 curWay = nextWay;
220 prevNode = nextNode;
221 }
222
223 usedWays.addAll(collectedWays);
224 joinedWays.add(new JoinedPolygon(collectedWays, collectedWaysReverse));
225 }
226
227 return joinedWays;
228 }
229
230 /**
231 * This method analyzes which ways are inner and which outer. Sets {@link #innerWays} and {@link #outerWays} to the result.
232 * @param polygons polygons to analyze
233 * @return error description if the ways cannot be split, {@code null} if all fine.
234 */
235 private String makeFromPolygons(List<JoinedPolygon> polygons) {
236 List<PolygonLevel> list = findOuterWaysRecursive(0, polygons);
237
238 if (list == null) {
239 return tr("There is an intersection between ways.");
240 }
241
242 this.outerWays.clear();
243 this.innerWays.clear();
244
245 //take every other level
246 for (PolygonLevel pol : list) {
247 if (pol.level % 2 == 0) {
248 this.outerWays.add(pol.outerWay);
249 } else {
250 this.innerWays.add(pol.outerWay);
251 }
252 }
253
254 return null;
255 }
256
257 /**
258 * Collects outer way and corresponding inner ways from all boundaries.
259 * @param boundaryWays
260 * @return the outermostWay, or {@code null} if intersection found.
261 */
262 private List<PolygonLevel> findOuterWaysRecursive(int level, Collection<JoinedPolygon> boundaryWays) {
263
264 //TODO: bad performance for deep nesting...
265 List<PolygonLevel> result = new ArrayList<>();
266
267 for (JoinedPolygon outerWay : boundaryWays) {
268
269 boolean outerGood = true;
270 List<JoinedPolygon> innerCandidates = new ArrayList<>();
271
272 for (JoinedPolygon innerWay : boundaryWays) {
273 if (innerWay == outerWay) {
274 continue;
275 }
276
277 PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
278
279 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
280 outerGood = false; // outer is inside another polygon
281 break;
282 } else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST) {
283 innerCandidates.add(innerWay);
284 } else if (intersection == PolygonIntersection.CROSSING) {
285 //ways intersect
286 return null;
287 }
288 }
289
290 if (!outerGood) {
291 continue;
292 }
293
294 //add new outer polygon
295 PolygonLevel pol = new PolygonLevel(outerWay, level);
296
297 //process inner ways
298 if (!innerCandidates.isEmpty()) {
299 List<PolygonLevel> innerList = this.findOuterWaysRecursive(level + 1, innerCandidates);
300 if (innerList == null) {
301 return null; //intersection found
302 }
303
304 result.addAll(innerList);
305
306 for (PolygonLevel pl : innerList) {
307 if (pl.level == level + 1) {
308 pol.innerWays.add(pl.outerWay);
309 }
310 }
311 }
312
313 result.add(pol);
314 }
315
316 return result;
317 }
318}
Note: See TracBrowser for help on using the repository browser.