source: josm/trunk/src/org/openstreetmap/josm/data/osm/Way.java@ 13670

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

fix #16189 - Add "almost square check" for buildings (patch by marxin, modified)

  • Property svn:eol-style set to native
File size: 25.0 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.util.ArrayList;
7import java.util.Arrays;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12
13import org.openstreetmap.josm.data.coor.LatLon;
14import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor;
15import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
16import org.openstreetmap.josm.spi.preferences.Config;
17import org.openstreetmap.josm.tools.CopyList;
18import org.openstreetmap.josm.tools.Geometry;
19import org.openstreetmap.josm.tools.Pair;
20import org.openstreetmap.josm.tools.Utils;
21
22/**
23 * One full way, consisting of a list of way {@link Node nodes}.
24 *
25 * @author imi
26 * @since 64
27 */
28public final class Way extends OsmPrimitive implements IWay {
29
30 /**
31 * All way nodes in this way
32 *
33 */
34 private Node[] nodes = new Node[0];
35 private BBox bbox;
36
37 /**
38 *
39 * You can modify returned list but changes will not be propagated back
40 * to the Way. Use {@link #setNodes(List)} to update this way
41 * @return Nodes composing the way
42 * @since 1862
43 */
44 public List<Node> getNodes() {
45 return new CopyList<>(nodes);
46 }
47
48 /**
49 * Set new list of nodes to way. This method is preferred to multiple calls to addNode/removeNode
50 * and similar methods because nodes are internally saved as array which means lower memory overhead
51 * but also slower modifying operations.
52 * @param nodes New way nodes. Can be null, in that case all way nodes are removed
53 * @since 1862
54 */
55 public void setNodes(List<Node> nodes) {
56 checkDatasetNotReadOnly();
57 boolean locked = writeLock();
58 try {
59 for (Node node:this.nodes) {
60 node.removeReferrer(this);
61 node.clearCachedStyle();
62 }
63
64 if (nodes == null) {
65 this.nodes = new Node[0];
66 } else {
67 this.nodes = nodes.toArray(new Node[0]);
68 }
69 for (Node node: this.nodes) {
70 node.addReferrer(this);
71 node.clearCachedStyle();
72 }
73
74 clearCachedStyle();
75 fireNodesChanged();
76 } finally {
77 writeUnlock(locked);
78 }
79 }
80
81 /**
82 * Prevent directly following identical nodes in ways.
83 * @param nodes list of nodes
84 * @return {@code nodes} with consecutive identical nodes removed
85 */
86 private static List<Node> removeDouble(List<Node> nodes) {
87 Node last = null;
88 int count = nodes.size();
89 for (int i = 0; i < count && count > 2;) {
90 Node n = nodes.get(i);
91 if (last == n) {
92 nodes.remove(i);
93 --count;
94 } else {
95 last = n;
96 ++i;
97 }
98 }
99 return nodes;
100 }
101
102 @Override
103 public int getNodesCount() {
104 return nodes.length;
105 }
106
107 /**
108 * Replies the node at position <code>index</code>.
109 *
110 * @param index the position
111 * @return the node at position <code>index</code>
112 * @throws ArrayIndexOutOfBoundsException if <code>index</code> &lt; 0
113 * or <code>index</code> &gt;= {@link #getNodesCount()}
114 * @since 1862
115 */
116 public Node getNode(int index) {
117 return nodes[index];
118 }
119
120 @Override
121 public long getNodeId(int idx) {
122 return nodes[idx].getUniqueId();
123 }
124
125 /**
126 * Replies true if this way contains the node <code>node</code>, false
127 * otherwise. Replies false if <code>node</code> is null.
128 *
129 * @param node the node. May be null.
130 * @return true if this way contains the node <code>node</code>, false
131 * otherwise
132 * @since 1911
133 */
134 public boolean containsNode(Node node) {
135 if (node == null) return false;
136
137 Node[] nodes = this.nodes;
138 for (Node n : nodes) {
139 if (n.equals(node))
140 return true;
141 }
142 return false;
143 }
144
145 /**
146 * Return nodes adjacent to <code>node</code>
147 *
148 * @param node the node. May be null.
149 * @return Set of nodes adjacent to <code>node</code>
150 * @since 4671
151 */
152 public Set<Node> getNeighbours(Node node) {
153 Set<Node> neigh = new HashSet<>();
154
155 if (node == null) return neigh;
156
157 Node[] nodes = this.nodes;
158 for (int i = 0; i < nodes.length; i++) {
159 if (nodes[i].equals(node)) {
160 if (i > 0)
161 neigh.add(nodes[i-1]);
162 if (i < nodes.length-1)
163 neigh.add(nodes[i+1]);
164 }
165 }
166 return neigh;
167 }
168
169 /**
170 * Replies the ordered {@link List} of chunks of this way. Each chunk is replied as a {@link Pair} of {@link Node nodes}.
171 * @param sort If true, the nodes of each pair are sorted as defined by {@link Pair#sort}.
172 * If false, Pair.a and Pair.b are in the way order
173 * (i.e for a given Pair(n), Pair(n-1).b == Pair(n).a, Pair(n).b == Pair(n+1).a, etc.)
174 * @return The ordered list of chunks of this way.
175 * @since 3348
176 */
177 public List<Pair<Node, Node>> getNodePairs(boolean sort) {
178 List<Pair<Node, Node>> chunkSet = new ArrayList<>();
179 if (isIncomplete()) return chunkSet;
180 Node lastN = null;
181 Node[] nodes = this.nodes;
182 for (Node n : nodes) {
183 if (lastN == null) {
184 lastN = n;
185 continue;
186 }
187 Pair<Node, Node> np = new Pair<>(lastN, n);
188 if (sort) {
189 Pair.sort(np);
190 }
191 chunkSet.add(np);
192 lastN = n;
193 }
194 return chunkSet;
195 }
196
197 @Override public void accept(OsmPrimitiveVisitor visitor) {
198 visitor.visit(this);
199 }
200
201 @Override public void accept(PrimitiveVisitor visitor) {
202 visitor.visit(this);
203 }
204
205 protected Way(long id, boolean allowNegative) {
206 super(id, allowNegative);
207 }
208
209 /**
210 * Contructs a new {@code Way} with id 0.
211 * @since 86
212 */
213 public Way() {
214 super(0, false);
215 }
216
217 /**
218 * Contructs a new {@code Way} from an existing {@code Way}.
219 * @param original The original {@code Way} to be identically cloned. Must not be null
220 * @param clearMetadata If {@code true}, clears the OSM id and other metadata as defined by {@link #clearOsmMetadata}.
221 * If {@code false}, does nothing
222 * @since 2410
223 */
224 public Way(Way original, boolean clearMetadata) {
225 super(original.getUniqueId(), true);
226 cloneFrom(original);
227 if (clearMetadata) {
228 clearOsmMetadata();
229 }
230 }
231
232 /**
233 * Contructs a new {@code Way} from an existing {@code Way} (including its id).
234 * @param original The original {@code Way} to be identically cloned. Must not be null
235 * @since 86
236 */
237 public Way(Way original) {
238 this(original, false);
239 }
240
241 /**
242 * Contructs a new {@code Way} for the given id. If the id &gt; 0, the way is marked
243 * as incomplete. If id == 0 then way is marked as new
244 *
245 * @param id the id. &gt;= 0 required
246 * @throws IllegalArgumentException if id &lt; 0
247 * @since 343
248 */
249 public Way(long id) {
250 super(id, false);
251 }
252
253 /**
254 * Contructs a new {@code Way} with given id and version.
255 * @param id the id. &gt;= 0 required
256 * @param version the version
257 * @throws IllegalArgumentException if id &lt; 0
258 * @since 2620
259 */
260 public Way(long id, int version) {
261 super(id, version, false);
262 }
263
264 @Override
265 public void load(PrimitiveData data) {
266 if (!(data instanceof WayData))
267 throw new IllegalArgumentException("Not a way data: " + data);
268 boolean locked = writeLock();
269 try {
270 super.load(data);
271
272 WayData wayData = (WayData) data;
273
274 if (!wayData.getNodes().isEmpty() && getDataSet() == null) {
275 throw new AssertionError("Data consistency problem - way without dataset detected");
276 }
277
278 List<Node> newNodes = new ArrayList<>(wayData.getNodes().size());
279 for (Long nodeId : wayData.getNodes()) {
280 Node node = (Node) getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE);
281 if (node != null) {
282 newNodes.add(node);
283 } else {
284 throw new AssertionError("Data consistency problem - way with missing node detected");
285 }
286 }
287 setNodes(newNodes);
288 } finally {
289 writeUnlock(locked);
290 }
291 }
292
293 @Override
294 public WayData save() {
295 WayData data = new WayData();
296 saveCommonAttributes(data);
297 for (Node node:nodes) {
298 data.getNodes().add(node.getUniqueId());
299 }
300 return data;
301 }
302
303 @Override
304 public void cloneFrom(OsmPrimitive osm) {
305 if (!(osm instanceof Way))
306 throw new IllegalArgumentException("Not a way: " + osm);
307 boolean locked = writeLock();
308 try {
309 super.cloneFrom(osm);
310 Way otherWay = (Way) osm;
311 setNodes(otherWay.getNodes());
312 } finally {
313 writeUnlock(locked);
314 }
315 }
316
317 @Override
318 public String toString() {
319 String nodesDesc = isIncomplete() ? "(incomplete)" : ("nodes=" + Arrays.toString(nodes));
320 return "{Way id=" + getUniqueId() + " version=" + getVersion()+ ' ' + getFlagsAsString() + ' ' + nodesDesc + '}';
321 }
322
323 @Override
324 public boolean hasEqualSemanticAttributes(OsmPrimitive other, boolean testInterestingTagsOnly) {
325 if (!(other instanceof Way))
326 return false;
327 Way w = (Way) other;
328 if (getNodesCount() != w.getNodesCount()) return false;
329 if (!super.hasEqualSemanticAttributes(other, testInterestingTagsOnly))
330 return false;
331 for (int i = 0; i < getNodesCount(); i++) {
332 if (!getNode(i).hasEqualSemanticAttributes(w.getNode(i)))
333 return false;
334 }
335 return true;
336 }
337
338 /**
339 * Removes the given {@link Node} from this way. Ignored, if n is null.
340 * @param n The node to remove. Ignored, if null
341 * @since 1463
342 */
343 public void removeNode(Node n) {
344 checkDatasetNotReadOnly();
345 if (n == null || isIncomplete()) return;
346 boolean locked = writeLock();
347 try {
348 boolean closed = lastNode() == n && firstNode() == n;
349 int i;
350 List<Node> copy = getNodes();
351 while ((i = copy.indexOf(n)) >= 0) {
352 copy.remove(i);
353 }
354 i = copy.size();
355 if (closed && i > 2) {
356 copy.add(copy.get(0));
357 } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
358 copy.remove(i-1);
359 }
360 setNodes(removeDouble(copy));
361 n.clearCachedStyle();
362 } finally {
363 writeUnlock(locked);
364 }
365 }
366
367 /**
368 * Removes the given set of {@link Node nodes} from this way. Ignored, if selection is null.
369 * @param selection The selection of nodes to remove. Ignored, if null
370 * @since 5408
371 */
372 public void removeNodes(Set<? extends Node> selection) {
373 checkDatasetNotReadOnly();
374 if (selection == null || isIncomplete()) return;
375 boolean locked = writeLock();
376 try {
377 boolean closed = isClosed() && selection.contains(lastNode());
378 List<Node> copy = new ArrayList<>();
379
380 for (Node n: nodes) {
381 if (!selection.contains(n)) {
382 copy.add(n);
383 }
384 }
385
386 int i = copy.size();
387 if (closed && i > 2) {
388 copy.add(copy.get(0));
389 } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
390 copy.remove(i-1);
391 }
392 setNodes(removeDouble(copy));
393 for (Node n : selection) {
394 n.clearCachedStyle();
395 }
396 } finally {
397 writeUnlock(locked);
398 }
399 }
400
401 /**
402 * Adds a node to the end of the list of nodes. Ignored, if n is null.
403 *
404 * @param n the node. Ignored, if null
405 * @throws IllegalStateException if this way is marked as incomplete. We can't add a node
406 * to an incomplete way
407 * @since 1313
408 */
409 public void addNode(Node n) {
410 checkDatasetNotReadOnly();
411 if (n == null) return;
412
413 boolean locked = writeLock();
414 try {
415 if (isIncomplete())
416 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
417 clearCachedStyle();
418 n.addReferrer(this);
419 nodes = Utils.addInArrayCopy(nodes, n);
420 n.clearCachedStyle();
421 fireNodesChanged();
422 } finally {
423 writeUnlock(locked);
424 }
425 }
426
427 /**
428 * Adds a node at position offs.
429 *
430 * @param offs the offset
431 * @param n the node. Ignored, if null.
432 * @throws IllegalStateException if this way is marked as incomplete. We can't add a node
433 * to an incomplete way
434 * @throws IndexOutOfBoundsException if offs is out of bounds
435 * @since 1313
436 */
437 public void addNode(int offs, Node n) {
438 checkDatasetNotReadOnly();
439 if (n == null) return;
440
441 boolean locked = writeLock();
442 try {
443 if (isIncomplete())
444 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
445
446 clearCachedStyle();
447 n.addReferrer(this);
448 Node[] newNodes = new Node[nodes.length + 1];
449 System.arraycopy(nodes, 0, newNodes, 0, offs);
450 System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
451 newNodes[offs] = n;
452 nodes = newNodes;
453 n.clearCachedStyle();
454 fireNodesChanged();
455 } finally {
456 writeUnlock(locked);
457 }
458 }
459
460 @Override
461 public void setDeleted(boolean deleted) {
462 boolean locked = writeLock();
463 try {
464 for (Node n:nodes) {
465 if (deleted) {
466 n.removeReferrer(this);
467 } else {
468 n.addReferrer(this);
469 }
470 n.clearCachedStyle();
471 }
472 fireNodesChanged();
473 super.setDeleted(deleted);
474 } finally {
475 writeUnlock(locked);
476 }
477 }
478
479 @Override
480 public boolean isClosed() {
481 if (isIncomplete()) return false;
482
483 Node[] nodes = this.nodes;
484 return nodes.length >= 3 && nodes[nodes.length-1] == nodes[0];
485 }
486
487 /**
488 * Determines if this way denotes an area (closed way with at least three distinct nodes).
489 * @return {@code true} if this way is closed and contains at least three distinct nodes
490 * @see #isClosed
491 * @since 5490
492 */
493 public boolean isArea() {
494 if (this.nodes.length >= 4 && isClosed()) {
495 Node distinctNode = null;
496 for (int i = 1; i < nodes.length-1; i++) {
497 if (distinctNode == null && nodes[i] != nodes[0]) {
498 distinctNode = nodes[i];
499 } else if (distinctNode != null && nodes[i] != nodes[0] && nodes[i] != distinctNode) {
500 return true;
501 }
502 }
503 }
504 return false;
505 }
506
507 /**
508 * Returns the last node of this way.
509 * The result equals <code>{@link #getNode getNode}({@link #getNodesCount getNodesCount} - 1)</code>.
510 * @return the last node of this way
511 * @since 1400
512 */
513 public Node lastNode() {
514 Node[] nodes = this.nodes;
515 if (isIncomplete() || nodes.length == 0) return null;
516 return nodes[nodes.length-1];
517 }
518
519 /**
520 * Returns the first node of this way.
521 * The result equals {@link #getNode getNode}{@code (0)}.
522 * @return the first node of this way
523 * @since 1400
524 */
525 public Node firstNode() {
526 Node[] nodes = this.nodes;
527 if (isIncomplete() || nodes.length == 0) return null;
528 return nodes[0];
529 }
530
531 /**
532 * Replies true if the given node is the first or the last one of this way, false otherwise.
533 * @param n The node to test
534 * @return true if the {@code n} is the first or the last node, false otherwise.
535 * @since 1400
536 */
537 public boolean isFirstLastNode(Node n) {
538 Node[] nodes = this.nodes;
539 if (isIncomplete() || nodes.length == 0) return false;
540 return n == nodes[0] || n == nodes[nodes.length -1];
541 }
542
543 /**
544 * Replies true if the given node is an inner node of this way, false otherwise.
545 * @param n The node to test
546 * @return true if the {@code n} is an inner node, false otherwise.
547 * @since 3515
548 */
549 public boolean isInnerNode(Node n) {
550 Node[] nodes = this.nodes;
551 if (isIncomplete() || nodes.length <= 2) return false;
552 /* circular ways have only inner nodes, so return true for them! */
553 if (n == nodes[0] && n == nodes[nodes.length-1]) return true;
554 for (int i = 1; i < nodes.length - 1; ++i) {
555 if (nodes[i] == n) return true;
556 }
557 return false;
558 }
559
560 @Override
561 public OsmPrimitiveType getType() {
562 return OsmPrimitiveType.WAY;
563 }
564
565 @Override
566 public OsmPrimitiveType getDisplayType() {
567 return isClosed() ? OsmPrimitiveType.CLOSEDWAY : OsmPrimitiveType.WAY;
568 }
569
570 private void checkNodes() {
571 DataSet dataSet = getDataSet();
572 if (dataSet != null) {
573 Node[] nodes = this.nodes;
574 for (Node n: nodes) {
575 if (n.getDataSet() != dataSet)
576 throw new DataIntegrityProblemException("Nodes in way must be in the same dataset",
577 tr("Nodes in way must be in the same dataset"));
578 if (n.isDeleted())
579 throw new DataIntegrityProblemException("Deleted node referenced: " + toString(),
580 "<html>" + tr("Deleted node referenced by {0}",
581 DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(this)) + "</html>");
582 }
583 if (Config.getPref().getBoolean("debug.checkNullCoor", true)) {
584 for (Node n: nodes) {
585 if (n.isVisible() && !n.isIncomplete() && !n.isLatLonKnown())
586 throw new DataIntegrityProblemException("Complete visible node with null coordinates: " + toString(),
587 "<html>" + tr("Complete node {0} with null coordinates in way {1}",
588 DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(n),
589 DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(this)) + "</html>");
590 }
591 }
592 }
593 }
594
595 private void fireNodesChanged() {
596 checkNodes();
597 if (getDataSet() != null) {
598 getDataSet().fireWayNodesChanged(this);
599 }
600 }
601
602 @Override
603 void setDataset(DataSet dataSet) {
604 super.setDataset(dataSet);
605 checkNodes();
606 }
607
608 @Override
609 public BBox getBBox() {
610 if (getDataSet() == null)
611 return new BBox(this);
612 if (bbox == null) {
613 bbox = new BBox(this);
614 }
615 return new BBox(bbox);
616 }
617
618 @Override
619 protected void addToBBox(BBox box, Set<PrimitiveId> visited) {
620 box.add(getBBox());
621 }
622
623 @Override
624 public void updatePosition() {
625 bbox = new BBox(this);
626 }
627
628 /**
629 * Replies true if this way has incomplete nodes, false otherwise.
630 * @return true if this way has incomplete nodes, false otherwise.
631 * @since 2587
632 */
633 public boolean hasIncompleteNodes() {
634 Node[] nodes = this.nodes;
635 for (Node node : nodes) {
636 if (node.isIncomplete())
637 return true;
638 }
639 return false;
640 }
641
642 /**
643 * Replies true if all nodes of the way have known lat/lon, false otherwise.
644 * @return true if all nodes of the way have known lat/lon, false otherwise
645 * @since 13033
646 */
647 public boolean hasOnlyLocatableNodes() {
648 Node[] nodes = this.nodes;
649 for (Node node : nodes) {
650 if (!node.isLatLonKnown())
651 return false;
652 }
653 return true;
654 }
655
656 @Override
657 public boolean isUsable() {
658 return super.isUsable() && !hasIncompleteNodes();
659 }
660
661 @Override
662 public boolean isDrawable() {
663 return super.isDrawable() && hasOnlyLocatableNodes();
664 }
665
666 /**
667 * Replies the length of the way, in metres, as computed by {@link LatLon#greatCircleDistance}.
668 * @return The length of the way, in metres
669 * @since 4138
670 */
671 public double getLength() {
672 double length = 0;
673 Node lastN = null;
674 for (Node n:nodes) {
675 if (lastN != null) {
676 LatLon lastNcoor = lastN.getCoor();
677 LatLon coor = n.getCoor();
678 if (lastNcoor != null && coor != null) {
679 length += coor.greatCircleDistance(lastNcoor);
680 }
681 }
682 lastN = n;
683 }
684 return length;
685 }
686
687 /**
688 * Replies the length of the longest segment of the way, in metres, as computed by {@link LatLon#greatCircleDistance}.
689 * @return The length of the segment, in metres
690 * @since 8320
691 */
692 public double getLongestSegmentLength() {
693 double length = 0;
694 Node lastN = null;
695 for (Node n:nodes) {
696 if (lastN != null) {
697 LatLon lastNcoor = lastN.getCoor();
698 LatLon coor = n.getCoor();
699 if (lastNcoor != null && coor != null) {
700 double l = coor.greatCircleDistance(lastNcoor);
701 if (l > length) {
702 length = l;
703 }
704 }
705 }
706 lastN = n;
707 }
708 return length;
709 }
710
711 /**
712 * Tests if this way is a oneway.
713 * @return {@code 1} if the way is a oneway,
714 * {@code -1} if the way is a reversed oneway,
715 * {@code 0} otherwise.
716 * @since 5199
717 */
718 public int isOneway() {
719 String oneway = get("oneway");
720 if (oneway != null) {
721 if ("-1".equals(oneway)) {
722 return -1;
723 } else {
724 Boolean isOneway = OsmUtils.getOsmBoolean(oneway);
725 if (isOneway != null && isOneway) {
726 return 1;
727 }
728 }
729 }
730 return 0;
731 }
732
733 /**
734 * Replies the first node of this way, respecting or not its oneway state.
735 * @param respectOneway If true and if this way is a reversed oneway, replies the last node. Otherwise, replies the first node.
736 * @return the first node of this way, according to {@code respectOneway} and its oneway state.
737 * @since 5199
738 */
739 public Node firstNode(boolean respectOneway) {
740 return !respectOneway || isOneway() != -1 ? firstNode() : lastNode();
741 }
742
743 /**
744 * Replies the last node of this way, respecting or not its oneway state.
745 * @param respectOneway If true and if this way is a reversed oneway, replies the first node. Otherwise, replies the last node.
746 * @return the last node of this way, according to {@code respectOneway} and its oneway state.
747 * @since 5199
748 */
749 public Node lastNode(boolean respectOneway) {
750 return !respectOneway || isOneway() != -1 ? lastNode() : firstNode();
751 }
752
753 @Override
754 public boolean concernsArea() {
755 return hasAreaTags();
756 }
757
758 @Override
759 public boolean isOutsideDownloadArea() {
760 for (final Node n : nodes) {
761 if (n.isOutsideDownloadArea()) {
762 return true;
763 }
764 }
765 return false;
766 }
767
768 @Override
769 protected void keysChangedImpl(Map<String, String> originalKeys) {
770 super.keysChangedImpl(originalKeys);
771 clearCachedNodeStyles();
772 }
773
774 /**
775 * Clears all cached styles for all nodes of this way. This should not be called from outside.
776 * @see Node#clearCachedStyle()
777 */
778 public void clearCachedNodeStyles() {
779 for (final Node n : nodes) {
780 n.clearCachedStyle();
781 }
782 }
783
784 /**
785 * Returns angles of vertices.
786 * @return angles of the way
787 * @since 13670
788 */
789 public synchronized List<Pair<Double, Node>> getAngles() {
790 List<Pair<Double, Node>> angles = new ArrayList<>();
791
792 for (int i = 1; i < nodes.length - 1; i++) {
793 Node n0 = nodes[i - 1];
794 Node n1 = nodes[i];
795 Node n2 = nodes[i + 1];
796
797 double angle = Geometry.getNormalizedAngleInDegrees(Geometry.getCornerAngle(
798 n0.getEastNorth(), n1.getEastNorth(), n2.getEastNorth()));
799 angles.add(new Pair<>(angle, n1));
800 }
801
802 angles.add(new Pair<>(Geometry.getNormalizedAngleInDegrees(Geometry.getCornerAngle(
803 nodes[nodes.length - 2].getEastNorth(),
804 nodes[0].getEastNorth(),
805 nodes[1].getEastNorth())), nodes[0]));
806
807 return angles;
808 }
809}
Note: See TracBrowser for help on using the repository browser.