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

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

Return copy of bbox in Way.getBBox (to make sure internal copy won't be modified)

  • Property svn:eol-style set to native
File size: 12.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.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 for (Node node:this.nodes) {
50 node.removeReferrer(this);
51 }
52
53 if (nodes == null) {
54 this.nodes = new Node[0];
55 } else {
56 this.nodes = nodes.toArray(new Node[nodes.size()]);
57 }
58 for (Node node:this.nodes) {
59 node.addReferrer(this);
60 }
61
62 clearCached();
63 fireNodesChanged();
64 }
65
66 /**
67 * Replies the number of nodes in this ways.
68 *
69 * @return the number of nodes in this ways.
70 * @since 1862
71 */
72 public int getNodesCount() {
73 return nodes.length;
74 }
75
76 /**
77 * Replies the node at position <code>index</code>.
78 *
79 * @param index the position
80 * @return the node at position <code>index</code>
81 * @exception IndexOutOfBoundsException thrown if <code>index</code> < 0
82 * or <code>index</code> >= {@see #getNodesCount()}
83 * @since 1862
84 */
85 public Node getNode(int index) {
86 return nodes[index];
87 }
88
89 /**
90 * Replies true if this way contains the node <code>node</code>, false
91 * otherwise. Replies false if <code>node</code> is null.
92 *
93 * @param node the node. May be null.
94 * @return true if this way contains the node <code>node</code>, false
95 * otherwise
96 * @since 1909
97 */
98 public boolean containsNode(Node node) {
99 if (node == null) return false;
100 for (int i=0; i<nodes.length; i++) {
101 if (nodes[i].equals(node))
102 return true;
103 }
104 return false;
105 }
106
107 /* mappaint data */
108 public boolean isMappaintArea = false;
109 public Integer mappaintDrawnAreaCode = 0;
110 /* end of mappaint data */
111 @Override protected void clearCached() {
112 super.clearCached();
113 isMappaintArea = false;
114 mappaintDrawnAreaCode = 0;
115 }
116
117 public ArrayList<Pair<Node,Node>> getNodePairs(boolean sort) {
118 ArrayList<Pair<Node,Node>> chunkSet = new ArrayList<Pair<Node,Node>>();
119 if (isIncomplete()) return chunkSet;
120 Node lastN = null;
121 for (Node n : this.nodes) {
122 if (lastN == null) {
123 lastN = n;
124 continue;
125 }
126 Pair<Node,Node> np = new Pair<Node,Node>(lastN, n);
127 if (sort) {
128 Pair.sort(np);
129 }
130 chunkSet.add(np);
131 lastN = n;
132 }
133 return chunkSet;
134 }
135
136 @Override public void visit(Visitor visitor) {
137 visitor.visit(this);
138 }
139
140 protected Way(long id, boolean allowNegative) {
141 super(id, allowNegative);
142 }
143
144 /**
145 * Creates a new way with id 0.
146 *
147 */
148 public Way(){
149 super(0, false);
150 }
151
152 /**
153 *
154 * @param original
155 * @param clearId
156 */
157 public Way(Way original, boolean clearId) {
158 super(original.getUniqueId(), true);
159 cloneFrom(original);
160 if (clearId) {
161 clearOsmId();
162 }
163 }
164
165 /**
166 * Create an identical clone of the argument (including the id).
167 *
168 * @param original the original way. Must not be null.
169 */
170 public Way(Way original) {
171 this(original, false);
172 }
173
174 /**
175 * Creates a new way for the given id. If the id > 0, the way is marked
176 * as incomplete. If id == 0 then way is marked as new
177 *
178 * @param id the id. >= 0 required
179 * @throws IllegalArgumentException thrown if id < 0
180 */
181 public Way(long id) throws IllegalArgumentException {
182 super(id, false);
183 }
184
185 /**
186 * Creates new way with given id and version.
187 * @param id
188 * @param version
189 */
190 public Way(long id, int version) {
191 super(id, version, false);
192 }
193
194 /**
195 *
196 * @param data
197 */
198 @Override
199 public void load(PrimitiveData data) {
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 }
214
215 @Override public WayData save() {
216 WayData data = new WayData();
217 saveCommonAttributes(data);
218 for (Node node:nodes) {
219 data.getNodes().add(node.getUniqueId());
220 }
221 return data;
222 }
223
224 @Override public void cloneFrom(OsmPrimitive osm) {
225 super.cloneFrom(osm);
226 Way otherWay = (Way)osm;
227 setNodes(otherWay.getNodes());
228 }
229
230 @Override public String toString() {
231 String nodesDesc = isIncomplete()?"(incomplete)":"nodes=" + Arrays.toString(nodes);
232 return "{Way id=" + getUniqueId() + " version=" + getVersion()+ " " + getFlagsAsString() + " " + nodesDesc + "}";
233 }
234
235 @Override
236 public boolean hasEqualSemanticAttributes(OsmPrimitive other) {
237 if (other == null || ! (other instanceof Way) )
238 return false;
239 if (! super.hasEqualSemanticAttributes(other))
240 return false;
241 Way w = (Way)other;
242 if (getNodesCount() != w.getNodesCount()) return false;
243 for (int i=0;i<getNodesCount();i++) {
244 if (! getNode(i).hasEqualSemanticAttributes(w.getNode(i)))
245 return false;
246 }
247 return true;
248 }
249
250 public int compareTo(OsmPrimitive o) {
251 if (o instanceof Relation)
252 return 1;
253 return o instanceof Way ? Long.valueOf(getUniqueId()).compareTo(o.getUniqueId()) : -1;
254 }
255
256 public void removeNode(Node n) {
257 if (isIncomplete()) return;
258 boolean closed = (lastNode() == n && firstNode() == n);
259 int i;
260 List<Node> copy = getNodes();
261 while ((i = copy.indexOf(n)) >= 0) {
262 copy.remove(i);
263 }
264 i = copy.size();
265 if (closed && i > 2) {
266 copy.add(copy.get(0));
267 } else if (i >= 2 && i <= 3 && copy.get(0) == copy.get(i-1)) {
268 copy.remove(i-1);
269 }
270 setNodes(copy);
271 }
272
273 public void removeNodes(Collection<? extends OsmPrimitive> selection) {
274 if (isIncomplete()) return;
275 for(OsmPrimitive p : selection) {
276 if (p instanceof Node) {
277 removeNode((Node)p);
278 }
279 }
280 }
281
282 /**
283 * Adds a node to the end of the list of nodes. Ignored, if n is null.
284 *
285 * @param n the node. Ignored, if null.
286 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
287 * to an incomplete way
288 */
289 public void addNode(Node n) throws IllegalStateException {
290 if (n==null) return;
291 if (isIncomplete())
292 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
293 clearCached();
294 n.addReferrer(this);
295 Node[] newNodes = new Node[nodes.length + 1];
296 System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
297 newNodes[nodes.length] = n;
298 nodes = newNodes;
299 fireNodesChanged();
300 }
301
302 /**
303 * Adds a node at position offs.
304 *
305 * @param int offs the offset
306 * @param n the node. Ignored, if null.
307 * @throws IllegalStateException thrown, if this way is marked as incomplete. We can't add a node
308 * to an incomplete way
309 * @throws IndexOutOfBoundsException thrown if offs is out of bounds
310 */
311 public void addNode(int offs, Node n) throws IllegalStateException, IndexOutOfBoundsException {
312 if (n==null) return;
313 if (isIncomplete())
314 throw new IllegalStateException(tr("Cannot add node {0} to incomplete way {1}.", n.getId(), getId()));
315 clearCached();
316 n.addReferrer(this);
317 Node[] newNodes = new Node[nodes.length + 1];
318 System.arraycopy(nodes, 0, newNodes, 0, offs);
319 System.arraycopy(nodes, offs, newNodes, offs + 1, nodes.length - offs);
320 newNodes[offs] = n;
321 nodes = newNodes;
322 fireNodesChanged();
323 }
324
325 @Override
326 public void setDeleted(boolean deleted) {
327 for (Node n:nodes) {
328 if (deleted) {
329 n.removeReferrer(this);
330 } else {
331 n.addReferrer(this);
332 }
333 }
334 fireNodesChanged();
335 super.setDeleted(deleted);
336 }
337
338 public boolean isClosed() {
339 if (isIncomplete()) return false;
340 return nodes.length >= 3 && lastNode() == firstNode();
341 }
342
343 public Node lastNode() {
344 if (isIncomplete() || nodes.length == 0) return null;
345 return nodes[nodes.length-1];
346 }
347
348 public Node firstNode() {
349 if (isIncomplete() || nodes.length == 0) return null;
350 return nodes[0];
351 }
352
353 public boolean isFirstLastNode(Node n) {
354 if (isIncomplete() || nodes.length == 0) return false;
355 return n == firstNode() || n == lastNode();
356 }
357
358 @Override
359 public String getDisplayName(NameFormatter formatter) {
360 return formatter.format(this);
361 }
362
363 public OsmPrimitiveType getType() {
364 return OsmPrimitiveType.WAY;
365 }
366
367 private void checkNodes() {
368 DataSet dataSet = getDataSet();
369 if (dataSet != null) {
370 for (Node n: nodes) {
371 if (n.getDataSet() != dataSet)
372 throw new DataIntegrityProblemException("Nodes in way must be in the same dataset");
373 }
374 if (Main.pref.getBoolean("debug.checkDeleteReferenced", true)) {
375 for (Node n: nodes) {
376 if (n.isDeleted())
377 throw new DataIntegrityProblemException("Deleted node referenced: " + toString());
378 }
379 }
380 }
381 }
382
383 private void fireNodesChanged() {
384 checkNodes();
385 if (getDataSet() != null) {
386 getDataSet().fireWayNodesChanged(this);
387 }
388 }
389
390 @Override
391 public void setDataset(DataSet dataSet) {
392 super.setDataset(dataSet);
393 checkNodes();
394 }
395
396 @Override
397 public BBox getBBox() {
398 if (getDataSet() == null)
399 return new BBox(this);
400 if (bbox == null) {
401 bbox = new BBox(this);
402 }
403 return new BBox(bbox);
404 }
405
406 @Override
407 public void updatePosition() {
408 bbox = new BBox(this);
409 }
410
411 public boolean hasIncompleteNodes() {
412 for (Node node:nodes) {
413 if (node.isIncomplete())
414 return true;
415 }
416 return false;
417 }
418
419 @Override
420 public boolean isUsable() {
421 return super.isUsable() && !hasIncompleteNodes();
422 }
423
424 @Override
425 public boolean isDrawable() {
426 return super.isDrawable() && !hasIncompleteNodes();
427 }
428}
Note: See TracBrowser for help on using the repository browser.