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

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

javadoc update

  • Property svn:eol-style set to native
File size: 14.6 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.Rectangle;
7import java.awt.geom.Area;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.HashSet;
12import java.util.List;
13import java.util.Set;
14import java.util.concurrent.Callable;
15import java.util.concurrent.ExecutionException;
16import java.util.concurrent.ExecutorService;
17import java.util.concurrent.Future;
18
19import org.openstreetmap.josm.tools.Geometry;
20import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
21import org.openstreetmap.josm.tools.MultiMap;
22import org.openstreetmap.josm.tools.Pair;
23import org.openstreetmap.josm.tools.Utils;
24
25/**
26 * Helper class to build multipolygons from multiple ways.
27 * @author viesturs
28 * @since 7392 (rename)
29 * @since 3704
30 */
31public class MultipolygonBuilder {
32
33 private static final Pair<Integer, ExecutorService> THREAD_POOL =
34 Utils.newThreadPool("multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY);
35
36 /**
37 * Represents one polygon that consists of multiple ways.
38 */
39 public static class JoinedPolygon {
40 public final List<Way> ways;
41 public final List<Boolean> reversed;
42 public final List<Node> nodes;
43 public final Area area;
44 public final Rectangle bounds;
45
46 /**
47 * Constructs a new {@code JoinedPolygon} from given list of ways.
48 * @param ways The ways used to build joined polygon
49 * @param reversed list of reversed states
50 */
51 public JoinedPolygon(List<Way> ways, List<Boolean> reversed) {
52 this.ways = ways;
53 this.reversed = reversed;
54 this.nodes = this.getNodes();
55 this.area = Geometry.getArea(nodes);
56 this.bounds = area.getBounds();
57 }
58
59 /**
60 * Creates a polygon from single way.
61 * @param way the way to form the polygon
62 */
63 public JoinedPolygon(Way way) {
64 this(Collections.singletonList(way), Collections.singletonList(Boolean.FALSE));
65 }
66
67 /**
68 * Builds a list of nodes for this polygon. First node is not duplicated as last node.
69 * @return list of nodes
70 */
71 public List<Node> getNodes() {
72 List<Node> nodes = new ArrayList<>();
73
74 for (int waypos = 0; waypos < this.ways.size(); waypos++) {
75 Way way = this.ways.get(waypos);
76 boolean reversed = this.reversed.get(waypos).booleanValue();
77
78 if (!reversed) {
79 for (int pos = 0; pos < way.getNodesCount() - 1; pos++) {
80 nodes.add(way.getNode(pos));
81 }
82 } else {
83 for (int pos = way.getNodesCount() - 1; pos > 0; pos--) {
84 nodes.add(way.getNode(pos));
85 }
86 }
87 }
88
89 return nodes;
90 }
91 }
92
93 /**
94 * Helper storage class for finding findOuterWays
95 */
96 static class PolygonLevel {
97 public final int level; // nesting level, even for outer, odd for inner polygons.
98 public final JoinedPolygon outerWay;
99
100 public List<JoinedPolygon> innerWays;
101
102 PolygonLevel(JoinedPolygon pol, int level) {
103 this.outerWay = pol;
104 this.level = level;
105 this.innerWays = new ArrayList<>();
106 }
107 }
108
109 /** List of outer ways **/
110 public final List<JoinedPolygon> outerWays;
111 /** List of inner ways **/
112 public final List<JoinedPolygon> innerWays;
113
114 /**
115 * Constructs a new {@code MultipolygonBuilder} initialized with given ways.
116 * @param outerWays The outer ways
117 * @param innerWays The inner ways
118 */
119 public MultipolygonBuilder(List<JoinedPolygon> outerWays, List<JoinedPolygon> innerWays) {
120 this.outerWays = outerWays;
121 this.innerWays = innerWays;
122 }
123
124 /**
125 * Constructs a new empty {@code MultipolygonBuilder}.
126 */
127 public MultipolygonBuilder() {
128 this.outerWays = new ArrayList<>(0);
129 this.innerWays = new ArrayList<>(0);
130 }
131
132 /**
133 * Splits ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
134 * TODO: Currently cannot process touching polygons. See code in JoinAreasAction.
135 * @param ways ways to analyze
136 * @return error description if the ways cannot be split, {@code null} if all fine.
137 */
138 public String makeFromWays(Collection<Way> ways) {
139 try {
140 List<JoinedPolygon> joinedWays = joinWays(ways);
141 //analyze witch way is inside witch outside.
142 return makeFromPolygons(joinedWays);
143 } catch (JoinedPolygonCreationException ex) {
144 return ex.getMessage();
145 }
146 }
147
148 /**
149 * An exception indicating an error while joining ways to multipolygon rings.
150 */
151 public static class JoinedPolygonCreationException extends RuntimeException {
152 /**
153 * Constructs a new {@code JoinedPolygonCreationException}.
154 * @param message the detail message. The detail message is saved for
155 * later retrieval by the {@link #getMessage()} method
156 */
157 public JoinedPolygonCreationException(String message) {
158 super(message);
159 }
160 }
161
162 /**
163 * Joins the given {@code ways} to multipolygon rings.
164 * @param ways the ways to join.
165 * @return a list of multipolygon rings.
166 * @throws JoinedPolygonCreationException if the creation fails.
167 */
168 public static List<JoinedPolygon> joinWays(Collection<Way> ways) throws JoinedPolygonCreationException {
169 List<JoinedPolygon> joinedWays = new ArrayList<>();
170
171 //collect ways connecting to each node.
172 MultiMap<Node, Way> nodesWithConnectedWays = new MultiMap<>();
173 Set<Way> usedWays = new HashSet<>();
174
175 for (Way w: ways) {
176 if (w.getNodesCount() < 2) {
177 throw new JoinedPolygonCreationException(tr("Cannot add a way with only {0} nodes.", w.getNodesCount()));
178 }
179
180 if (w.isClosed()) {
181 //closed way, add as is.
182 JoinedPolygon jw = new JoinedPolygon(w);
183 joinedWays.add(jw);
184 usedWays.add(w);
185 } else {
186 nodesWithConnectedWays.put(w.lastNode(), w);
187 nodesWithConnectedWays.put(w.firstNode(), w);
188 }
189 }
190
191 //process unclosed ways
192 for (Way startWay: ways) {
193 if (usedWays.contains(startWay)) {
194 continue;
195 }
196
197 Node startNode = startWay.firstNode();
198 List<Way> collectedWays = new ArrayList<>();
199 List<Boolean> collectedWaysReverse = new ArrayList<>();
200 Way curWay = startWay;
201 Node prevNode = startNode;
202
203 //find polygon ways
204 while (true) {
205 boolean curWayReverse = prevNode == curWay.lastNode();
206 Node nextNode = curWayReverse ? curWay.firstNode() : curWay.lastNode();
207
208 //add cur way to the list
209 collectedWays.add(curWay);
210 collectedWaysReverse.add(Boolean.valueOf(curWayReverse));
211
212 if (nextNode == startNode) {
213 //way finished
214 break;
215 }
216
217 //find next way
218 Collection<Way> adjacentWays = nodesWithConnectedWays.get(nextNode);
219
220 if (adjacentWays.size() != 2) {
221 throw new JoinedPolygonCreationException(tr("Each node must connect exactly 2 ways"));
222 }
223
224 Way nextWay = null;
225 for (Way way: adjacentWays) {
226 if (way != curWay) {
227 nextWay = way;
228 }
229 }
230
231 //move to the next way
232 curWay = nextWay;
233 prevNode = nextNode;
234 }
235
236 usedWays.addAll(collectedWays);
237 joinedWays.add(new JoinedPolygon(collectedWays, collectedWaysReverse));
238 }
239
240 return joinedWays;
241 }
242
243 /**
244 * This method analyzes which ways are inner and which outer. Sets {@link #innerWays} and {@link #outerWays} to the result.
245 * @param polygons polygons to analyze
246 * @return error description if the ways cannot be split, {@code null} if all fine.
247 */
248 private String makeFromPolygons(List<JoinedPolygon> polygons) {
249 List<PolygonLevel> list = findOuterWaysMultiThread(polygons);
250
251 if (list == null) {
252 return tr("There is an intersection between ways.");
253 }
254
255 this.outerWays.clear();
256 this.innerWays.clear();
257
258 //take every other level
259 for (PolygonLevel pol : list) {
260 if (pol.level % 2 == 0) {
261 this.outerWays.add(pol.outerWay);
262 } else {
263 this.innerWays.add(pol.outerWay);
264 }
265 }
266
267 return null;
268 }
269
270 private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
271 boolean outerGood = true;
272 List<JoinedPolygon> innerCandidates = new ArrayList<>();
273
274 for (JoinedPolygon innerWay : boundaryWays) {
275 if (innerWay == outerWay) {
276 continue;
277 }
278
279 // Preliminary computation on bounds. If bounds do not intersect, no need to do a costly area intersection
280 if (outerWay.bounds.intersects(innerWay.bounds)) {
281 // Bounds intersection, let's see in detail
282 PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
283
284 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
285 outerGood = false; // outer is inside another polygon
286 break;
287 } else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST) {
288 innerCandidates.add(innerWay);
289 } else if (intersection == PolygonIntersection.CROSSING) {
290 // ways intersect
291 return null;
292 }
293 }
294 }
295
296 return new Pair<>(outerGood, innerCandidates);
297 }
298
299 /**
300 * Collects outer way and corresponding inner ways from all boundaries.
301 * @param boundaryWays boundary ways
302 * @return the outermostWay, or {@code null} if intersection found.
303 */
304 private static List<PolygonLevel> findOuterWaysMultiThread(List<JoinedPolygon> boundaryWays) {
305 final List<PolygonLevel> result = new ArrayList<>();
306 final List<Worker> tasks = new ArrayList<>();
307 final int bucketsize = Math.max(32, boundaryWays.size()/THREAD_POOL.a/3);
308 final int noBuckets = (boundaryWays.size() + bucketsize - 1) / bucketsize;
309 final boolean singleThread = THREAD_POOL.a == 1 || noBuckets == 1;
310 for (int i = 0; i < noBuckets; i++) {
311 int from = i*bucketsize;
312 int to = Math.min((i+1)*bucketsize, boundaryWays.size());
313 List<PolygonLevel> target = singleThread ? result : new ArrayList<PolygonLevel>(to - from);
314 tasks.add(new Worker(boundaryWays, from, to, target));
315 }
316 if (singleThread) {
317 try {
318 for (Worker task : tasks) {
319 if (task.call() == null) {
320 return null;
321 }
322 }
323 } catch (Exception ex) {
324 throw new RuntimeException(ex);
325 }
326 } else if (!tasks.isEmpty()) {
327 try {
328 for (Future<List<PolygonLevel>> future : THREAD_POOL.b.invokeAll(tasks)) {
329 List<PolygonLevel> res = future.get();
330 if (res == null) {
331 return null;
332 }
333 result.addAll(res);
334 }
335 } catch (InterruptedException | ExecutionException ex) {
336 throw new RuntimeException(ex);
337 }
338 }
339 return result;
340 }
341
342 private static class Worker implements Callable<List<PolygonLevel>> {
343
344 private final List<JoinedPolygon> input;
345 private final int from;
346 private final int to;
347 private final List<PolygonLevel> output;
348
349 Worker(List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output) {
350 this.input = input;
351 this.from = from;
352 this.to = to;
353 this.output = output;
354 }
355
356 /**
357 * Collects outer way and corresponding inner ways from all boundaries.
358 * @param level nesting level
359 * @param boundaryWays boundary ways
360 * @return the outermostWay, or {@code null} if intersection found.
361 */
362 private static List<PolygonLevel> findOuterWaysRecursive(int level, List<JoinedPolygon> boundaryWays) {
363
364 final List<PolygonLevel> result = new ArrayList<>();
365
366 for (JoinedPolygon outerWay : boundaryWays) {
367 if (processOuterWay(level, boundaryWays, result, outerWay) == null) {
368 return null;
369 }
370 }
371
372 return result;
373 }
374
375 private static List<PolygonLevel> processOuterWay(int level, List<JoinedPolygon> boundaryWays,
376 final List<PolygonLevel> result, JoinedPolygon outerWay) {
377 Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates(outerWay, boundaryWays);
378 if (p == null) {
379 // ways intersect
380 return null;
381 }
382
383 if (p.a) {
384 //add new outer polygon
385 PolygonLevel pol = new PolygonLevel(outerWay, level);
386
387 //process inner ways
388 if (!p.b.isEmpty()) {
389 List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, p.b);
390 if (innerList == null) {
391 return null; //intersection found
392 }
393
394 result.addAll(innerList);
395
396 for (PolygonLevel pl : innerList) {
397 if (pl.level == level + 1) {
398 pol.innerWays.add(pl.outerWay);
399 }
400 }
401 }
402
403 result.add(pol);
404 }
405 return result;
406 }
407
408 @Override
409 public List<PolygonLevel> call() throws Exception {
410 for (int i = from; i < to; i++) {
411 if (processOuterWay(0, input, output, input.get(i)) == null) {
412 return null;
413 }
414 }
415 return output;
416 }
417 }
418}
Note: See TracBrowser for help on using the repository browser.