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