source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java@ 12841

Last change on this file since 12841 was 12841, checked in by bastiK, 7 years ago

see #15229 - fix deprecations caused by [12840]

  • Property svn:eol-style set to native
File size: 28.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.osm.visitor.paint.relations;
3
4import java.awt.geom.Path2D;
5import java.awt.geom.PathIterator;
6import java.awt.geom.Rectangle2D;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.HashSet;
11import java.util.Iterator;
12import java.util.List;
13import java.util.Optional;
14import java.util.Set;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
18import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.osm.DataSet;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.RelationMember;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
27import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
28import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
29import org.openstreetmap.josm.data.projection.Projection;
30import org.openstreetmap.josm.tools.Geometry;
31import org.openstreetmap.josm.tools.Geometry.AreaAndPerimeter;
32import org.openstreetmap.josm.tools.Logging;
33
34/**
35 * Multipolygon data used to represent complex areas, see <a href="https://wiki.openstreetmap.org/wiki/Relation:multipolygon">wiki</a>.
36 * @since 2788
37 */
38public class Multipolygon {
39
40 /** preference key for a collection of roles which indicate that the respective member belongs to an
41 * <em>outer</em> polygon. Default is <tt>outer</tt>.
42 */
43 public static final String PREF_KEY_OUTER_ROLES = "mappaint.multipolygon.outer.roles";
44
45 /** preference key for collection of role prefixes which indicate that the respective
46 * member belongs to an <em>outer</em> polygon. Default is empty.
47 */
48 public static final String PREF_KEY_OUTER_ROLE_PREFIXES = "mappaint.multipolygon.outer.role-prefixes";
49
50 /** preference key for a collection of roles which indicate that the respective member belongs to an
51 * <em>inner</em> polygon. Default is <tt>inner</tt>.
52 */
53 public static final String PREF_KEY_INNER_ROLES = "mappaint.multipolygon.inner.roles";
54
55 /** preference key for collection of role prefixes which indicate that the respective
56 * member belongs to an <em>inner</em> polygon. Default is empty.
57 */
58 public static final String PREF_KEY_INNER_ROLE_PREFIXES = "mappaint.multipolygon.inner.role-prefixes";
59
60 /**
61 * <p>Kind of strategy object which is responsible for deciding whether a given
62 * member role indicates that the member belongs to an <em>outer</em> or an
63 * <em>inner</em> polygon.</p>
64 *
65 * <p>The decision is taken based on preference settings, see the four preference keys
66 * above.</p>
67 */
68 private static class MultipolygonRoleMatcher implements PreferenceChangedListener {
69 private final List<String> outerExactRoles = new ArrayList<>();
70 private final List<String> outerRolePrefixes = new ArrayList<>();
71 private final List<String> innerExactRoles = new ArrayList<>();
72 private final List<String> innerRolePrefixes = new ArrayList<>();
73
74 private void initDefaults() {
75 outerExactRoles.clear();
76 outerRolePrefixes.clear();
77 innerExactRoles.clear();
78 innerRolePrefixes.clear();
79 outerExactRoles.add("outer");
80 innerExactRoles.add("inner");
81 }
82
83 private static void setNormalized(Collection<String> literals, List<String> target) {
84 target.clear();
85 for (String l: literals) {
86 if (l == null) {
87 continue;
88 }
89 l = l.trim();
90 if (!target.contains(l)) {
91 target.add(l);
92 }
93 }
94 }
95
96 private void initFromPreferences() {
97 initDefaults();
98 if (Main.pref == null) return;
99 Collection<String> literals;
100 literals = Main.pref.getList(PREF_KEY_OUTER_ROLES);
101 if (literals != null && !literals.isEmpty()) {
102 setNormalized(literals, outerExactRoles);
103 }
104 literals = Main.pref.getList(PREF_KEY_OUTER_ROLE_PREFIXES);
105 if (literals != null && !literals.isEmpty()) {
106 setNormalized(literals, outerRolePrefixes);
107 }
108 literals = Main.pref.getList(PREF_KEY_INNER_ROLES);
109 if (literals != null && !literals.isEmpty()) {
110 setNormalized(literals, innerExactRoles);
111 }
112 literals = Main.pref.getList(PREF_KEY_INNER_ROLE_PREFIXES);
113 if (literals != null && !literals.isEmpty()) {
114 setNormalized(literals, innerRolePrefixes);
115 }
116 }
117
118 @Override
119 public void preferenceChanged(PreferenceChangeEvent evt) {
120 if (PREF_KEY_INNER_ROLE_PREFIXES.equals(evt.getKey()) ||
121 PREF_KEY_INNER_ROLES.equals(evt.getKey()) ||
122 PREF_KEY_OUTER_ROLE_PREFIXES.equals(evt.getKey()) ||
123 PREF_KEY_OUTER_ROLES.equals(evt.getKey())) {
124 initFromPreferences();
125 }
126 }
127
128 boolean isOuterRole(String role) {
129 if (role == null) return false;
130 for (String candidate: outerExactRoles) {
131 if (role.equals(candidate)) return true;
132 }
133 for (String candidate: outerRolePrefixes) {
134 if (role.startsWith(candidate)) return true;
135 }
136 return false;
137 }
138
139 boolean isInnerRole(String role) {
140 if (role == null) return false;
141 for (String candidate: innerExactRoles) {
142 if (role.equals(candidate)) return true;
143 }
144 for (String candidate: innerRolePrefixes) {
145 if (role.startsWith(candidate)) return true;
146 }
147 return false;
148 }
149 }
150
151 /*
152 * Init a private global matcher object which will listen to preference changes.
153 */
154 private static MultipolygonRoleMatcher roleMatcher;
155
156 private static synchronized MultipolygonRoleMatcher getMultipolygonRoleMatcher() {
157 if (roleMatcher == null) {
158 roleMatcher = new MultipolygonRoleMatcher();
159 if (Main.pref != null) {
160 roleMatcher.initFromPreferences();
161 Main.pref.addPreferenceChangeListener(roleMatcher);
162 }
163 }
164 return roleMatcher;
165 }
166
167 /**
168 * Class representing a string of ways.
169 *
170 * The last node of one way is the first way of the next one.
171 * The string may or may not be closed.
172 */
173 public static class JoinedWay {
174 protected final List<Node> nodes;
175 protected final Collection<Long> wayIds;
176 protected boolean selected;
177
178 /**
179 * Constructs a new {@code JoinedWay}.
180 * @param nodes list of nodes - must not be null
181 * @param wayIds list of way IDs - must not be null
182 * @param selected whether joined way is selected or not
183 */
184 public JoinedWay(List<Node> nodes, Collection<Long> wayIds, boolean selected) {
185 this.nodes = new ArrayList<>(nodes);
186 this.wayIds = new ArrayList<>(wayIds);
187 this.selected = selected;
188 }
189
190 /**
191 * Replies the list of nodes.
192 * @return the list of nodes
193 */
194 public List<Node> getNodes() {
195 return Collections.unmodifiableList(nodes);
196 }
197
198 /**
199 * Replies the list of way IDs.
200 * @return the list of way IDs
201 */
202 public Collection<Long> getWayIds() {
203 return Collections.unmodifiableCollection(wayIds);
204 }
205
206 /**
207 * Determines if this is selected.
208 * @return {@code true} if this is selected
209 */
210 public final boolean isSelected() {
211 return selected;
212 }
213
214 /**
215 * Sets whether this is selected
216 * @param selected {@code true} if this is selected
217 * @since 10312
218 */
219 public final void setSelected(boolean selected) {
220 this.selected = selected;
221 }
222
223 /**
224 * Determines if this joined way is closed.
225 * @return {@code true} if this joined way is closed
226 */
227 public boolean isClosed() {
228 return nodes.isEmpty() || getLastNode().equals(getFirstNode());
229 }
230
231 /**
232 * Returns the first node.
233 * @return the first node
234 * @since 10312
235 */
236 public Node getFirstNode() {
237 return nodes.get(0);
238 }
239
240 /**
241 * Returns the last node.
242 * @return the last node
243 * @since 10312
244 */
245 public Node getLastNode() {
246 return nodes.get(nodes.size() - 1);
247 }
248 }
249
250 /**
251 * The polygon data for a multipolygon part.
252 * It contains the outline of this polygon in east/north space.
253 */
254 public static class PolyData extends JoinedWay {
255 /**
256 * The intersection type used for {@link PolyData#contains(java.awt.geom.Path2D.Double)}
257 */
258 public enum Intersection {
259 /**
260 * The polygon is completely inside this PolyData
261 */
262 INSIDE,
263 /**
264 * The polygon is completely outside of this PolyData
265 */
266 OUTSIDE,
267 /**
268 * The polygon is partially inside and outside of this PolyData
269 */
270 CROSSING
271 }
272
273 private final Path2D.Double poly;
274 private Rectangle2D bounds;
275 private final List<PolyData> inners;
276
277 /**
278 * Constructs a new {@code PolyData} from a closed way.
279 * @param closedWay closed way
280 */
281 public PolyData(Way closedWay) {
282 this(closedWay.getNodes(), closedWay.isSelected(), Collections.singleton(closedWay.getUniqueId()));
283 }
284
285 /**
286 * Constructs a new {@code PolyData} from a {@link JoinedWay}.
287 * @param joinedWay joined way
288 */
289 public PolyData(JoinedWay joinedWay) {
290 this(joinedWay.nodes, joinedWay.selected, joinedWay.wayIds);
291 }
292
293 private PolyData(List<Node> nodes, boolean selected, Collection<Long> wayIds) {
294 super(nodes, wayIds, selected);
295 this.inners = new ArrayList<>();
296 this.poly = new Path2D.Double();
297 this.poly.setWindingRule(Path2D.WIND_EVEN_ODD);
298 buildPoly();
299 }
300
301 /**
302 * Constructs a new {@code PolyData} from an existing {@code PolyData}.
303 * @param copy existing instance
304 */
305 public PolyData(PolyData copy) {
306 super(copy.nodes, copy.wayIds, copy.selected);
307 this.poly = (Path2D.Double) copy.poly.clone();
308 this.inners = new ArrayList<>(copy.inners);
309 }
310
311 private void buildPoly() {
312 boolean initial = true;
313 for (Node n : nodes) {
314 EastNorth p = n.getEastNorth();
315 if (p != null) {
316 if (initial) {
317 poly.moveTo(p.getX(), p.getY());
318 initial = false;
319 } else {
320 poly.lineTo(p.getX(), p.getY());
321 }
322 }
323 }
324 if (nodes.size() >= 3 && nodes.get(0) == nodes.get(nodes.size() - 1)) {
325 poly.closePath();
326 }
327 for (PolyData inner : inners) {
328 appendInner(inner.poly);
329 }
330 }
331
332 /**
333 * Checks if this multipolygon contains or crosses an other polygon
334 * @param p The path to check. Needs to be in east/north space.
335 * @return a {@link Intersection} constant
336 */
337 public Intersection contains(Path2D.Double p) {
338 int contains = 0;
339 int total = 0;
340 double[] coords = new double[6];
341 for (PathIterator it = p.getPathIterator(null); !it.isDone(); it.next()) {
342 switch (it.currentSegment(coords)) {
343 case PathIterator.SEG_MOVETO:
344 case PathIterator.SEG_LINETO:
345 if (poly.contains(coords[0], coords[1])) {
346 contains++;
347 }
348 total++;
349 break;
350 default: // Do nothing
351 }
352 }
353 if (contains == total) return Intersection.INSIDE;
354 if (contains == 0) return Intersection.OUTSIDE;
355 return Intersection.CROSSING;
356 }
357
358 /**
359 * Adds an inner polygon
360 * @param inner The polygon to add as inner polygon.
361 */
362 public void addInner(PolyData inner) {
363 inners.add(inner);
364 appendInner(inner.poly);
365 }
366
367 private void appendInner(Path2D.Double inner) {
368 poly.append(inner.getPathIterator(null), false);
369 }
370
371 /**
372 * Gets the polygon outline and interior as java path
373 * @return The path in east/north space.
374 */
375 public Path2D.Double get() {
376 return poly;
377 }
378
379 /**
380 * Gets the bounds as {@link Rectangle2D} in east/north space.
381 * @return The bounds
382 */
383 public Rectangle2D getBounds() {
384 if (bounds == null) {
385 bounds = poly.getBounds2D();
386 }
387 return bounds;
388 }
389
390 /**
391 * Gets a list of all inner polygons.
392 * @return The inner polygons.
393 */
394 public List<PolyData> getInners() {
395 return Collections.unmodifiableList(inners);
396 }
397
398 private void resetNodes(DataSet dataSet) {
399 if (!nodes.isEmpty()) {
400 DataSet ds = dataSet;
401 // Find DataSet (can be null for several nodes when undoing nodes creation, see #7162)
402 for (Iterator<Node> it = nodes.iterator(); it.hasNext() && ds == null;) {
403 ds = it.next().getDataSet();
404 }
405 nodes.clear();
406 if (ds == null) {
407 // DataSet still not found. This should not happen, but a warning does no harm
408 Logging.warn("DataSet not found while resetting nodes in Multipolygon. " +
409 "This should not happen, you may report it to JOSM developers.");
410 } else if (wayIds.size() == 1) {
411 Way w = (Way) ds.getPrimitiveById(wayIds.iterator().next(), OsmPrimitiveType.WAY);
412 nodes.addAll(w.getNodes());
413 } else if (!wayIds.isEmpty()) {
414 List<Way> waysToJoin = new ArrayList<>();
415 for (Long wayId : wayIds) {
416 Way w = (Way) ds.getPrimitiveById(wayId, OsmPrimitiveType.WAY);
417 if (w != null && w.getNodesCount() > 0) { // fix #7173 (empty ways on purge)
418 waysToJoin.add(w);
419 }
420 }
421 if (!waysToJoin.isEmpty()) {
422 nodes.addAll(joinWays(waysToJoin).iterator().next().getNodes());
423 }
424 }
425 resetPoly();
426 }
427 }
428
429 private void resetPoly() {
430 poly.reset();
431 buildPoly();
432 bounds = null;
433 }
434
435 /**
436 * Check if this polygon was changed by a node move
437 * @param event The node move event
438 */
439 public void nodeMoved(NodeMovedEvent event) {
440 final Node n = event.getNode();
441 boolean innerChanged = false;
442 for (PolyData inner : inners) {
443 if (inner.nodes.contains(n)) {
444 inner.resetPoly();
445 innerChanged = true;
446 }
447 }
448 if (nodes.contains(n) || innerChanged) {
449 resetPoly();
450 }
451 }
452
453 /**
454 * Check if this polygon was affected by a way change
455 * @param event The way event
456 */
457 public void wayNodesChanged(WayNodesChangedEvent event) {
458 final Long wayId = event.getChangedWay().getUniqueId();
459 boolean innerChanged = false;
460 for (PolyData inner : inners) {
461 if (inner.wayIds.contains(wayId)) {
462 inner.resetNodes(event.getDataset());
463 innerChanged = true;
464 }
465 }
466 if (wayIds.contains(wayId) || innerChanged) {
467 resetNodes(event.getDataset());
468 }
469 }
470
471 @Override
472 public boolean isClosed() {
473 if (nodes.size() < 3 || !getFirstNode().equals(getLastNode()))
474 return false;
475 for (PolyData inner : inners) {
476 if (!inner.isClosed())
477 return false;
478 }
479 return true;
480 }
481
482 /**
483 * Calculate area and perimeter length in the given projection.
484 *
485 * @param projection the projection to use for the calculation, {@code null} defaults to {@link Main#getProjection()}
486 * @return area and perimeter
487 */
488 public AreaAndPerimeter getAreaAndPerimeter(Projection projection) {
489 AreaAndPerimeter ap = Geometry.getAreaAndPerimeter(nodes, projection);
490 double area = ap.getArea();
491 double perimeter = ap.getPerimeter();
492 for (PolyData inner : inners) {
493 AreaAndPerimeter apInner = inner.getAreaAndPerimeter(projection);
494 area -= apInner.getArea();
495 perimeter += apInner.getPerimeter();
496 }
497 return new AreaAndPerimeter(area, perimeter);
498 }
499 }
500
501 private final List<Way> innerWays = new ArrayList<>();
502 private final List<Way> outerWays = new ArrayList<>();
503 private final List<PolyData> combinedPolygons = new ArrayList<>();
504 private final List<Node> openEnds = new ArrayList<>();
505
506 private boolean incomplete;
507
508 /**
509 * Constructs a new {@code Multipolygon} from a relation.
510 * @param r relation
511 */
512 public Multipolygon(Relation r) {
513 load(r);
514 }
515
516 private void load(Relation r) {
517 MultipolygonRoleMatcher matcher = getMultipolygonRoleMatcher();
518
519 // Fill inner and outer list with valid ways
520 for (RelationMember m : r.getMembers()) {
521 if (m.getMember().isIncomplete()) {
522 this.incomplete = true;
523 } else if (m.getMember().isDrawable() && m.isWay()) {
524 Way w = m.getWay();
525
526 if (w.getNodesCount() < 2) {
527 continue;
528 }
529
530 if (matcher.isInnerRole(m.getRole())) {
531 innerWays.add(w);
532 } else if (!m.hasRole() || matcher.isOuterRole(m.getRole())) {
533 outerWays.add(w);
534 } // Remaining roles ignored
535 } // Non ways ignored
536 }
537
538 final List<PolyData> innerPolygons = new ArrayList<>();
539 final List<PolyData> outerPolygons = new ArrayList<>();
540 createPolygons(innerWays, innerPolygons);
541 createPolygons(outerWays, outerPolygons);
542 if (!outerPolygons.isEmpty()) {
543 addInnerToOuters(innerPolygons, outerPolygons);
544 }
545 }
546
547 /**
548 * Determines if this multipolygon is incomplete.
549 * @return {@code true} if this multipolygon is incomplete
550 */
551 public final boolean isIncomplete() {
552 return incomplete;
553 }
554
555 private void createPolygons(List<Way> ways, List<PolyData> result) {
556 List<Way> waysToJoin = new ArrayList<>();
557 for (Way way: ways) {
558 if (way.isClosed()) {
559 result.add(new PolyData(way));
560 } else {
561 waysToJoin.add(way);
562 }
563 }
564
565 for (JoinedWay jw: joinWays(waysToJoin)) {
566 result.add(new PolyData(jw));
567 if (!jw.isClosed()) {
568 openEnds.add(jw.getFirstNode());
569 openEnds.add(jw.getLastNode());
570 }
571 }
572 }
573
574 /**
575 * Attempt to combine the ways in the list if they share common end nodes
576 * @param waysToJoin The ways to join
577 * @return A collection of {@link JoinedWay} objects indicating the possible join of those ways
578 */
579 public static Collection<JoinedWay> joinWays(Collection<Way> waysToJoin) {
580 final Collection<JoinedWay> result = new ArrayList<>();
581 final Way[] joinArray = waysToJoin.toArray(new Way[waysToJoin.size()]);
582 int left = waysToJoin.size();
583 while (left > 0) {
584 Way w = null;
585 boolean selected = false;
586 List<Node> nodes = null;
587 Set<Long> wayIds = new HashSet<>();
588 boolean joined = true;
589 while (joined && left > 0) {
590 joined = false;
591 for (int i = 0; i < joinArray.length && left != 0; ++i) {
592 if (joinArray[i] != null) {
593 Way c = joinArray[i];
594 if (c.getNodesCount() == 0) {
595 continue;
596 }
597 if (w == null) {
598 w = c;
599 selected = w.isSelected();
600 joinArray[i] = null;
601 --left;
602 } else {
603 int mode = 0;
604 int cl = c.getNodesCount()-1;
605 int nl;
606 if (nodes == null) {
607 nl = w.getNodesCount()-1;
608 if (w.getNode(nl) == c.getNode(0)) {
609 mode = 21;
610 } else if (w.getNode(nl) == c.getNode(cl)) {
611 mode = 22;
612 } else if (w.getNode(0) == c.getNode(0)) {
613 mode = 11;
614 } else if (w.getNode(0) == c.getNode(cl)) {
615 mode = 12;
616 }
617 } else {
618 nl = nodes.size()-1;
619 if (nodes.get(nl) == c.getNode(0)) {
620 mode = 21;
621 } else if (nodes.get(0) == c.getNode(cl)) {
622 mode = 12;
623 } else if (nodes.get(0) == c.getNode(0)) {
624 mode = 11;
625 } else if (nodes.get(nl) == c.getNode(cl)) {
626 mode = 22;
627 }
628 }
629 if (mode != 0) {
630 joinArray[i] = null;
631 joined = true;
632 if (c.isSelected()) {
633 selected = true;
634 }
635 --left;
636 if (nodes == null) {
637 nodes = w.getNodes();
638 wayIds.add(w.getUniqueId());
639 }
640 nodes.remove((mode == 21 || mode == 22) ? nl : 0);
641 if (mode == 21) {
642 nodes.addAll(c.getNodes());
643 } else if (mode == 12) {
644 nodes.addAll(0, c.getNodes());
645 } else if (mode == 22) {
646 for (Node node : c.getNodes()) {
647 nodes.add(nl, node);
648 }
649 } else /* mode == 11 */ {
650 for (Node node : c.getNodes()) {
651 nodes.add(0, node);
652 }
653 }
654 wayIds.add(c.getUniqueId());
655 }
656 }
657 }
658 }
659 }
660
661 if (nodes == null && w != null) {
662 nodes = w.getNodes();
663 wayIds.add(w.getUniqueId());
664 }
665
666 if (nodes != null) {
667 result.add(new JoinedWay(nodes, wayIds, selected));
668 }
669 }
670
671 return result;
672 }
673
674 /**
675 * Find a matching outer polygon for the inner one
676 * @param inner The inner polygon to search the outer for
677 * @param outerPolygons The possible outer polygons
678 * @return The outer polygon that was found or <code>null</code> if none was found.
679 */
680 public PolyData findOuterPolygon(PolyData inner, List<PolyData> outerPolygons) {
681 // First try to test only bbox, use precise testing only if we don't get unique result
682 Rectangle2D innerBox = inner.getBounds();
683 PolyData insidePolygon = null;
684 PolyData intersectingPolygon = null;
685 int insideCount = 0;
686 int intersectingCount = 0;
687
688 for (PolyData outer: outerPolygons) {
689 if (outer.getBounds().contains(innerBox)) {
690 insidePolygon = outer;
691 insideCount++;
692 } else if (outer.getBounds().intersects(innerBox)) {
693 intersectingPolygon = outer;
694 intersectingCount++;
695 }
696 }
697
698 if (insideCount == 1)
699 return insidePolygon;
700 else if (intersectingCount == 1)
701 return intersectingPolygon;
702
703 PolyData result = null;
704 for (PolyData combined : outerPolygons) {
705 if (combined.contains(inner.poly) != Intersection.OUTSIDE
706 && (result == null || result.contains(combined.poly) == Intersection.INSIDE)) {
707 result = combined;
708 }
709 }
710 return result;
711 }
712
713 private void addInnerToOuters(List<PolyData> innerPolygons, List<PolyData> outerPolygons) {
714 if (innerPolygons.isEmpty()) {
715 combinedPolygons.addAll(outerPolygons);
716 } else if (outerPolygons.size() == 1) {
717 PolyData combinedOuter = new PolyData(outerPolygons.get(0));
718 for (PolyData inner: innerPolygons) {
719 combinedOuter.addInner(inner);
720 }
721 combinedPolygons.add(combinedOuter);
722 } else {
723 for (PolyData outer: outerPolygons) {
724 combinedPolygons.add(new PolyData(outer));
725 }
726
727 for (PolyData pdInner: innerPolygons) {
728 Optional.ofNullable(findOuterPolygon(pdInner, combinedPolygons)).orElseGet(() -> outerPolygons.get(0))
729 .addInner(pdInner);
730 }
731 }
732 }
733
734 /**
735 * Replies the list of outer ways.
736 * @return the list of outer ways
737 */
738 public List<Way> getOuterWays() {
739 return Collections.unmodifiableList(outerWays);
740 }
741
742 /**
743 * Replies the list of inner ways.
744 * @return the list of inner ways
745 */
746 public List<Way> getInnerWays() {
747 return Collections.unmodifiableList(innerWays);
748 }
749
750 /**
751 * Replies the list of combined polygons.
752 * @return the list of combined polygons
753 */
754 public List<PolyData> getCombinedPolygons() {
755 return Collections.unmodifiableList(combinedPolygons);
756 }
757
758 /**
759 * Replies the list of inner polygons.
760 * @return the list of inner polygons
761 */
762 public List<PolyData> getInnerPolygons() {
763 final List<PolyData> innerPolygons = new ArrayList<>();
764 createPolygons(innerWays, innerPolygons);
765 return innerPolygons;
766 }
767
768 /**
769 * Replies the list of outer polygons.
770 * @return the list of outer polygons
771 */
772 public List<PolyData> getOuterPolygons() {
773 final List<PolyData> outerPolygons = new ArrayList<>();
774 createPolygons(outerWays, outerPolygons);
775 return outerPolygons;
776 }
777
778 /**
779 * Returns the start and end node of non-closed rings.
780 * @return the start and end node of non-closed rings.
781 */
782 public List<Node> getOpenEnds() {
783 return Collections.unmodifiableList(openEnds);
784 }
785}
Note: See TracBrowser for help on using the repository browser.