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

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

fix Findbugs performance issues

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