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 }