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

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

fix #9440 - Consistent definition of ways treated as areas

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