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

Last change on this file since 5059 was 5059, checked in by simon04, 12 years ago

fix #6561 - fix several overflowing dialog texts

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