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

Revision 5199, 18.1 KB checked in by simon04, 5 weeks ago (diff)

fix #7546 - add validation warning Superfluous turnrestriction as "to" way is oneway

  • Property svn:eol-style set to native
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 lastNcoor = lastN.getCoor();
584                LatLon coor = n.getCoor();
585                if (lastNcoor != null && coor != null) {
586                    length += coor.greatCircleDistance(lastNcoor);
587                }
588            }
589            lastN = n;
590        }
591        return length;
592    }
593
594    /**
595     * Tests if this way is a oneway.
596     * @return {@code 1} if the way is a oneway, {@code -1} if the way is a reversed oneway,
597     * {@code 0} otherwise.
598     */
599    public int isOneway() {
600        String oneway = get("oneway");
601        if (oneway != null) {
602            if ("-1".equals(oneway)) {
603                return -1;
604            } else {
605                Boolean isOneway = OsmUtils.getOsmBoolean(oneway);
606                if (isOneway != null && isOneway) {
607                    return 1;
608                }
609            }
610        }
611        return 0;
612    }
613
614    public Node firstNode(boolean respectOneway) {
615        return !respectOneway || isOneway() != -1 ? firstNode() : lastNode();
616    }
617
618    public Node lastNode(boolean respectOneway) {
619        return !respectOneway || isOneway() != -1 ? lastNode() : firstNode();
620    }
621}
Note: See TracBrowser for help on using the repository browser.