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

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

see #7934 - Only throw DataIntegrityProblemException for *visible* complete nodes without coordinates + fix javadoc of Way

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