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

Last change on this file since 3715 was 3630, checked in by bastiK, 14 years ago

applied #5552 (patch by extropy) - Delete performance improvement

  • Property svn:eol-style set to native
File size: 14.5 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 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 public 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(Set<? extends OsmPrimitive> selection) {
297 if (isIncomplete()) return;
298 boolean locked = writeLock();
299 try {
300 boolean closed = (lastNode() == firstNode() && selection.contains(lastNode()));
301 List<Node> copy = new ArrayList<Node>();
302
303 for (Node n: nodes) {
304 if (!selection.contains(n)) {
305 copy.add(n);
306 }
307 }
308
309 int i = copy.size();
310 if (closed && i > 2) {
311 copy.add(copy.get(0));
312 } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
313 copy.remove(i-1);
314 }
315 setNodes(copy);
316 } finally {
317 writeUnlock(locked);
318 }
319 }
320
321 /**
322 * Adds a node to the end of the list of nodes. Ignored, if n is null.
323 *
324 * @param n the node. Ignored, if null.
325 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
326 * to an incomplete way
327 */
328 public void addNode(Node n) throws IllegalStateException {
329 if (n==null) return;
330
331 boolean locked = writeLock();
332 try {
333 if (isIncomplete())
334 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
335 clearCached();
336 n.addReferrer(this);
337 Node[] newNodes = new Node[nodes.length + 1];
338 System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
339 newNodes[nodes.length] = n;
340 nodes = newNodes;
341 fireNodesChanged();
342 } finally {
343 writeUnlock(locked);
344 }
345 }
346
347 /**
348 * Adds a node at position offs.
349 *
350 * @param int offs the offset
351 * @param n the node. Ignored, if null.
352 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
353 * to an incomplete way
354 * @throws IndexOutOfBoundsException thrown if offs is out of bounds
355 */
356 public void addNode(int offs, Node n) throws IllegalStateException, IndexOutOfBoundsException {
357 if (n==null) return;
358
359 boolean locked = writeLock();
360 try {
361 if (isIncomplete())
362 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
363
364 clearCached();
365 n.addReferrer(this);
366 Node[] newNodes = new Node[nodes.length + 1];
367 System.arraycopy(nodes, 0, newNodes, 0, offs);
368 System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
369 newNodes[offs] = n;
370 nodes = newNodes;
371 fireNodesChanged();
372 } finally {
373 writeUnlock(locked);
374 }
375 }
376
377 @Override
378 public void setDeleted(boolean deleted) {
379 boolean locked = writeLock();
380 try {
381 for (Node n:nodes) {
382 if (deleted) {
383 n.removeReferrer(this);
384 } else {
385 n.addReferrer(this);
386 }
387 }
388 fireNodesChanged();
389 super.setDeleted(deleted);
390 } finally {
391 writeUnlock(locked);
392 }
393 }
394
395 public boolean isClosed() {
396 if (isIncomplete()) return false;
397
398 Node[] nodes = this.nodes;
399 return nodes.length >= 3 && nodes[nodes.length-1] == nodes[0];
400 }
401
402 public Node lastNode() {
403 Node[] nodes = this.nodes;
404 if (isIncomplete() || nodes.length == 0) return null;
405 return nodes[nodes.length-1];
406 }
407
408 public Node firstNode() {
409 Node[] nodes = this.nodes;
410 if (isIncomplete() || nodes.length == 0) return null;
411 return nodes[0];
412 }
413
414 public boolean isFirstLastNode(Node n) {
415 Node[] nodes = this.nodes;
416 if (isIncomplete() || nodes.length == 0) return false;
417 return n == nodes[0] || n == nodes[nodes.length -1];
418 }
419
420 public boolean isInnerNode(Node n) {
421 Node[] nodes = this.nodes;
422 if (isIncomplete() || nodes.length <= 2) return false;
423 /* circular ways have only inner nodes, so return true for them! */
424 if (n == nodes[0] && n == nodes[nodes.length-1]) return true;
425 for(int i = 1; i < nodes.length - 1; ++i) {
426 if(nodes[i] == n) return true;
427 }
428 return false;
429 }
430
431
432 @Override
433 public String getDisplayName(NameFormatter formatter) {
434 return formatter.format(this);
435 }
436
437 public OsmPrimitiveType getType() {
438 return OsmPrimitiveType.WAY;
439 }
440
441 private void checkNodes() {
442 DataSet dataSet = getDataSet();
443 if (dataSet != null) {
444 Node[] nodes = this.nodes;
445 for (Node n: nodes) {
446 if (n.getDataSet() != dataSet)
447 throw new DataIntegrityProblemException("Nodes in way must be in the same dataset");
448 if (n.isDeleted())
449 throw new DataIntegrityProblemException("Deleted node referenced: " + toString());
450 }
451 if (Main.pref.getBoolean("debug.checkNullCoor", true)) {
452 for (Node n: nodes) {
453 if (!n.isIncomplete() && (n.getCoor() == null || n.getEastNorth() == null))
454 throw new DataIntegrityProblemException("Complete node with null coordinates: " + toString() + n.get3892DebugInfo());
455 }
456 }
457 }
458 }
459
460 private void fireNodesChanged() {
461 checkNodes();
462 if (getDataSet() != null) {
463 getDataSet().fireWayNodesChanged(this);
464 }
465 }
466
467 @Override
468 public void setDataset(DataSet dataSet) {
469 super.setDataset(dataSet);
470 checkNodes();
471 }
472
473 @Override
474 public BBox getBBox() {
475 if (getDataSet() == null)
476 return new BBox(this);
477 if (bbox == null) {
478 bbox = new BBox(this);
479 }
480 return new BBox(bbox);
481 }
482
483 @Override
484 public void updatePosition() {
485 bbox = new BBox(this);
486 }
487
488 public boolean hasIncompleteNodes() {
489 Node[] nodes = this.nodes;
490 for (Node node:nodes) {
491 if (node.isIncomplete())
492 return true;
493 }
494 return false;
495 }
496
497 @Override
498 public boolean isUsable() {
499 return super.isUsable() && !hasIncompleteNodes();
500 }
501
502 @Override
503 public boolean isDrawable() {
504 return super.isDrawable() && !hasIncompleteNodes();
505 }
506}
Note: See TracBrowser for help on using the repository browser.