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

Last change on this file since 3348 was 3348, checked in by jttt, 14 years ago

Thread safe Dataset and OsmPrimitive, read/write lock for dataset

  • Property svn:eol-style set to native
File size: 13.7 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.Collection;
9import java.util.List;
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 clearCached();
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 /* mappaint data */
115 public boolean isMappaintArea = false;
116 public Integer mappaintDrawnAreaCode = 0;
117 /* end of mappaint data */
118 @Override protected void clearCached() {
119 super.clearCached();
120 isMappaintArea = false;
121 mappaintDrawnAreaCode = 0;
122 }
123
124 public List<Pair<Node,Node>> getNodePairs(boolean sort) {
125 List<Pair<Node,Node>> chunkSet = new ArrayList<Pair<Node,Node>>();
126 if (isIncomplete()) return chunkSet;
127 Node lastN = null;
128 Node[] nodes = this.nodes;
129 for (Node n : nodes) {
130 if (lastN == null) {
131 lastN = n;
132 continue;
133 }
134 Pair<Node,Node> np = new Pair<Node,Node>(lastN, n);
135 if (sort) {
136 Pair.sort(np);
137 }
138 chunkSet.add(np);
139 lastN = n;
140 }
141 return chunkSet;
142 }
143
144 @Override public void visit(Visitor visitor) {
145 visitor.visit(this);
146 }
147
148 protected Way(long id, boolean allowNegative) {
149 super(id, allowNegative);
150 }
151
152 /**
153 * Creates a new way with id 0.
154 *
155 */
156 public Way(){
157 super(0, false);
158 }
159
160 /**
161 *
162 * @param original
163 * @param clearId
164 */
165 public Way(Way original, boolean clearId) {
166 super(original.getUniqueId(), true);
167 cloneFrom(original);
168 if (clearId) {
169 clearOsmId();
170 }
171 }
172
173 /**
174 * Create an identical clone of the argument (including the id).
175 *
176 * @param original the original way. Must not be null.
177 */
178 public Way(Way original) {
179 this(original, false);
180 }
181
182 /**
183 * Creates a new way for the given id. If the id > 0, the way is marked
184 * as incomplete. If id == 0 then way is marked as new
185 *
186 * @param id the id. >= 0 required
187 * @throws IllegalArgumentException thrown if id < 0
188 */
189 public Way(long id) throws IllegalArgumentException {
190 super(id, false);
191 }
192
193 /**
194 * Creates new way with given id and version.
195 * @param id
196 * @param version
197 */
198 public Way(long id, int version) {
199 super(id, version, false);
200 }
201
202 /**
203 *
204 * @param data
205 */
206 @Override
207 public void load(PrimitiveData data) {
208 boolean locked = writeLock();
209 try {
210 super.load(data);
211
212 WayData wayData = (WayData) data;
213
214 List<Node> newNodes = new ArrayList<Node>(wayData.getNodes().size());
215 for (Long nodeId : wayData.getNodes()) {
216 Node node = (Node)getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE);
217 if (node != null) {
218 newNodes.add(node);
219 } else
220 throw new AssertionError("Data consistency problem - way with missing node detected");
221 }
222 setNodes(newNodes);
223 } finally {
224 writeUnlock(locked);
225 }
226 }
227
228 @Override public WayData save() {
229 WayData data = new WayData();
230 saveCommonAttributes(data);
231 for (Node node:nodes) {
232 data.getNodes().add(node.getUniqueId());
233 }
234 return data;
235 }
236
237 @Override public void cloneFrom(OsmPrimitive osm) {
238 boolean locked = writeLock();
239 try {
240 super.cloneFrom(osm);
241 Way otherWay = (Way)osm;
242 setNodes(otherWay.getNodes());
243 } finally {
244 writeUnlock(locked);
245 }
246 }
247
248 @Override public String toString() {
249 String nodesDesc = isIncomplete()?"(incomplete)":"nodes=" + Arrays.toString(nodes);
250 return "{Way id=" + getUniqueId() + " version=" + getVersion()+ " " + getFlagsAsString() + " " + nodesDesc + "}";
251 }
252
253 @Override
254 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
255 if (other == null || ! (other instanceof Way) )
256 return false;
257 if (! super.hasEqualSemanticAttributes(other))
258 return false;
259 Way w = (Way)other;
260 if (getNodesCount() != w.getNodesCount()) return false;
261 for (int i=0;i<getNodesCount();i++) {
262 if (! getNode(i).hasEqualSemanticAttributes(w.getNode(i)))
263 return false;
264 }
265 return true;
266 }
267
268 public int compareTo(OsmPrimitive o) {
269 if (o instanceof Relation)
270 return 1;
271 return o instanceof Way ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
272 }
273
274 public void removeNode(Node n) {
275 if (isIncomplete()) return;
276 boolean locked = writeLock();
277 try {
278 boolean closed = (lastNode() == n && firstNode() == n);
279 int i;
280 List<Node> copy = getNodes();
281 while ((i = copy.indexOf(n)) >= 0) {
282 copy.remove(i);
283 }
284 i = copy.size();
285 if (closed && i > 2) {
286 copy.add(copy.get(0));
287 } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
288 copy.remove(i-1);
289 }
290 setNodes(copy);
291 } finally {
292 writeUnlock(locked);
293 }
294 }
295
296 public void removeNodes(Collection<? extends OsmPrimitive> selection) {
297 if (isIncomplete()) return;
298 boolean locked = writeLock();
299 try {
300 for(OsmPrimitive p : selection) {
301 if (p instanceof Node) {
302 removeNode((Node)p);
303 }
304 }
305 } finally {
306 writeUnlock(locked);
307 }
308 }
309
310 /**
311 * Adds a node to the end of the list of nodes. Ignored, if n is null.
312 *
313 * @param n the node. Ignored, if null.
314 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
315 * to an incomplete way
316 */
317 public void addNode(Node n) throws IllegalStateException {
318 if (n==null) return;
319
320 boolean locked = writeLock();
321 try {
322 if (isIncomplete())
323 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
324 clearCached();
325 n.addReferrer(this);
326 Node[] newNodes = new Node[nodes.length + 1];
327 System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
328 newNodes[nodes.length] = n;
329 nodes = newNodes;
330 fireNodesChanged();
331 } finally {
332 writeUnlock(locked);
333 }
334 }
335
336 /**
337 * Adds a node at position offs.
338 *
339 * @param int offs the offset
340 * @param n the node. Ignored, if null.
341 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
342 * to an incomplete way
343 * @throws IndexOutOfBoundsException thrown if offs is out of bounds
344 */
345 public void addNode(int offs, Node n) throws IllegalStateException, IndexOutOfBoundsException {
346 if (n==null) return;
347
348 boolean locked = writeLock();
349 try {
350 if (isIncomplete())
351 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
352
353 clearCached();
354 n.addReferrer(this);
355 Node[] newNodes = new Node[nodes.length + 1];
356 System.arraycopy(nodes, 0, newNodes, 0, offs);
357 System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
358 newNodes[offs] = n;
359 nodes = newNodes;
360 fireNodesChanged();
361 } finally {
362 writeUnlock(locked);
363 }
364 }
365
366 @Override
367 public void setDeleted(boolean deleted) {
368 boolean locked = writeLock();
369 try {
370 for (Node n:nodes) {
371 if (deleted) {
372 n.removeReferrer(this);
373 } else {
374 n.addReferrer(this);
375 }
376 }
377 fireNodesChanged();
378 super.setDeleted(deleted);
379 } finally {
380 writeUnlock(locked);
381 }
382 }
383
384 public boolean isClosed() {
385 if (isIncomplete()) return false;
386
387 Node[] nodes = this.nodes;
388 return nodes.length >= 3 && nodes[nodes.length-1] == nodes[0];
389 }
390
391 public Node lastNode() {
392 Node[] nodes = this.nodes;
393 if (isIncomplete() || nodes.length == 0) return null;
394 return nodes[nodes.length-1];
395 }
396
397 public Node firstNode() {
398 Node[] nodes = this.nodes;
399 if (isIncomplete() || nodes.length == 0) return null;
400 return nodes[0];
401 }
402
403 public boolean isFirstLastNode(Node n) {
404 Node[] nodes = this.nodes;
405 if (isIncomplete() || nodes.length == 0) return false;
406 return n == nodes[0] || n == nodes[nodes.length -1];
407 }
408
409 @Override
410 public String getDisplayName(NameFormatter formatter) {
411 return formatter.format(this);
412 }
413
414 public OsmPrimitiveType getType() {
415 return OsmPrimitiveType.WAY;
416 }
417
418 private void checkNodes() {
419 DataSet dataSet = getDataSet();
420 if (dataSet != null) {
421 Node[] nodes = this.nodes;
422 for (Node n: nodes) {
423 if (n.getDataSet() != dataSet)
424 throw new DataIntegrityProblemException("Nodes in way must be in the same dataset");
425 if (n.isDeleted())
426 throw new DataIntegrityProblemException("Deleted node referenced: " + toString());
427 }
428 if (Main.pref.getBoolean("debug.checkNullCoor", true)) {
429 for (Node n: nodes) {
430 if (!n.isIncomplete() && (n.getCoor() == null || n.getEastNorth() == null))
431 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString() + n.get3892DebugInfo());
432 }
433 }
434 }
435 }
436
437 private void fireNodesChanged() {
438 checkNodes();
439 if (getDataSet() != null) {
440 getDataSet().fireWayNodesChanged(this);
441 }
442 }
443
444 @Override
445 public void setDataset(DataSet dataSet) {
446 super.setDataset(dataSet);
447 checkNodes();
448 }
449
450 @Override
451 public BBox getBBox() {
452 if (getDataSet() == null)
453 return new BBox(this);
454 if (bbox == null) {
455 bbox = new BBox(this);
456 }
457 return new BBox(bbox);
458 }
459
460 @Override
461 public void updatePosition() {
462 bbox = new BBox(this);
463 }
464
465 public boolean hasIncompleteNodes() {
466 Node[] nodes = this.nodes;
467 for (Node node:nodes) {
468 if (node.isIncomplete())
469 return true;
470 }
471 return false;
472 }
473
474 @Override
475 public boolean isUsable() {
476 return super.isUsable() && !hasIncompleteNodes();
477 }
478
479 @Override
480 public boolean isDrawable() {
481 return super.isDrawable() && !hasIncompleteNodes();
482 }
483}
Note: See TracBrowser for help on using the repository browser.