meta { title: "Validation rules for Kaart Group"; description: "Various checks that are useful to Kaart"; author: "Taylor Smock, Derek Bevan"; min-josm-version: "8087"; version: "46_2021-03-03"; } /**************** ** Kaart Users * ****************/ relation[JOSM_search("user:JAAS or user:DowntownAbby or user:sbburg or user:RConnorsCarte19 or user:ProfessorLupin or user:ReedtheRiver or user:GhostEye or user:Buddy-the-Elf or user:Corban8 or user:tomincolorado or user:hairyhaggis or user:AnonJason or user:CoolGuyJake or user:\"Whimsical Otter\" or user:theArchDruid or user:Baconcrisp or user:DRM150 or user:birdeatscake or user:Kaarti_B or user:DerekBev or user:nutchayahongvilai or user:InnerPace")]{ set .kaart_user } relation[type=route].kaart_user { group: tr("kaart"); throwOther: "Route modified by Kaart, check for continuity problems"; } /**************** ** Roads ******* ****************/ /* Check roads for construction tags */ way[highway][construction][highway!=construction]:modified { group: tr("kaart"); throwWarning: tr("Is this road still under construction?"); } /* throw error on oneway=-1 */ way[oneway=-1] { group: tr("kaart"); throwError: tr("oneway should not be -1"); suggestAlternative: tr("oneway=yes and reverse the way"); } @supports (min-josm-version: 14802) { /* Check that roads have surfaces */ /* way[highway=~/^(motorway|trunk|primary|secondary|tertiary|residential|unclassified)$/][!surface][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway=~/^.*_link$/][!surface][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("Missing surface on a road"); } way[highway=~/^(service|living_street)$/][!surface][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwOther: tr("Missing surface on a minor road"); } */ /* way[highway=~/^(motorway|trunk|primary|secondary|tertiary|residential)$/][surface=paved][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway=~/^(pedestrian)$/][name][surface=paved][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway=~/^.*_link$/][surface=paved][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("Generic paved surface on road"); } */ /* Check that roads have lanes */ /* way[highway=~/^(motorway|trunk|primary|secondary|tertiary|motorway_link|trunk_link|primary_link|secondary_link|tertiary_link)$/][!lanes][surface!~/^(gravel|ground)$/][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("Missing lanes on road"); } */ /* Check for bad lanes */ way[highway][lanes=0] { group: tr("kaart"); throwError: tr("Bad tag: {0}", "{1.tag}"); } /* Check implied motorway tags */ way[highway=~/^(motorway|motorway_link)$/][!oneway][!junction=roundabout][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("{0} is typically {1}", "{0.tag}", "{1.key}"); suggestAlternative: "oneway=yes"; } way[highway=~/^(motorway|motorway_link)$/][!access][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwOther: tr("{0} is typically reserved for motor_vehicles", "{0.tag}"); suggestAlternative: "access=no with motor_vehicle=yes"; } /* Check that turn:lanes/:forward/:backward */ way[highway][turn:lanes][!/^(oneway|junction)$/][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("turn:lanes without oneway -- are turn lanes the same in both directions?"); } way[highway][turn:lanes][turn:lanes:forward][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes][turn:lanes:backward][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwError: tr("{0} and a directional {1}", "{1.tag}", "{2.tag}"); } way[highway][turn:lanes][/^(oneway|junction)$/][count(split("|", tag("turn:lanes"))) != tag("lanes")][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes][!/^(oneway|junction)$/][2 * count(split("|", tag("turn:lanes"))) != tag("lanes")][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes][!lanes] { group: tr("kaart"); throwWarning: tr("{0} do not match {1}", "{1.key}", "lanes"); } way[highway][turn:lanes:forward][lanes:forward][count(split("|", key("turn:lanes:forward"))) != key("lanes:forward")][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes:forward][!lanes:forward][count(split("|", key("turn:lanes:forward"))) != key("lanes") / 2][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes:forward][!lanes][!lanes:forward][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("{0} do not match lanes:forward", "{1.key}"); } way[highway][turn:lanes:backward][lanes:backward][count(split("|", key("turn:lanes:backward"))) != key("lanes:backward")][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes:backward][!lanes:backward][count(split("|", key("turn:lanes:backward"))) != key("lanes") / 2][gpx_distance() < 30 || gpx_distance() >= 50000000], way[highway][turn:lanes:backward][!lanes][!lanes:backward][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("{0} do not match lanes:backward", "{1.key}"); } } /* Look for possible highways without highway tags */ way[!/^(.*:)*(highway|railway)$/][/^.*(lanes|maxspeed|maxweight|maxheight|maxwidth|turn).*$/][!/^aeroway$/], way[!/^(.*:)*(highway|railway)$/][/^(surface)$/][!/^amenity|leisure|aeroway|natural$/][place!=square] { group: tr("kaart"); throwError: tr("Is this a {0} -- it has a {1} tag", "{0.key}", "{1.key}"); assertMatch: "way surface=asphalt"; assertNoMatch: "way disused:highway=residential surface=asphalt"; } node[/^.*(lanes|turn).*$/], node[/^.*(surface)/][!/^(traffic_calming)$/], node[/^.*(maxspeed|maxweight|maxheight|maxwidth).*$/][!/^(traffic_sign|parking|barrier|traffic_calming)$/][!highway=speed_camera] { group: tr("kaart"); throwError: tr("Nodes typically don''t have {0}", "{0.key}"); fixRemove: "{0.key}"; assertMatch: "node lanes=3"; assertNoMatch: "node name=Me"; } way[highway=~/^(motorway|motorway_link)$/][access=no][!motor_vehicle] { group: tr("kaart"); throwOther: tr("{0} is typically accessible to motor_vehicles", "{0.tag}"); suggestAlternative: "motor_vehicle=yes"; } /* Check lanes from way to way (ONLY TWO WAYS) TODO check patches */ /* node[count(parent_osm_ids()) = 2 && count(parent_osm_ids("lanes")) != 0 && get(parent_osm_ids("lanes"),0) != get(parent_osm_ids("lanes"),1) && (regexp_match("(slight_right|slight_left|right|left)", get(parent_osm_ids("turn:lanes"))) != null )] { } */ way[highway][turn], way[highway][turn:forward], way[highway][turn:backward], way[highway][turn:both_ways] { group: tr("kaart"); throwWarning: tr("We shouldn''t be using turn:"); suggestAlternative: tr("turn:lanes:"); } /* Check for turn lanes that are definately wrong */ way[highway][/turn:lanes/][/turn:lanes/!~/^(none[|;]?)*((reverse|merge_to_right|left)[|;]?)*((merge_to_right|slight_left)[|;]?)*[|]*((through|none)[|;]?)*[|]*((slight_right|merge_to_left)[|;]?)*((right|merge_to_left)[|;]?)*(none[|;]?)*$/]:righthandtraffic { group: tr("kaart"); throwError: tr("Check the order of {0}", "{1.key}"); assertMatch: "way highway=tertiary turn:lanes=\"through|reverse\""; assertNoMatch: "way highway=tertiary turn:lanes=\"reverse|left|merge_to_right|through\""; assertNoMatch: "way highway=motorway turn:lanes=through|through|through;slight_right|none"; } @supports (min-josm-version: 14802) { /* Check *_links to see if they have a oneway tag */ /* Taylor Smock, Derek Bevan */ way[highway=~/^.*_link$/][!oneway] { group: tr("kaart"); throwWarning: tr("Links are usually oneway. Please verify"); /* suggestAlternative: "oneway"; */ } } /* Check links to make certain they match the road they are going to TODO */ way[highway=~/^.*_link$/][oneway=yes] >[index=-1] node { set .end_of_link } way[highway=~/^.*_link$/][oneway=yes] >[index=1] node { set .start_of_link } node.end_of_link, node.start_of_link { set .link } way[highway=motorway] > node.link { set .motorway } way[highway=motorway_link] > node.link { set .motorway_link } way[highway=trunk] > node.link { set .trunk } way[highway=trunk_link] > node.link { set .trunk_link } way[highway=primary] > node.link { set .primary } way[highway=primary_link] > node.link { set .primary_link } way[highway=secondary] > node.link { set .secondary } way[highway=secondary_link] > node.link { set .secondary_link } way[highway=tertiary] > node.link { set .tertiary } way[highway=tertiary_link] > node.link { set .tertiary_link } node.motorway!.motorway_link, node.trunk!.motorway_link!.trunk_link, node.primary!.motorway_link!.trunk_link!.primary_link, node.secondary!.motorway_link!.trunk_link!.primary_link!.secondary_link, node.tertiary!.motorway_link!.trunk_link!.primary_link!.secondary_link!.tertiary_link { group: tr("kaart"); throwWarning: tr("Check the link and the highest road it goes to/comes from"); } way >[index=-1] node { set .end_of_way } way >[index=1] node { set .start_of_way } /* Check that specific types of roads have refs and names */ way[highway][ref] > node { set .nodeHasRef } @supports (min-josm-version: 14802) { way[highway=~/^(motorway|trunk|primary|secondary)$/][!ref][gpx_distance() < 30 || gpx_distance() >= 50000000] > node { set .nodeNoRef } node.nodeHasRef.nodeNoRef { group: tr("kaart"); throwWarning: tr("A road may be missing a ref"); } /* Check for erroneous maxspeed */ /* way[highway][/^(?!maxspeed:type|maxspeed:variable)maxspeed/][/^maxspeed/!~/^((\d{0,2}[0,5]( mph| knots)?)|none|walk|signals|\p{Lu}{2}:(\p{Ll}|:)*)$/][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwError: tr("Unknown maxspeed -- check the maxspeeds"); } way[highway][inside("US")][/^(?!maxspeed:type|maxspeed:variable)maxspeed/][/^maxspeed/!~/^((\d{0,2}[0,5] mph)|none|walk|signals|\p{Lu}{2}:(\p{Ll}|:)*)$/][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwError: tr("{0} should be in mph in the US, try adding mph to {0}", "{3.key}"); } */ } /* Traffic sign checks */ /* node[traffic_sign][maxspeed][tag("maxspeed") != parent_tag("maxspeed")][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxspeed][tag("maxspeed") != parent_tag("maxspeed:forward") || count(parent_tags("maxspeed:forward")) == 0 && tag("maxspeed") != parent_tag("maxspeed")][traffic_sign:forward?], node[traffic_sign][maxspeed][tag("maxspeed") != parent_tag("maxspeed:backward") || count(parent_tags("maxspeed:backward")) == 0 && tag("maxspeed") != parent_tag("maxspeed")][traffic_sign:backward?], node[traffic_sign][maxweight][tag("maxweight") != parent_tag("maxweight")][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxweight][tag("maxweight") != parent_tag("maxweight:forward") || count(parent_tags("maxweight:forward")) == 0 && tag("maxweight") != parent_tag("maxweight")][traffic_sign:forward?], node[traffic_sign][maxweight][tag("maxweight") != parent_tag("maxweight:backward") || count(parent_tags("maxweight:backward")) == 0 && tag("maxweight") != parent_tag("maxweight")][traffic_sign:backward?], node[traffic_sign][maxheight][tag("maxheight") != parent_tag("maxheight")][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxheight][tag("maxheight") != parent_tag("maxheight:forward") || count(parent_tags("maxheight:forward")) == 0 && tag("maxheight") != parent_tag("maxheight")][traffic_sign:forward?], node[traffic_sign][maxheight][tag("maxheight") != parent_tag("maxheight:backward") || count(parent_tags("maxheight:backward")) == 0 && tag("maxheight") != parent_tag("maxheight")][traffic_sign:backward?], node[traffic_sign][maxlength][tag("maxlength") != parent_tag("maxlength")][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxlength][tag("maxlength") != parent_tag("maxlength:forward") || count(parent_tags("maxlength:forward")) == 0 && tag("maxlength") != parent_tag("maxlength")][traffic_sign:forward?], node[traffic_sign][maxlength][tag("maxlength") != parent_tag("maxlength:backward") || count(parent_tags("maxlength:backward")) == 0 && tag("maxlength") != parent_tag("maxlength")][traffic_sign:backward?], node[traffic_sign][overtaking][tag("overtaking") != parent_tag("overtaking")][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][overtaking][tag("overtaking") != parent_tag("overtaking:forward") || count(parent_tags("overtaking:forward")) == 0 && tag("overtaking") != parent_tag("overtaking")][traffic_sign:forward?], node[traffic_sign][overtaking][tag("overtaking") != parent_tag("overtaking:backward") || count(parent_tags("overtaking:backward")) == 0 && tag("overtaking") != parent_tag("overtaking")][traffic_sign:backward?] { group: tr("kaart"); throwWarning: tr("{0} on {1} does not match {2} on parent way", "{1.key}", "{0.key}", "{1.key}"); } */ /* node[traffic_sign][maxspeed][count(parent_tags("maxspeed")) == 0][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxspeed][count(parent_tags("maxspeed:forward")) == 0 && count(parent_tags("maxspeed")) == 0][traffic_sign:forward?], node[traffic_sign][maxspeed][count(parent_tags("maxspeed:backward")) == 0 && count(parent_tags("maxspeed")) == 0][traffic_sign:backward?], node[traffic_sign][maxweight][count(parent_tags("maxweight")) == 0][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxweight][count(parent_tags("maxweight:forward")) == 0 && count(parent_tags("maxweight")) == 0][traffic_sign:forward?], node[traffic_sign][maxweight][count(parent_tags("maxweight:backward")) == 0 && count(parent_tags("maxweight")) == 0][traffic_sign:backward?], node[traffic_sign][maxheight][count(parent_tags("maxheight")) == 0][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxheight][count(parent_tags("maxheight:forward")) == 0 && count(parent_tags("maxheight")) == 0][traffic_sign:forward?], node[traffic_sign][maxheight][count(parent_tags("maxheight:backward")) == 0 && count(parent_tags("maxheight")) == 0][traffic_sign:backward?], node[traffic_sign][maxlength][count(parent_tags("maxlength")) == 0][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][maxlength][count(parent_tags("maxlength:forward")) == 0 && count(parent_tags("maxlength")) == 0][traffic_sign:forward?], node[traffic_sign][maxlength][count(parent_tags("maxlength:backward")) == 0 && count(parent_tags("maxlength")) == 0][traffic_sign:backward?], node[traffic_sign][overtaking][count(parent_tags("overtaking")) == 0][!/^(direction|traffic_sign:(forward|backward))$/], node[traffic_sign][overtaking][count(parent_tags("overtaking:forward")) == 0 && count(parent_tags("overtaking")) == 0][traffic_sign:forward?], node[traffic_sign][overtaking][count(parent_tags("overtaking:backward")) == 0 && count(parent_tags("overtaking")) == 0][traffic_sign:backward?] { group: tr("kaart"); throwWarning: tr("{0} on {1} does not exist on parent way", "{1.key}", "{0.key}"); } */ /* Check for areas that should have units on maxweight */ /* *[maxweight][maxweight=~/^[0-9.]+$/][inside("US")], *[maxaxleload][maxaxleload=~/^[0-9.]+$/][inside("US")] { group: tr("kaart"); throwError: tr("{0} is always in short tons (st)", "{0.key}"); } */ /* Check *_links to see if they have a name/noname tag */ way[highway=~/^.*_link$/][name], way[highway=~/^.*_link$/][noname] { group: tr("kaart"); throwWarning: tr("Links shouldn''t have name/noname tags"); } @supports (min-josm-version: 14802) { /* Bring pedestrian crossings forward */ /* node[highway=crossing][!crossing][gpx_distance() < 30 || gpx_distance() >= 50000000]:in-downloaded-area { group: tr("kaart"); throwWarning: tr("A highway crossing does not have a crossing type"); } */ /* node[highway=crossing][crossing][crossing!~/^(traffic_signals|uncontrolled|no|unmarked|zebra|pelican|tiger|toucan|pegasus|island)$/][gpx_distance() < 30 || gpx_distance() >= 50000000]:in-downloaded-area { group: tr("kaart"); throwWarning: tr("A highway crossing is using an unknown crossing type ({0})", tag("crossing")); assertNoMatch: "node highway=crossing crossing=traffic_signals"; assertNoMatch: "node highway=crossing crossing=uncontrolled"; assertNoMatch: "node highway=crossing crossing=unmarked"; assertNoMatch: "node highway=crossing crossing=no"; /* assertMatch: "node highway=crossing crossing=bad_crossing"; TODO figure out why this errors *//* } */ /* node[highway=crossing][crossing=island][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwWarning: tr("{0} is no longer recommended, due to conflicting with unmarked/uncontrolled/traffic_signals", "{1.tag}"); suggestAlternative: "crossing:island=yes"; fixRemove: "crossing"; fixAdd: "crossing:island=yes"; } */ /* node[highway=crossing][crossing=~/^(zebra|pelican|tiger|toucan|pegasus)$/][gpx_distance() < 30 || gpx_distance() >= 50000000]:in-downloaded-area { group: tr("kaart"); throwWarning: tr("A highway crossing is using an unclear crossing type that should be in crossing_ref"); fixChangeKey: "crossing => crossing_ref"; } */ } /* Look for intersections of railways and highways */ way[railway][highway] > node { set .in_railway_highway; } way[railway][railway!~/^(disused|abandoned|dismantled|razed|historic)$/] > node { set .in_railway } way[highway][highway!~/^(pedestrian|footway)$/] > node { set .in_highway } way[highway][highway=~/^(pedestrian|footway)$/] > node { set .in_pedestrian_highway } /* Mark some tram nodes as in the Balkans since they don't want level_crossing on those nodes */ /* CS not a valid country, as far as JOSM is concerned */ way[railway][railway=tram][inside("AL,BA,BG,MK,HR,GR,IT,RO,RS,SI,TR")] > node { set .in_balkans } @supports (min-josm-version: 14802) { node[railway!=level_crossing][railway!=crossing][gpx_distance() < 30 || gpx_distance() >= 50000000].in_railway.in_highway!.in_railway_highway!.in_balkans { group: tr("kaart"); throwWarning: tr("There should be a crossing of some type here"); suggestAlternative: "railway=level_crossing" } node[railway!~/^(level_crossing|crossing|station|tram_stop|subway_entrance)$/][gpx_distance() < 30 || gpx_distance() >= 50000000].in_railway.in_pedestrian_highway!.in_balkans { group: tr("kaart"); throwWarning: tr("There should be a crossing of some type here"); suggestAlternative: "railway=crossing" } } /* Check that way that is ONLY connecting crossings is a footway TODO */ /*way[highway!=footway][!name] >[index=-1] node[highway=crossing] >[index=1] node[highway=crossing] { group: tr("kaart"); throwWarning: tr("This should probably be a footway"); }*/ /* Check *_links and streets to find ways that split-end into _links TODO */ way[highway=~/^.*_link$/][oneway=no] >[index=-1] node, way[highway=~/^.*_link$/][oneway=no] >[index=1] node{ set .end_of_twoway_link } way[highway!~/^.*_link$/][name] >[index=-1] node, way[highway!~/^.*_link$/][name] >[index=1] node { set .end_of_road } way[highway=~/^.*_link$/] > node {set .is_in_link} way[highway =~ /^(bus_guideway|living_street|motorway|motorway_link|pedestrian|primary|primary_link|raceway|residential|road|secondary|secondary_link|service|tertiary|tertiary_link|track|trunk|trunk_link|unclassified)$/] > node { set .is_in_major_road } way[highway =~ /^(service)$/] > node { set .is_in_minor_road } @supports (min-josm-version: 14802) { /* Check for potential road restrictions */ /* way[highway][bridge][!maxweight][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwOther: tr("Bridges may have a maxweight"); suggestAlternative: "maxweight"; } */ /* TODO fix */ /* way[highway][bridge] ⧉ way[highway][!bridge][!maxheight][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwOther: tr("Ways under bridges occassionally have a maxheight attribute. Check on ground for the information (or OpenStreetCam/Mapillary)"); suggestAlternative: "maxheight"; } */ } way[highway][maxheight=~/^\d+'\s\d+"$/] { group: tr("kaart"); throwWarning: tr("maxheight is not properly formatted for feet and inches"); } way|z21-[JOSM_search("inview")][highway][maxheight=~/^\d+'\s\d+"$/] { group: tr("kaart"); throwWarning: tr("maxheight is not properly formatted for feet and inches"); fixAdd: tr("{0}={1}", "maxheight", replace(tag("maxheight"), " ", "")); } /* @supports (min-josm-version: 14802) { way[highway][tunnel][gpx_distance() < 30 || gpx_distance() >= 50000000] { group: tr("kaart"); throwOther: tr("Tunnels may have a maxheight"); suggestAlternative: "maxheight"; } } */ /* Check for motor_vehicle=no tag -- the tag may not be necessary */ /* Derek Bevan */ way[highway =~ /^(tertiary|secondary|primary|trunk|motorway)(_link)?$/][motor_vehicle=no] { group: tr("kaart"); throwWarning: tr("High-priority road contains motor_vehicle=no tag. Remove tag, reclassify way, or add access tag?"); assertMatch: "way highway=tertiary motor_vehicle=no"; assertNoMatch: "way highway=residential motor_vehicle=no"; assertNoMatch: "way highway=footway"; assertNoMatch: "way highway=steps"; assertNoMatch: "way highway=pedestrian"; } relation[type=restriction][!/^restriction/] { group: tr("kaart"); throwError: tr("Restriction relations should have the type of restriction"); assertMatch: "relation type=restriction"; assertNoMatch: "relation type=restriction restriction=no_u_turn"; assertNoMatch: "relation type=restriction restriction:hgv=no_u_turn"; } /* Check for ref and name being equal */ way[highway][name][ref][name = *ref] { throwWarning: tr("{0} and {1} are the same", "name", "ref"); group: tr("kaart"); assertMatch: "way highway=tertiary name=22 ref=22"; assertNoMatch: "way highway=tertiary name=22 ref=23"; } /* Check for barrier=kerb on roads */ way[highway][barrier=kerb][highway!~/(pedestrian|footway)/] { group: tr("kaart"); throwWarning: tr("There is a {0} on the road", "{1.value}"); } node[barrier=kerb] < way[highway][highway=~/(service|residential|unclassified|tertiary|secondary|primary|trunk|motorway)/] { group: tr("kaart"); throwWarning: tr("There is a {0} on the road", "kerb"); } way[highway=~/^.*_link$/][junction] { group: tr("kaart"); throwWarning: tr("{0} should not be a {1} if any of the connecting roads are not {1}s", tag("junction"), "_link"); } /***************** * Roundabouts *** *****************/ /* Checks for roundabouts */ way[junction=roundabout]:in-downloaded-area > node {set .is_in_roundabout} node[traffic_calming=island]:in-downloaded-area { set .is_traffic_calming_island} /* Taylor Smock, Derek Bevan */ way[highway][highway!~/^(pedestrian|footway|cycleway)$/][oneway!=yes][junction!=roundabout]:in-downloaded-area > node.is_in_roundabout!.is_traffic_calming_island { group: tr("kaart"); throwWarning: tr("Highway connecting to roundabout is NOT a oneway -- should there be a Y-junction?"); /* suggestAlternative: "split ways to have roundabout flares or add traffic_calming=island to a node using imagery" */ } way[highway][oneway!=yes][junction!=roundabout]:in-downloaded-area > node.is_in_roundabout.is_traffic_calming_island { group: tr("kaart"); throwWarning: tr("The node where a roundabout and a way connect should NOT be a traffic_calming=island (the island should be on the approach, not in the roundabout)"); } /* Check for roundabouts with MORE than 1 road entering at the same location */ way[junction=roundabout][name] > node {set .is_in_named_roundabout} /* TODO remove the one with parent_tags as soon as parent_osm_ids is in josm-tested */ node.is_in_roundabout!.is_in_named_roundabout[count(parent_tags("name")) - count(parent_tags("landuse")) > 1]:in-downloaded-area, node.is_in_roundabout.is_in_named_roundabout[count(parent_tags("name")) - count(parent_tags("landuse")) > 2]:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Roundabouts should not have more than two additional ways at any node"); } node.is_in_roundabout[count(parent_osm_ids("highway")) - count(parent_osm_ids("junction")) > 1] { group: tr("kaart"); throwError: tr("Roundabouts should not have more than two additional ways at any node"); } /* Ways entering the roundabout should start/end there */ way[name][highway][!junction] > node.is_in_roundabout!.end_of_way!.start_of_way { set .roundabout_way_ends } node.roundabout_way_ends { group: tr("kaart"); throwWarning: tr("Way {0} does not end at roundabout", parent_tags("name")); } /* Derek Bevan */ @supports (min-josm-version: 15353) { /* Check that roundabout names are different from the names of incoming ways */ way[highway][name][!junction] >[index=1] node, way[highway][name][!junction] >[index=-1] node {set .named_way_end_node} node.is_in_named_roundabout.named_way_end_node[count(uniq_list(parent_tags("name"))) == 1] { group: tr("kaart"); throwWarning: tr("Name of roundabout and incoming way are the same"); } } way[highway=~/(motorway|trunk|primary|secondary|tertiary|unclassified|residential|.*_link)$/][junction!=circular][junction!=roundabout]:closed { group: tr("kaart"); throwWarning: tr("Possible roundabout or circular junction"); } /* Check mini-roundabouts for the correct direction */ node[highway=mini_roundabout][direction!=clockwise][JOSM_search("-inview")]!:righthandtraffic:in-downloaded-area, node|z-20[highway=mini_roundabout][direction!=clockwise]!:righthandtraffic:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Mini-roundabouts should have a clockwise direction in this country"); } node|z21-[JOSM_search("inview")][highway=mini_roundabout][direction!=clockwise]!:righthandtraffic:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Mini-roundabouts should have a clockwise direction in this country"); /*fixAdd: "direction=clockwise";*/ } node[highway=mini_roundabout][direction!=anticlockwise][JOSM_search("-inview")]:righthandtraffic:in-downloaded-area, node|z-20[highway=mini_roundabout][direction!=anticlockwise]:righthandtraffic:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Mini-roundabouts should have a anticlockwise direction in this country"); } node|z21-[JOSM_search("inview")][highway=mini_roundabout][direction!=anticlockwise]:righthandtraffic:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Mini-roundabouts should have a anticlockwise direction in this country"); /*fixAdd: "direction=anticlockwise";*/ } /*************** **** Layers **** ****************/ /* Check layer tagging */ way[highway][bridge][!layer] { group: tr("kaart"); throwWarning: tr("Bridges should have a layer"); } way|z21-[JOSM_search("inview")][highway][bridge][!layer] { group: tr("kaart"); throwWarning: tr("Bridges should have a layer"); fixAdd: "layer=1"; } /* Derek Bevan */ way[highway][bridge] >[index=1] node, way[highway][bridge] >[index=-1] node {set .bridge_end_node} way[highway][!bridge] >[index=1] node, way[highway][!bridge] >[index=-1] node {set .highway_end_node} way[highway][!bridge] > node {set .non_bridge_node} /* Derek Bevan */ node.bridge_end_node.non_bridge_node!.highway_end_node, node[JOSM_search("type:node child(highway bridge) child(highway -bridge) ways:3-")] { group: tr("kaart"); throwWarning: tr("Bridges should not end in intersections (False positives may be caused by landuse, borders, buildings, etc.)"); } /* Check tunnels */ /* Derek Bevan */ way[highway][tunnel] >[index=1] node, way[highway][tunnel] >[index=-1] node {set .tunnel_end_node} way[highway][!tunnel] >[index=1] node, way[highway][!tunnel] >[index=-1] node {set .non_tunnel_end_node} way[highway][!tunnel] > node {set .non_tunnel_node} node.tunnel_end_node.non_tunnel_node!.non_tunnel_end_node, node[JOSM_search("type:node child(highway tunnel) child(highway -tunnel) ways:3-")] { group: tr("kaart"); throwWarning: tr("Tunnels should not end in intersections"); } /* Check for intersections with multiple layers */ /* Derek Bevan */ node!.bridge_end_node!.tunnel_end_node[JOSM_search("type:node ((child((highway|railway) -layer) child(highway layer -layer=0)) OR (child((highway|railway) layer=1) child(highway -layer=1)) OR (child((highway|railway) layer=2) child(highway -layer=2)) OR (child((highway|railway) layer=-1) child(highway -layer=-1)))")] { group: tr("kaart"); throwWarning: tr("Intersections with multiple layers"); } /************** *** Service *** ***************/ /* Look for highway=* that is not service but has service=* */ *[highway][highway!=service][service] { group: tr("kaart"); throwError: tr("Non-service roads should not have a service tag in most cases"); suggestAlternative: tr("{0} or remove the {1} tag", "highway=service", "service"); } /*********************** ** Country Specific *** ***********************/ /* Bosnia/Serbia/Croatia */ /* The first letter of sentences AND the first letter of quotes are capitalized */ *[name=~/^\p{Ll}.*$/][inside("BA,HR,SR")], *[name=~/^.*\.\s*\p{Ll}.*$/][inside("BA,HR,SR,RS")], *[name=~/^.*"\p{Ll}.*$/][inside("SR")] { group: tr("kaart"); throwOther: tr("Bosnian/Croatian/Serbian have capital letters at the beginning of sentences or quotes"); assertNoMatch: "node name=\"Čovek je šetao gradom\""; assertMatch: "node name=\"čovek je šetao gradom\""; assertNoMatch: "node name=\"\"Sutra ću kupiti automobil\"\""; assertMatch: "node name=\"\"sutra ću kupiti automobil\"\""; assertMatch: "node name=\"Čovek je šetao gradom. čovek je šetao gradom.\""; assertMatch: "node name=\"sutra ću kupiti automobil\""; } /* The first letter of sentences AND the first letter of quotes are capitalized, but not others */ *[name=~/(\p{Lu}\p{Ll}*([\p{Lu}\p{Ll}\s])*){2,}/][inside("BA,HR,SR,RS")] { group: tr("kaart"); throwOther: tr("Bosnian/Croatian/Serbian have capital letters at the beginning of sentences or quotes but not usually after that (with some exceptions)"); assertNoMatch: "node name=\"Čovek je šetao gradom\""; assertNoMatch: "node name=\"čovek je šetao gradom\""; assertNoMatch: "node name=\"\"Sutra ću kupiti automobil\"\""; assertNoMatch: "node name=\"\"sutra ću kupiti automobil\"\""; assertMatch: "node name=\"Čovek je šetao Гradom. čovek je šetao gradom.\""; assertMatch: "node name=\"Sutra ću Кupiti automobil\""; assertMatch: "way name=\"Цтунеа Кдле\""; } /* Encourage copying name tags to name:en */ *[name=~/^([a-zA-Z0-9.$`'‘’ -]+)$/][!"name:en"][inside("JO")]:modified, *[name=~/^([a-zA-Z0-9.$`'‘’ -]+)$/][!"name:en"][inside("JO")]:selected { group: tr("kaart"); throwError: tr("Please copy (NOT move) the name over to name:en"); fixAdd: concat("name:en=", tag("name")); } /* Check for the possibility of copying name:ar to name in Jordan */ *[name][name=*"name:en"]["name:ar"][inside("JO")]:modified, *[name][name=*"name:en"]["name:ar"][inside("JO")]:selected { group: tr("kaart"); throwWarning: tr("The default language is arabic for this country. Consider copying name:ar to name."); fixAdd: concat("name=", tag("name:ar")); } *[name][name!=*"name:ar"][name!=*"name:en"][inside("JO")]:modified, *[name][name!=*"name:ar"][name!=*"name:en"][inside("JO")]:selected { group: tr("kaart"); throwWarning: tr("The default language is arabic for this country. Consider copying name:ar to name, but check spelling first (this may also be a mismatch between name and name:ar)"); } /* Make certain there is a name tag if there is a name:* tag */ *[!name][/name:/]:modified, *[!name][/name:/]:selected { group: tr("kaart"); throwError: tr("Copy (NOT move) a translated name to the name tag"); assertMatch: "way name:en=True"; assertNoMatch: "way name=True name:en=True"; } /* Check against incorrect capitalization of names where the first word IS capitalized */ *[/^name/=~/^((\p{Lu}[\p{Ll}'-]*)|[0-9]*).*\s\p{Ll}{4,}($|\s)/][highway=~/service|living_street|residential|tertiary|secondary|primary|trunk|motorway/]{ group: tr("kaart"); throwWarning: tr("First word is capitalized, additional words are probably capitalized (check the country specific naming rules) in {0}", "{0.key}"); assertMatch: "way name=\"29 road\" highway=residential"; assertNoMatch: "way name=\"29 Road\" highway=residential"; assertMatch: "way name=\"Twenty-nine road\" highway=residential"; assertNoMatch: "way name=\"Twenty-nine Road\" highway=residential"; assertMatch: "way name=\"Al-Jazhera road\" highway=residential"; assertNoMatch: "way name=\"Al-Jazhera Road\" highway=residential"; } /* Convert ‘ and ’ to ' (unicode differences) */ *[/name/=~/.*([‘’]+).*/]:modified { group: tr("kaart"); throwOther: tr("Avoid using unicode '' in names (‘’) ({0})", "{0.key}"); suggestAlternative: "'"; /*fixAdd: concat("{0.key}", "=", replace(replace(replace(tag("{0.key}"), "‘", "'"), "’", "'"), "Street", "Avenue"));*/ /*fixAdd: concat("{0.key}", "=", concat("{0.tag}", "(check)"), " ", "{0.value}");*/ assertNoMatch: "way name=\"Testing's Road\""; assertMatch: "way name=\"Testing‘s Road\""; assertMatch: "way name=\"Testing’s Road\""; } /*************** * Misc ******** ***************/ /* Check for duplicated words in names */ *[/name/][regexp_test("\\b(\\p{L}+)\\b(?:\\s+\\1\\b)+", tag("name"), "(?i)")] { group: tr("kaart"); throwWarning: tr("Possible duplicated words in {0}", "{0.key}"); assertMatch: "node name=\"Duplicate Duplicate\""; assertNoMatch: "node name=\"Nothing to see here\""; } /* Check for ways that should have a building tag */ /* way[/^(addr.*)$/][!building]:closed, way[amenity][amenity=~/fuel/][!building]:closed { group: tr("kaart"); throwWarning: tr("Area may be a building"); } */ /* Check for pedestrian ways */ node[crossing] { set .crossing_node } way[highway=~/^(cycleway|footway|pedestrian)$/][footway=crossing] > node { set .footway_crossing } way[highway][highway!~/^(footway|cycleway)$/][oneway=yes] > node { set .possible_divided_highway } @supports (min-josm-version: 14802) { /* way[highway!~/^(motorway|trunk|primary|secondary|tertiary|residential)$/][waylength() < 35][gpx_distance() < 30 || gpx_distance() >= 50000000] > node.crossing_node.possible_divided_highway!.footway_crossing, way[!highway][waylength() < 30][gpx_distance() < 30 || gpx_distance() >= 50000000] > node.crossing_node.possible_divided_highway!.footway_crossing { group: tr("kaart"); throwWarning: tr("Possible divided-highway pedestrian crossing. Should there be a footway?"); /* suggestAlternative: tr("highway=footway, footway=crossing"); *//* } */ /* node[!inside("GR")][gpx_distance() < 30 || gpx_distance() >= 50000000].crossing_node.possible_divided_highway!.footway_crossing:in-downloaded-area { group: tr("kaart"); throwWarning: tr("Maybe add a pedestrian crossing here"); suggestAlternative: tr("highway=footway, footway=crossing"); } */ } /* Check for traffic signals with directions */ node[highway=traffic_signals][traffic_signals:direction][count(parent_tags("name")) >= 2] { group: tr("kaart"); throwError: tr("{0} should not be used on nodes connecting two different roads", "{1.key}"); } node[traffic_signal:direction] { group: tr("kaart"); throwError: tr("{0} should be traffic_signals:direction", "{0.key}"); fixChangeKey: "traffic_signal:direction => traffic_signals:direction"; } way[/^highway|railway$/][oneway=yes] > node, way[/^highway|railway$/][junction=roundabout] > node { set .onewaynode } node[highway=traffic_signals][traffic_signals:direction].onewaynode, node[highway=traffic_signals][direction].onewaynode { group: tr("kaart"); throwWarning: tr("There should not be {0} for {1} on a oneway", "{1.key}", "{0.key}"); fixRemove: "{1.key}"; } /* node[highway=traffic_signals][!direction][!traffic_signals:direction][count(parent_tags("name")) <= 1 && count(parent_tags("ref")) <= 1 && count(parent_tags("highway")) <= 1][!crossing]!.onewaynode:in-downloaded-area { group: tr("kaart"); throwWarning: tr("{0} should have a direction when not on a oneway or an intersection", "{0.value}"); suggestAlternative: "traffic_signals:direction"; } */ /* node[highway=traffic_signals][direction=~/^(forward|backward)$/] { group: tr("kaart"); throwWarning: tr("{0} should use traffic_signals:direction instead of direction for {1}", "{0.value}", "{1.value}"); fixChangeKey: "direction => traffic_signals:direction"; assertMatch: "node highway=traffic_signals direction=forward"; assertMatch: "node highway=traffic_signals direction=backward"; assertNoMatch: "node highway=traffic_signals traffic_signals:direction=backward"; assertNoMatch: "node highway=traffic_signals direction=359.9"; } */ /* Check for traffic sign issues */ /* node[traffic_sign][direction=~/^(forward|backward)$/] { group: tr("kaart"); throwWarning: tr("{0} should use traffic_sign:{1}=yes", "{0.key}", tag("direction")); fixAdd: concat("traffic_sign:", tag("direction"), "=yes"); fixRemove: "direction"; } */ relation[type=associatedStreet] >[role=house] *[addr:housenumber] { set .associatedStreet } /* Check for address tagging */ *[addr:housenumber][!addr:street][!addr:place]!.associatedStreet:modified { group: tr("kaart"); throwError: tr("Addresses should have a street"); suggestAlternative: tr("{0}, {1}, or a {2} relation", "addr:street", "addr:place", "associatedStreet"); } /* Check for house numbers in addr:street tag */ /* Derek Bevan */ *[addr:street =~ /[0-9]/] { group: tr("kaart"); throwOther: tr("Addr:street tag may contain a housenumber"); } /* Recommend removing associatedStreet relations when the houses have the addr:street tag */ relation[type=associatedStreet][name] >[role=street] *[highway][!junction][!name], relation[type=associatedStreet][name] >[role=house] *[building][!addr:street] { group: tr("kaart"); throwError: tr("{0} has no name and the {1} relation does have a name", "{0.key}", "associatedStreet"); fixAdd: concat("{1.key}", "=", parent_tag("name")); } /* relation[type=associatedStreet][name] { group: tr("kaart"); throwWarning: tr("Try to remove the {0} relation by copying the {1} tag to the {1}/{2} tags on the members (should be in error section above) and then deleting the relation", "associatedStreet", "name", "addr:street"); fixDeleteObject: this; } */ way[addr:interpolation][addr:street][!building] { group: tr("kaart"); throwWarning: tr("Interpolation lines should not have addr:street on it"); } way[highway][name][/^addr:/], way[highway][!name][/^addr:/][!addr:street] { group: tr("kaart"); throwWarning: tr("There shouldn''t be any {0} tags on a {1}", "addr:*", "{0.key}"); fixRemove: "{2.key}"; assertMatch: "way highway=residential name=random addr:street=random"; assertNoMatch: "way highway=residential addr:street=random2"; } way[highway][!name][addr:street] { group: tr("kaart"); throwWarning: tr("There shouldn''t be any {0} tags on a {1}", "{2.key}", "{0.key}"); assertMatch: "way highway=residential addr:street=random"; assertNoMatch: "way highway=residential name=random1 addr:street=random2"; } /* Check for fixme's that have tag-value pairs */ *[fixme][count(split(" ", tag("fixme"))) == 1][tag(tag("fixme")) != "none"] { group: tr("kaart"); throwWarning: tr("fixme is a tag which exists on the object, is the fixme fixed?"); assertNoMatch: "way fixme=name"; assertMatch: "way name=TODO fixme=name"; assertNoMatch: "way name=TODO fixme=\"name me\""; assertNoMatch: "way name=TODO"; } /* Check for modified landuses */ node:new < *[/landuse|boundary/]:modified!:new { group: tr("kaart"); throwError: tr("A {0} was modified, did you intend to modify it? If not, use `alt` + `j` to disconnect the node from the way.", "{0.key}"); } /* Check for separators with a space */ *[/.*/=~/.*;[\\n\\f\\v ]+.*/]:modified { group: tr("kaart"); throwError: tr("There exists a tag with {0} separators and whitespace after the separator", ";"); assertMatch: "way alt_name=\"Avenida 11; Cerrada Calle 11\""; assertNoMatch: "way alt_name=\"Avenida 11;Cerrada Calle 11\""; assertNoMatch: "way \"turn:lanes\"=\"right;through|right;through\""; } /****************** ** Golf Courses ** ******************/ way[highway=~/^(path|footway|track)$/][golf_cart=~/^(yes|designated)$/] > node { set .golf_carts_allowed } way[highway=~/^(path|footway|track)$/][motor_vehicle=~/^(yes|designated)$/] > node { set .motor_vehicle_allowed } way[highway=~/^(path|footway|track)$/][motor_vehicle=~/^(yes|designated)$/] ∈ way[leisure=golf_course], way[highway][golf] { group: tr("kaart"); throwWarning: tr("Should motor_vehicles be allowed on this path?"); suggestAlternative: "golf_cart=(yes|designated)"; } node.golf_carts_allowed.motor_vehicle_allowed { group: tr("kaart"); throwOther: tr("There is a junction point on paths/tracks where motor_vehicles are allowed and golf_carts are allowed. Is this right?"); } /*********** ** POI **** ***********/ /* Gas stations */ /* Mark gas stations without a brand but with a common brand name */ /* *[amenity=fuel][name=~/(BP|Caltex|Engen|Sasol|Shell|Total)$/][!brand] { group: tr("kaart"); throwWarning: tr("Gas station has a name but no brand"); } *|z21-[JOSM_search("inview")][amenity=fuel][name=~/(BP|Caltex|Engen|Sasol|Shell|Total)$/][!brand] { group: tr("kaart"); throwWarning: tr("Gas station has a name but no brand"); fixAdd: concat("brand=", tag("name")); } */ /* node[amenity=fuel]:modified { group: tr("kaart"); throwWarning: tr("Gas stations should be an area, not a node"); } */ /* Mark gas stations without a brand */ /* *[amenity=fuel][name][!brand] { group: tr("kaart"); throwWarning: tr("Gas station has a name but no brand"); } */ /* node[amenity=fuel][!name][!brand][fixme!=name] { group: tr("kaart"); throwWarning: tr("Gas station should have either a brand or a name"); } */ /* Check for names on POI's */ *[amenity=~/^(bank|clinic|college|courthouse|dentist|doctors|fire_station|fuel|hospital|kindergarten|pharmacy|place_of_worship|police|school|university)$/][!name][fixme!=name]:modified, *[office=~/^(government)$/][!name][fixme!=name]:modified, *[tourism=~/^(hotel)$/][!name][fixme!=name]:modified { group: tr("kaart"); throwWarning: tr("There should be a name or a fixme=name"); } /* Ensure modified poi's have an addr:street */ /* *[tourism=~/^(hotel)$/][!addr:street][!addr:place]:modified, *[office=~/^(government)$/][!addr:street][!addr:place]:modified, *[amenity=~/^(bank|clinic|college|courthouse|dentist|doctors|fire_station|fuel|hospital|kindergarten|pharmacy|place_of_worship|police|school|university)$/][!addr:street][!addr:place]:modified { group: tr("kaart"); throwError: tr("Try to add {0} or {1} to new or modified POI's", "addr:street", "addr:place"); } */ *[fixme=~/^.*[ A-Z]+.*$/]:modified, *[fixme][fixme!~/^[a-z:]*$/]:modified { group: tr("kaart"); throwError: tr("fixme should ONLY be a tag"); suggestAlternative: tr("note"); assertMatch: "way fixme=\"bad name\""; assertNoMatch: "way fixme=\"name\""; assertMatch: "way fixme=\"Name\""; assertNoMatch: "way fixme=\"destination:lang:int\""; } /************************************** ***** Destination Tagging ************ **************************************/ /* Check for destination_sign relations where there shouldn't be destination_sign relations */ /* relation[type=destination_sign][!distance][!time] > way[highway=motorway], relation[type=destination_sign][!distance][!time] > way[highway=trunk][oneway=yes] { group: tr("kaart"); throwError: tr("{0} relations should not be used on {1}", "destination_sign", tag("highway")); suggestAlternative: "destination:lanes"; } */ /* Check destination lane tagging */ /* Check toll roads */ /* *[destination:ref:lanes=~/(.*)toll(.*)$/] { group: tr("kaart"); throwWarning: tr("Toll roads in destination:ref:lanes are lower case, instead of upper case"); fixAdd: concat("destination:ref:lanes=", replace(tag("destination:ref:lanes"), "toll", "Toll")); } */ *[destination:ref=~/(.*)toll(.*)$/] { group: tr("kaart"); throwWarning: tr("Toll roads in destination:ref are lower case, instead of upper case"); fixAdd: concat("destination:ref=", replace(tag("destination:ref"), "toll", "Toll")); } /* *[destination:lanes:ref] { group: tr("kaart"); throwWarning: tr("destination:lanes:ref should be destination:ref:lanes"); fixChangeKey: "destination:lanes:ref => destination:ref:lanes"; } */ /* *[destination:lanes:ref:to] { group: tr("kaart"); throwWarning: tr("destination:lanes:ref:to should be destination:ref:to:lanes"); fixChangeKey: "destination:lanes:ref:to => destination:ref:to:lanes"; } */ /* Check *_links to make certain they have destination:ref */ way[highway=~/^.*_link$/][ref][!destination:ref][oneway=yes] { group: tr("kaart"); throwWarning: tr("Link has ref but no destination:ref"); } way|z21-[JOSM_search("inview")][highway=~/^.*_link$/][ref][!destination:ref][oneway=yes] { group: tr("kaart"); throwWarning: tr("Link has ref but no destination:ref"); fixChangeKey: "ref => destination:ref"; } /* Check if *_links are appropriately ref'd */ way[highway!~/^.*_link$/][ref] > node.end_of_link { set .has_ref } way[highway=~/^.*_link$/][destination:ref] > node.end_of_link { set .has_destination_ref } /* TODO figure out way to select the _link */ /* TODO remove once java validator is pushed out */ node[count(parent_tags("ref")) > 0 && parent_tag("destination:ref") != parent_tag("ref")].end_of_link { group: tr("kaart"); throwWarning: tr("The destination:ref tag does not match the ref tag"); /*fixAdd: concat("destination:ref=", parent_tag("ref"));*/ } /**** not working *** node[count(parent_tags("destination:ref")) != count(parent_tags("destination:ref"))].end_of_link.has_destination_ref { group: tr("kaart"); throwWarning: tr("The destination:ref tag does not match the next links'' destination:ref tag"); /*fixAdd: concat("destination:ref=", parent_tag("ref"));*//* } */ /* Check for connected links with different destination:ref tags */ /* Derek Bevan */ way[highway =~ /_link/] > node[count(uniq_list(parent_tags("destination:ref"))) > 1] { group: tr("kaart"); throwWarning: tr("Connected links have different destination:ref tags. Are both tags correct?"); } /* Check if link has destination:ref */ way[highway=~/.*_link$/][!destination:ref] >[index=-1] node { set .link_no_ref } node.link_no_ref.has_ref { group: tr("kaart"); throwWarning: tr("Link may be missing a destination:ref tag"); /* suggestAlternative: tr("{0} or {1}", "{0.key}", "{1.key}"); */ } /* way[highway][highway=~/.*_link$/][!/destination:street:(forward|backward)/][!/destination:ref:(forward|backward)/][oneway=no] { group: tr("kaart"); throwWarning: tr("_link does not have {0} or {1}", "{2.key}", "{3.key}"); suggestAlternative: tr("{0} or {1}", "{2.key}", "{3.key}"); assertMatch: "way highway=motorway_link oneway=no"; assertNoMatch: "way highway=motorway oneway=no"; assertMatch: "way highway=motorway_link destination:ref=M22 oneway=no"; assertNoMatch: "way highway=motorway_link destination:ref:forward=M22 oneway=no"; assertMatch: "way highway=motorway_link destination:street=M22 oneway=no"; assertNoMatch: "way highway=motorway_link destination:street:forward=M22 oneway=no"; assertNoMatch: "way highway=motorway_link oneway=yes"; } */ /* Check for destination:street that is the same as the name of the street */ way[highway][name][destination:street][tag("name") == tag("destination:street")] { group: tr("kaart"); throwError: tr("The name of the road is the same as the name of the destination:street"); fixRemove: "destination:street"; } /* Ensure that the destination:lanes/:forward/:backward match the number of lanes on the road */ /* way[highway][!lanes][destination:lanes] { group: tr("kaart"); throwWarning: tr("There should be a lanes tag when there is a destination:lanes tag"); } way[highway][lanes][destination:lanes][oneway][count(split("|", tag("destination:lanes"))) != tag("lanes")], way[highway][lanes:forward][destination:lanes:forward][oneway][count(split("|", tag("destination:lanes:forward"))) != tag("lanes:forward")], way[highway][!lanes:forward][destination:lanes:forward][count(split("|", tag("destination:lanes:forward"))) != tag("lanes") / 2], way[highway][lanes:backward][destination:lanes:backward][oneway][count(split("|", tag("destination:lanes:backward"))) != tag("lanes:backward")], way[highway][!lanes:backward][destination:lanes:backward][count(split("|", tag("destination:lanes:backward"))) != tag("lanes") / 2], way[highway][lanes][destination:ref:lanes][oneway][count(split("|", tag("destination:ref:lanes"))) != tag("lanes")], way[highway][lanes:forward][destination:ref:lanes:forward][oneway][count(split("|", tag("destination:ref:lanes:forward"))) != tag("lanes:forward")], way[highway][!lanes:forward][destination:ref:lanes:forward][count(split("|", tag("destination:ref:lanes:forward"))) != tag("lanes") / 2], way[highway][lanes:backward][destination:ref:lanes:backward][oneway][count(split("|", tag("destination:ref:lanes:backward"))) != tag("lanes:backward")], way[highway][!lanes:backward][destination:ref:lanes:backward][count(split("|", tag("destination:ref:lanes:backward"))) != tag("lanes") / 2] { group: tr("kaart"); throwWarning: tr("The implied lanes in {0} do not match the lanes in {1}", "{2.tag}", "{1.key}"); } */ *[destination=~/^(?i)(ave|blvd|str) .*$/], *[destination=~/^.* (?i)(ave|blvd|str)$/], *[destination:to=~/^(?i)(ave|blvd|str) .*$/], *[destination:to=~/^.* (?i)(ave|blvd|str)$/], *[destination:street=~/^(?i)(ave|blvd|str) .*$/], *[destination:street=~/^.* (?i)(ave|blvd|str)$/], *[destination:lanes=~/^.* (?i)(ave|blvd|str)$/], *[destination:lanes=~/^(?i)(ave|blvd|str) .*$/] { group: tr("kaart"); throwWarning: tr("{0} shouldn''t have a street abbreviation in {1}", tag("name"), "{0.key}"); /*fixAdd: concat("{0.key}=", replace(replace(replace(replace(replace(replace(replace(replace( tr("{0}", "{0.key}"), "ave", "Avenue"), "ave.", "Avenue"), "Ave", "Avenue"), "Ave.", "Avenue"), "blvd", "Boulevard"), "blvd.", "Boulevard"), "Blvd", "Boulevard"), "Blvd.", "Boulevard") );*/ } /* Check for bad destination_sign relations */ /* way[highway] > node { set .road_node; } relation[type=destination_sign] >[role=sign] node.road_node { group: tr("kaart"); throwWarning: tr("The {0} role should typically be where the {0} is, not on the road way itself", "sign"); } */ /* Check for via roles in destination_sign relations */ /* relation[type=destination_sign] >[role=via] * { group: tr("kaart"); throwError: tr("{0} is not a valid role in {1} relations", "via", "destination_sign"); suggestAlternative: "intersection"; } */ /* Check for from/to roles that are nodes */ /* relation[type=destination_sign] >[role=to] node { group: tr("kaart"); throwWarning: tr("{0} should be a way", "to/from"); } */ /* Ensure tagging consistency for destination::forward/:backward */ *[/destination:(forward|backward):(.*)/] { group: tr("kaart"); throwError: tr("Bad tagging scheme for destination directions"); suggestAlternative: "destination::(forward|backward)"; assertNoMatch: "way destination:ref:to:forward=A9"; assertMatch: "way destination:forward:ref:to=A9"; } /* Ensure that destination_sign relations have a from role */ /* relation[type=destination_sign][count_roles("from") == 0] { group: tr("kaart"); throwWarning: tr("{0} should have a {1} role", "destination_sign", "from"); } */ /************* * Oneway ***** *************/ /* Check for common backward/forward tagging */ way[motor_vehicle:backward=no] { throwWarning: tr("Try to use oneway tags"); fixAdd: "oneway=yes"; fixRemove: "motor_vehicle:backward"; assertMatch: "way motor_vehicle:backward=no"; assertNoMatch: "way oneway=yes"; } way[motorcycle:backward=yes] { throwWarning: tr("Try to use oneway tags"); fixAdd: "oneway:motorcycle=no"; fixRemove: "motorcycle:backward"; assertMatch: "way motorcycle:backward=yes"; assertNoMatch: "way oneway=yes"; } way[moped:backward=yes] { throwWarning: tr("Try to use oneway tags"); fixAdd: "oneway:moped=no"; fixRemove: "moped:backward"; assertMatch: "way moped:backward=yes"; assertNoMatch: "way oneway=yes"; } way[mofa:backward=yes] { throwWarning: tr("Try to use oneway tags"); fixAdd: "oneway:mofa=no"; fixRemove: "mofa:backward"; assertMatch: "way mofa:backward=yes"; assertNoMatch: "way oneway=yes"; } way[/(motorcycle|moped|mofa|motor_vehicle|vehicle):(forward|backward)/] { throwOther: tr("Try to use oneway tags"); } /* Check for dead-end oneways */ /* Derek Bevan */ way[highway][oneway=yes]!:closed >[index=-1] node:in-downloaded-area!:connection, way[highway][oneway=yes]!:closed >[index=1] node:in-downloaded-area!:connection { group: tr("kaart"); throwError: tr("Dead-end oneways"); } /* Check for converging oneways with no exit */ /* Derek Bevan */ way[highway][oneway=yes] >[index=-1] node:in-downloaded-area:connection {set .end_oneway} way[highway][oneway=yes] >[index!=-1] node:in-downloaded-area:connection, way[highway][oneway!=yes] > node:in-downloaded-area:connection {set .way_out} node.end_oneway!.way_out { group: tr("kaart"); throwError: tr("Converging oneways with no exit"); } /************* * refs ******* *************/ /* @supports (min-josm-version: 15323) { /* Check for ways that don't have the parent relation ref */ /* relation[type=route][route=road][ref] > way[ref][!regexp_test(concat("^(", join_list("|", uniq_list(split(";", join(";", tag_regex("ref"))))), "|;?)+$"), join_list(";", uniq_list(sort_list(parent_tags("ref")))), "i")] { group: tr("kaart"); throwWarning: tr("Parent ref not in child ref"); set .parent_ref_not_in_child_ref; } /* Check for ways that have a ref that is not in a parent */ /* relation[type=route][route=road] > way[highway][ref][count(parent_tags("ref")) != 0 && !regexp_test(join_list(";", uniq_list(sort_list(split(";", join_list(";", tag_regex("^(ref|nat_ref|int_ref|reg_ref)$")))))), join_list(";", uniq_list(sort_list(parent_tags("ref")))), "i")]!.parent_ref_not_in_child_ref { group: tr("kaart"); throwWarning: tr("A ref on a way is not in a parent relation"); set .ref_not_in_relation } } */ /* Find ways that are missing the parent relation ref or have a different ref than the parent relation */ /* Derek Bevan */ way[highway =~ /(tertiary|secondary|primary|trunk|motorway)/][JOSM_search("type:way child(type:relation route=road ref)")][(parent_tag("ref") != tag("ref")) || JOSM_search("-ref")] { group: tr("kaart"); throwOther: tr("Ref on route relation may hint at missing highway refs"); } /******************************** * Connectivity relation checks * ********************************/ relation[type=connectivity][/connectivity/!~/^([0-9]:([0-9],?)[|$]?)+/] { group: tr("kaart"); throwError: tr("{0} should not have multiple from lanes in the same statement or is otherwise malformed", "{0.key}"); assertMatch: "relation type=connectivity connectivity=\"1,2:3|2:4\""; assertNoMatch: "relation type=connectivity connectivity=\"1:3|2:4\""; assertMatch: "relation type=connectivity connectivity=\" 1:1|2:2|3:3\""; assertNoMatch: "relation type=connectivity connectivity=\"1:1|2:2|3:3\""; } relation[type=connectivity][connectivity][regexp_test("^(([0-9]):\\2(\\|?))+$", tag("connectivity"), "(?i)")] { group: tr("kaart"); throwError: tr("{0} is redundant and unnecessary, since it is a simple case", "{0.key}"); assertMatch: "relation type=connectivity connectivity=\"1:1|2:2|3:3\""; assertNoMatch: "relation type=connectivity connectivity=\"1:1|2:2|3:2\""; } relation[type=connectivity][count_roles("via") < 1 || count_roles("from") != 1 || count_roles("to") != 1] { group: tr("kaart"); throwError: tr(“Check number of ‘from’, ‘via’, and ‘to’ members.”); } /** TODO list * 1) Count max lanes with :lanes suffix * 2) Ensure that the traffic can actually go into the lanes (check for hgv:lanes=no|yes|no and hgv:lanes=no|no|yes with connectivity=1:1|2:2|3:3) * 3) Get splitting ways for this relation in JOSM working properly (src/org/openstreetmap/josm/command/SplitWayCommand.java +313) */ /**************** * Invalid Tags * ****************/ *[/.*/=~/^(?i)(null|.*Object object.*|.*\{.*\}.*|\*)$/] { group: tr("kaart"); throwError: tr("Probable Invalid Tags - {0}", "{0.tag}"); /*fixRemoveKey:*/ assertMatch: "way name=null"; assertMatch: "way name=\"Object object\""; assertMatch: "way name=\"object object\""; } /****************** * Access Tagging * ******************/ *[/^(.*access.*|(bus|psv|motorcar|motor_vehicle|mofa|motorcycle|pedestrian):lanes.*)$/][/^(.*access.*|(bus|psv|motorcar|motor_vehicle|mofa|motorcycle|pedestrian):lanes.*)$/!~/^((yes|no|private|permissive|destination|delivery|customers|designated|use_sidepath|dismount|agricultural|forestry|discouraged|unknown|wlan|wired|service|terminal)?([|])?)*$/] { group: tr("kaart"); throwWarning: tr("Probable bad access tag (check spelling)"); assertNoMatch: "way \"bus:lanes:forward=designated|\""; assertMatch: "way \"bus:lanes:forward\"=\"desginated|\""; assertMatch: "way \"access=desginated\""; assertNoMatch: "node highway=crossing"; assertNoMatch: "node internet_access=wlan"; } /* Toll booths, entrances, and lift_gates default to access=yes */ /* Taylor Smock, Derek Bevan */ /* way[highway] > node[barrier][barrier =~ /^(gate)$/][!access] { group: tr("kaart"); throwWarning: tr("Gates missing access tags"); /* suggestAlternative: "access"; *//* } */ /* Check for missing arabic characters */ *[name][name:ar][name!=*"name:ar"][name=~/شارع/][name:ar!~/شارع/][inside("JO")], *[name][name:ar][name!=*"name:ar"][name:ar=~/شارع/][name!~/شارع/][inside("JO")] { group: tr("kaart"); throwError: tr("{0} does not match {2} and has a Street suffix (شارع).", "{3.key}", "{4.key}"); assertMatch: "way name=\"شارع جبل عرفات\" name:ar=\"جبل عرفات\""; assertNoMatch: "way name=\"جبل عرفات\" name:ar=\"جبل عرفات\""; } *[/^(name|alt_name)$/=~/(?i)antiga/] { group: tr("kaart"); throwWarning: tr("{0} in the name", "{0.value}"); assertMatch: "way name=\"antiga testing\""; assertMatch: "way name=\"Antiga testing\""; assertNoMatch: "way name=\"Something else\""; assertMatch: "way name=\"Rua Sao Paulo Antiga\""; }