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

Last change on this file since 3943 was 3943, checked in by bastiK, 13 years ago

fixed #6040 - JOSM doesn't display mappaint colours and style for newly created multipolygons (any more)

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