Ticket #18138: 18138.2.patch
| File 18138.2.patch, 17.1 KB (added by , 6 years ago) |
|---|
-
data/defaultpresets.xml
7515 7515 <role key="to" text="to way" requisite="required" count="1" type="way" /> 7516 7516 </roles> 7517 7517 </item> <!-- Turn Restriction --> 7518 <item name="Lane Connectivity" type="relation" preset_name_label="true" icon="presets/transport/way/relation_connectivity.svg"> 7519 <link wiki="Relation:connectivity" /> 7520 <space /> 7521 <key key="type" value="connectivity" /> 7522 <optional> 7523 <text key="connectivity" text="Lane Connectivity" /> 7524 </optional> 7525 <roles> 7526 <role key="from" text="from way" requisite="required" count="1" type="way" /> 7527 <role key="via" text="via node or ways" requisite="required" type="way,node" /> 7528 <role key="to" text="to way" requisite="required" count="1" type="way" /> 7529 </roles> 7530 </item> <!-- Lane Connectivity --> 7518 7531 <item name="Enforcement" icon="presets/vehicle/restriction/speed_camera.svg" type="relation" preset_name_label="true"> 7519 7532 <link wiki="Relation:enforcement" /> 7520 7533 <space /> -
images/presets/transport/way/relation_connectivity.svg
1 <svg version="1.1" viewBox="0.0 0.0 139.61679790026247 135.48556430446195" fill="none" stroke="none" stroke-linecap="square" stroke-miterlimit="10" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><clipPath id="p.0"><path d="m0 0l139.61679 0l0 135.48557l-139.61679 0l0 -135.48557z" clip-rule="nonzero"/></clipPath><g clip-path="url(#p.0)"><path fill="#000000" fill-opacity="0.0" d="m0 0l139.61679 0l0 135.48557l-139.61679 0z" fill-rule="evenodd"/><path fill="#b6d7a8" d="m4.093107 23.93733l0 0c0 -12.092524 9.802927 -21.89545 21.89545 -21.89545l87.64217 0l0 0c5.8070297 0 11.376228 2.3068354 15.482414 6.4130297c4.106201 4.1061935 6.41304 9.675386 6.41304 15.482422l0 87.57918c0 12.092522 -9.802933 21.895447 -21.895454 21.895447l-87.64217 0c-12.092524 0 -21.89545 -9.802925 -21.89545 -21.895447z" fill-rule="evenodd"/><path fill="#999999" d="m49.773502 2.088398l39.874012 0l0 131.27559l-39.874012 0z" fill-rule="evenodd"/><path fill="#999999" d="m109.658676 10.232221l21.763779 7.7165346l-40.97638 115.49607l-21.763779 -7.7165375z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m68.68241 11.295276l4.818901 121.07086" fill-rule="evenodd"/><path stroke="#ffd966" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="butt" d="m69.39829 29.281034l4.1030197 103.0851" fill-rule="evenodd"/><path fill="#ffd966" stroke="#ffd966" stroke-width="3.0" stroke-linecap="butt" d="m74.34956 29.083961l-5.492729 -13.406449l-4.4098206 13.800593z" fill-rule="evenodd"/><path fill="#000000" fill-opacity="0.0" d="m114.6063 23.437008l-41.102364 108.944885" fill-rule="evenodd"/><path stroke="#ffd966" stroke-width="3.0" stroke-linejoin="round" stroke-linecap="butt" d="m108.25247 40.278294l-34.748535 92.10361" fill-rule="evenodd"/><path fill="#ffd966" stroke="#ffd966" stroke-width="3.0" stroke-linecap="butt" d="m112.888695 42.027428l0.16949463 -14.487034l-9.441933 10.988762z" fill-rule="evenodd"/></g></svg> 2 No newline at end of file -
src/org/openstreetmap/josm/data/validation/OsmValidator.java
42 42 import org.openstreetmap.josm.data.validation.tests.BarriersEntrances; 43 43 import org.openstreetmap.josm.data.validation.tests.Coastlines; 44 44 import org.openstreetmap.josm.data.validation.tests.ConditionalKeys; 45 import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations; 45 46 import org.openstreetmap.josm.data.validation.tests.CrossingWays; 46 47 import org.openstreetmap.josm.data.validation.tests.DuplicateNode; 47 48 import org.openstreetmap.josm.data.validation.tests.DuplicateRelation; … … 150 151 PublicTransportRouteTest.class, // 3600 .. 3699 151 152 RightAngleBuildingTest.class, // 3700 .. 3799 152 153 SharpAngles.class, // 3800 .. 3899 154 ConnectivityRelations.class, // 3900 .. 3999 153 155 }; 154 156 155 157 /** -
src/org/openstreetmap/josm/data/validation/tests/ConnectivityRelations.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.Collections; 7 import java.util.Comparator; 8 import java.util.HashMap; 9 import java.util.Map; 10 import java.util.Map.Entry; 11 import java.util.regex.Pattern; 12 13 import org.openstreetmap.josm.data.osm.Node; 14 import org.openstreetmap.josm.data.osm.OsmPrimitive; 15 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 16 import org.openstreetmap.josm.data.osm.Relation; 17 import org.openstreetmap.josm.data.osm.RelationMember; 18 import org.openstreetmap.josm.data.osm.Way; 19 import org.openstreetmap.josm.data.validation.Severity; 20 import org.openstreetmap.josm.data.validation.Test; 21 import org.openstreetmap.josm.data.validation.TestError; 22 import org.openstreetmap.josm.tools.Logging; 23 24 /** 25 * Check for inconsistencies in lane information between relation and members. 26 */ 27 public class ConnectivityRelations extends Test { 28 29 protected static final int INCONSISTENT_LANE_COUNT = 3900; 30 31 protected static final int UNKNOWN_CONNECTIVITY_ROLE = INCONSISTENT_LANE_COUNT + 1; 32 33 protected static final int NO_CONNECTIVITY_TAG = INCONSISTENT_LANE_COUNT + 2; 34 35 protected static final int TOO_MANY_ROLES = INCONSISTENT_LANE_COUNT + 3; 36 37 private static final String CONNECTIVITY_TAG = "connectivity"; 38 private static final String VIA = "via"; 39 private static final String TO = "to"; 40 private static final String FROM = "from"; 41 private static final Pattern OPTIONAL_LANE_PATTERN = Pattern.compile("\\([0-9]+\\)"); 42 private static final Pattern TO_LANE_PATTERN = Pattern.compile("\\p{Zs}*[,:;]\\p{Zs}*"); 43 44 /** 45 * Constructor 46 */ 47 public ConnectivityRelations() { 48 super(tr("Connectivity Relation Check"), tr("Checks that lane count of relation matches with lanes of members")); 49 } 50 51 /** 52 * Convert the connectivity tag into a map of values 53 * 54 * @param relation A relation with a {@code connectivity} tag. 55 * @return A Map in the form of {@code Map<Lane From, Map<Lane To, Optional>>} May contain nulls when errors are encountered 56 * @since xxx 57 */ 58 public static Map<Integer, Map<Integer, Boolean>> parseConnectivityTag(Relation relation) { 59 final String joined = relation.get(CONNECTIVITY_TAG); 60 61 if (joined == null) { 62 return Collections.emptyMap(); 63 } 64 65 final Map<Integer, Map<Integer, Boolean>> result = new HashMap<>(); 66 String[] lanes = joined.split("\\|", -1); 67 for (int i = 0; i < lanes.length; i++) { 68 String[] lane = lanes[i].split(":", -1); 69 int laneNumber = Integer.parseInt(lane[0].trim()); 70 Map<Integer, Boolean> connections = new HashMap<>(); 71 String[] toLanes = TO_LANE_PATTERN.split(lane[1]); 72 for (int j = 0; j < toLanes.length; j++) { 73 String toLane = toLanes[j].trim(); 74 try { 75 if (OPTIONAL_LANE_PATTERN.matcher(toLane).matches()) { 76 toLane = toLane.replace("(", "").replace(")", "").trim(); 77 connections.put(Integer.parseInt(toLane), Boolean.TRUE); 78 } else { 79 connections.put(Integer.parseInt(toLane), Boolean.FALSE); 80 } 81 } catch (NumberFormatException e) { 82 Logging.debug(e); 83 connections.put(null, null); 84 } 85 86 } 87 result.put(laneNumber, connections); 88 } 89 return result; 90 } 91 92 @Override 93 public void visit(Relation r) { 94 if (r.hasTag("type", CONNECTIVITY_TAG)) { 95 if (!r.hasKey(CONNECTIVITY_TAG)) { 96 errors.add(TestError.builder(this, Severity.WARNING, NO_CONNECTIVITY_TAG) 97 .message(tr("No connectivity tag in connectivity relation")).primitives(r).build()); 98 } else if (!r.hasIncompleteMembers()) { 99 boolean badRole = checkForBadRole(r); 100 if (!badRole) 101 checkForInconsistentLanes(r); 102 } 103 } 104 } 105 106 private void checkForInconsistentLanes(Relation relation) { 107 // Lane count from connectivity tag 108 Map<Integer, Map<Integer, Boolean>> connTagLanes = parseConnectivityTag(relation); 109 // Lane count from member tags 110 Map<String, Integer> roleLanes = new HashMap<>(); 111 112 for (RelationMember rM : relation.getMembers()) { 113 // Check lanes 114 if (rM.getType() == OsmPrimitiveType.WAY) { 115 OsmPrimitive prim = rM.getMember(); 116 if (prim.hasKey("lanes") && !VIA.equals(rM.getRole())) { 117 roleLanes.put(rM.getRole(), Integer.parseInt(prim.get("lanes"))); 118 } 119 } 120 } 121 boolean fromCheck = roleLanes.get(FROM) < Collections 122 .max(connTagLanes.entrySet(), Comparator.comparingInt(Map.Entry::getKey)).getKey(); 123 boolean toCheck = false; 124 for (Entry<Integer, Map<Integer, Boolean>> to : connTagLanes.entrySet()) { 125 toCheck = roleLanes.get(TO) < Collections 126 .max(to.getValue().entrySet(), Comparator.comparingInt(Map.Entry::getKey)).getKey(); 127 } 128 if (fromCheck || toCheck) { 129 errors.add(TestError.builder(this, Severity.WARNING, INCONSISTENT_LANE_COUNT) 130 .message(tr("Inconsistent lane numbering between relation and members")).primitives(relation) 131 .build()); 132 } 133 } 134 135 private boolean checkForBadRole(Relation relation) { 136 // Check role names 137 int viaWays = 0; 138 int viaNodes = 0; 139 int toWays = 0; 140 int fromWays = 0; 141 for (RelationMember relationMember : relation.getMembers()) { 142 if (relationMember.getMember() instanceof Way) { 143 if (relationMember.hasRole(FROM)) 144 fromWays++; 145 else if (relationMember.hasRole(TO)) 146 toWays++; 147 else if (relationMember.hasRole(VIA)) 148 viaWays++; 149 else { 150 createUnknownRole(relation, relationMember.getMember()); 151 return true; 152 } 153 } else if (relationMember.getMember() instanceof Node) { 154 if (!relationMember.hasRole(VIA)) { 155 createUnknownRole(relation, relationMember.getMember()); 156 return true; 157 } 158 viaNodes++; 159 } 160 } 161 return mixedViaNodeAndWay(relation, viaWays, viaNodes, toWays, fromWays); 162 } 163 164 private boolean mixedViaNodeAndWay(Relation relation, int viaWays, int viaNodes, int toWays, int fromWays) { 165 String message = ""; 166 if ((viaWays != 0 && viaNodes != 0) || viaNodes > 1) { 167 message = tr("Relation contains {1} {0} roles.", VIA, viaWays + viaNodes); 168 } else if (toWays != 1) { 169 message = tr("Relation contains too many {0} roles", TO); 170 } else if (fromWays != 1) { 171 message = tr("Relation contains too many {0} roles", FROM); 172 } 173 if (message.isEmpty()) { 174 return false; 175 } else { 176 errors.add(TestError.builder(this, Severity.WARNING, TOO_MANY_ROLES) 177 .message(message).primitives(relation).build()); 178 return true; 179 } 180 } 181 182 private void createUnknownRole(Relation relation, OsmPrimitive primitive) { 183 errors.add(TestError.builder(this, Severity.WARNING, UNKNOWN_CONNECTIVITY_ROLE) 184 .message(tr("Unkown role in connectivity relation")).primitives(relation).highlight(primitive).build()); 185 } 186 } -
test/unit/org/openstreetmap/josm/data/validation/tests/ConnectivityRelationsTest.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import org.junit.Assert; 5 import org.junit.Before; 6 import org.junit.Test; 7 import org.openstreetmap.josm.JOSMFixture; 8 import org.openstreetmap.josm.TestUtils; 9 import org.openstreetmap.josm.data.coor.LatLon; 10 import org.openstreetmap.josm.data.osm.Node; 11 import org.openstreetmap.josm.data.osm.Relation; 12 import org.openstreetmap.josm.data.osm.RelationMember; 13 14 /** 15 * Test the ConnectivityRelations validation test 16 * 17 * @author Taylor Smock 18 */ 19 public class ConnectivityRelationsTest { 20 private ConnectivityRelations check; 21 private static final String CONNECTIVITY = "connectivity"; 22 /** 23 * Setup test. 24 * 25 * @throws Exception if an error occurs 26 */ 27 @Before 28 public void setUp() throws Exception { 29 JOSMFixture.createUnitTestFixture().init(); 30 check = new ConnectivityRelations(); 31 } 32 33 private Relation createDefaultTestRelation() { 34 Node connection = new Node(new LatLon(0, 0)); 35 return TestUtils.newRelation("type=connectivity connectivity=1:1", 36 new RelationMember("from", TestUtils.newWay("lanes=4", new Node(new LatLon(-0.1, -0.1)), connection)), 37 new RelationMember("via", connection), 38 new RelationMember("to", TestUtils.newWay("lanes=4", connection, new Node(new LatLon(0.1, 0.1))))); 39 } 40 41 /** 42 * Test for connectivity relations without a connectivity tag 43 */ 44 @Test 45 public void testNoConnectivityTag() { 46 Relation relation = createDefaultTestRelation(); 47 check.visit(relation); 48 49 Assert.assertEquals(0, check.getErrors().size()); 50 51 relation.remove(CONNECTIVITY); 52 check.visit(relation); 53 Assert.assertEquals(1, check.getErrors().size()); 54 } 55 56 /** 57 * Check for lanes that don't make sense 58 */ 59 @Test 60 public void testMisMatchedLanes() { 61 Relation relation = createDefaultTestRelation(); 62 check.visit(relation); 63 int expectedFailures = 0; 64 65 Assert.assertEquals(expectedFailures, check.getErrors().size()); 66 67 relation.put(CONNECTIVITY, "45000:1"); 68 check.visit(relation); 69 Assert.assertEquals(++expectedFailures, check.getErrors().size()); 70 71 relation.put(CONNECTIVITY, "1:45000"); 72 check.visit(relation); 73 Assert.assertEquals(++expectedFailures, check.getErrors().size()); 74 75 relation.put(CONNECTIVITY, "1:1,2"); 76 check.visit(relation); 77 Assert.assertEquals(expectedFailures, check.getErrors().size()); 78 79 relation.put(CONNECTIVITY, "1:1,(2)"); 80 check.visit(relation); 81 Assert.assertEquals(expectedFailures, check.getErrors().size()); 82 83 relation.put(CONNECTIVITY, "1:1,(20000)"); 84 check.visit(relation); 85 Assert.assertEquals(++expectedFailures, check.getErrors().size()); 86 } 87 88 /** 89 * Check for bad roles (not from/via/to) 90 */ 91 @Test 92 public void testForBadRole() { 93 Relation relation = createDefaultTestRelation(); 94 check.visit(relation); 95 int expectedFailures = 0; 96 97 Assert.assertEquals(expectedFailures, check.getErrors().size()); 98 99 for (int i = 0; i < relation.getMembers().size(); i++) { 100 String tRole = replaceMember(relation, i, "badRole"); 101 check.visit(relation); 102 Assert.assertEquals(++expectedFailures, check.getErrors().size()); 103 replaceMember(relation, i, tRole); 104 check.visit(relation); 105 Assert.assertEquals(expectedFailures, check.getErrors().size()); 106 } 107 } 108 109 private String replaceMember(Relation relation, int index, String replacementRole) { 110 RelationMember relationMember = relation.getMember(index); 111 String currentRole = relationMember.getRole(); 112 relation.removeMember(index); 113 relation.addMember(index, new RelationMember(replacementRole, relationMember.getMember())); 114 return currentRole; 115 } 116 }
