001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.data.validation.tests;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.util.ArrayList;
007    import java.util.Arrays;
008    import java.util.Collections;
009    import java.util.List;
010    
011    import org.openstreetmap.josm.data.osm.Node;
012    import org.openstreetmap.josm.data.osm.OsmPrimitive;
013    import org.openstreetmap.josm.data.osm.Relation;
014    import org.openstreetmap.josm.data.osm.RelationMember;
015    import org.openstreetmap.josm.data.osm.Way;
016    import org.openstreetmap.josm.data.validation.Severity;
017    import org.openstreetmap.josm.data.validation.Test;
018    import org.openstreetmap.josm.data.validation.TestError;
019    
020    public class TurnrestrictionTest extends Test {
021    
022        protected static final int NO_VIA = 1801;
023        protected static final int NO_FROM = 1802;
024        protected static final int NO_TO = 1803;
025        protected static final int MORE_VIA = 1804;
026        protected static final int MORE_FROM = 1805;
027        protected static final int MORE_TO = 1806;
028        protected static final int UNKNOWN_ROLE = 1807;
029        protected static final int UNKNOWN_TYPE = 1808;
030        protected static final int FROM_VIA_NODE = 1809;
031        protected static final int TO_VIA_NODE = 1810;
032        protected static final int FROM_VIA_WAY = 1811;
033        protected static final int TO_VIA_WAY = 1812;
034        protected static final int MIX_VIA = 1813;
035        protected static final int UNCONNECTED_VIA = 1814;
036        protected static final int SUPERFLUOUS = 1815;
037    
038        public TurnrestrictionTest() {
039            super(tr("Turnrestrictions"), tr("This test checks if turnrestrictions are valid"));
040        }
041    
042        @Override
043        public void visit(Relation r) {
044            if (!"restriction".equals(r.get("type")))
045                return;
046    
047            Way fromWay = null;
048            Way toWay = null;
049            List<OsmPrimitive> via = new ArrayList<OsmPrimitive>();
050    
051            boolean morefrom = false;
052            boolean moreto = false;
053            boolean morevia = false;
054            boolean mixvia = false;
055    
056            /* find the "from", "via" and "to" elements */
057            for (RelationMember m : r.getMembers()) {
058                if (m.getMember().isIncomplete())
059                    return;
060    
061                ArrayList<OsmPrimitive> l = new ArrayList<OsmPrimitive>();
062                l.add(r);
063                l.add(m.getMember());
064                if (m.isWay()) {
065                    Way w = m.getWay();
066                    if (w.getNodesCount() < 2) {
067                        continue;
068                    }
069    
070                    if ("from".equals(m.getRole())) {
071                        if (fromWay != null) {
072                            morefrom = true;
073                        } else {
074                            fromWay = w;
075                        }
076                    } else if ("to".equals(m.getRole())) {
077                        if (toWay != null) {
078                            moreto = true;
079                        } else {
080                            toWay = w;
081                        }
082                    } else if ("via".equals(m.getRole())) {
083                        if (!via.isEmpty() && via.get(0) instanceof Node) {
084                            mixvia = true;
085                        } else {
086                            via.add(w);
087                        }
088                    } else {
089                        errors.add(new TestError(this, Severity.WARNING, tr("Unknown role"), UNKNOWN_ROLE,
090                                l, Collections.singletonList(m)));
091                    }
092                } else if (m.isNode()) {
093                    Node n = m.getNode();
094                    if ("via".equals(m.getRole())) {
095                        if (!via.isEmpty()) {
096                            if (via.get(0) instanceof Node) {
097                                morevia = true;
098                            } else {
099                                mixvia = true;
100                            }
101                        } else {
102                            via.add(n);
103                        }
104                    } else {
105                        errors.add(new TestError(this, Severity.WARNING, tr("Unknown role"), UNKNOWN_ROLE,
106                                l, Collections.singletonList(m)));
107                    }
108                } else {
109                    errors.add(new TestError(this, Severity.WARNING, tr("Unknown member type"), UNKNOWN_TYPE,
110                            l, Collections.singletonList(m)));
111                }
112            }
113            if (morefrom) {
114                errors.add(new TestError(this, Severity.ERROR, tr("More than one \"from\" way found"), MORE_FROM, r));
115            }
116            if (moreto) {
117                errors.add(new TestError(this, Severity.ERROR, tr("More than one \"to\" way found"), MORE_TO, r));
118            }
119            if (morevia) {
120                errors.add(new TestError(this, Severity.ERROR, tr("More than one \"via\" node found"), MORE_VIA, r));
121            }
122            if (mixvia) {
123                errors.add(new TestError(this, Severity.ERROR, tr("Cannot mix node and way for role \"via\""), MIX_VIA, r));
124            }
125    
126            if (fromWay == null) {
127                errors.add(new TestError(this, Severity.ERROR, tr("No \"from\" way found"), NO_FROM, r));
128                return;
129            }
130            if (toWay == null) {
131                errors.add(new TestError(this, Severity.ERROR, tr("No \"to\" way found"), NO_TO, r));
132                return;
133            }
134            if (via.isEmpty()) {
135                errors.add(new TestError(this, Severity.ERROR, tr("No \"via\" node or way found"), NO_VIA, r));
136                return;
137            }
138    
139            if (via.get(0) instanceof Node) {
140                final Node viaNode = (Node) via.get(0);
141                final Way viaPseudoWay = new Way();
142                viaPseudoWay.addNode(viaNode);
143                checkIfConnected(fromWay, viaPseudoWay,
144                        tr("The \"from\" way does not start or end at a \"via\" node"), FROM_VIA_NODE);
145                if (toWay.isOneway() != 0 && viaNode.equals(toWay.lastNode(true))) {
146                    errors.add(new TestError(this, Severity.WARNING, tr("Superfluous turnrestriction as \"to\" way is oneway"), SUPERFLUOUS, r));
147                    return;
148                }
149                checkIfConnected(viaPseudoWay, toWay,
150                        tr("The \"to\" way does not start or end at a \"via\" node"), TO_VIA_NODE);
151            } else {
152                // check if consecutive ways are connected: from/via[0], via[i-1]/via[i], via[last]/to
153                checkIfConnected(fromWay, (Way) via.get(0),
154                        tr("The \"from\" and the first \"via\" way are not connected."), FROM_VIA_WAY);
155                if (via.size() > 1) {
156                    for (int i = 1; i < via.size(); i++) {
157                        Way previous = (Way) via.get(i - 1);
158                        Way current = (Way) via.get(i);
159                        checkIfConnected(previous, current, 
160                                tr("The \"via\" ways are not connected."), UNCONNECTED_VIA);
161                    }
162                }
163                if (toWay.isOneway() != 0 && ((Way) via.get(via.size() - 1)).isFirstLastNode(toWay.lastNode(true))) {
164                    errors.add(new TestError(this, Severity.WARNING, tr("Superfluous turnrestriction as \"to\" way is oneway"), SUPERFLUOUS, r));
165                    return;
166                }
167                checkIfConnected((Way) via.get(via.size() - 1), toWay, 
168                        tr("The last \"via\" and the \"to\" way are not connected."), TO_VIA_WAY);
169    
170            }
171        }
172    
173        private void checkIfConnected(Way previous, Way current, String msg, int code) {
174            boolean c;
175            if (previous.isOneway() != 0 && current.isOneway() != 0) {
176                // both oneways: end/start node must be equal
177                c = previous.lastNode(true).equals(current.firstNode(true));
178            } else if (previous.isOneway() != 0) {
179                // previous way is oneway: end of previous must be start/end of current
180                c = current.isFirstLastNode(previous.lastNode(true));
181            } else if (current.isOneway() != 0) {
182                // current way is oneway: start of current must be start/end of previous
183                c = previous.isFirstLastNode(current.firstNode(true));
184            } else {
185                // otherwise: start/end of previous must be start/end of current
186                c = current.isFirstLastNode(previous.firstNode()) || current.isFirstLastNode(previous.lastNode());
187            }
188            if (!c) {
189                errors.add(new TestError(this, Severity.ERROR, msg, code, Arrays.asList(previous, current)));
190            }
191        }
192    }