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

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

where applicable, replace System.arraycopy by Arrays.copyOf

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