Public Transport GTFS
These rules add additional checks for public transportation tagging. One major part are tags in use with GTFS and PTNA (OSM-Wiki, website).
See Public Transport GTFS preset for a corresponding preset.
Help needed
Please, feel free to:
- enhance and update the preset
- translate at Launchpad
- create a nice icon.
- write me an email
Notes
- Really beta so far and under development. Expect false positives and lots of changes.
- The tagging schema is still under heavy development and therefore the rules might change quite often.
Supported Tags
So far checks in the categories Missing tags
, Conflicting tags
and Value syntax
for the following tags exists.
- colour
- duration
- fee
- from
- gtfs:feed
- gtfs:name (no page, yet)
- gtfs:route_id
- gtfs:shape_id
- gtfs:trip_id
- gtfs:trip_id:sample
gtfs:source_datereplace bygtfs:release_date
- gtfs:release_date
- gtfs:stop_id
- interval
- local_ref
- network network
- network:guid
- network:short
- opening_hours
- operator
- operator:guid
- operator:short
- railway=stop
- ref
- ref:IFOPT
- source
- to
- website
Rules source code
meta { title: "Public Transport GTFS"; link: "https://josm.openstreetmap.de/wiki/Rules/PublicTransportGtfs"; description: "Special rules for Public transportation supporting GTFS and tags for PTNA. "; author: "skyper"; version: "0.0.42_2024-02-07"; } /* ----------------------- * * Classes */ relation[type=route_master][route_master =~ /^(bus|coach|share_taxi|trolleybus|train|light_rail|subway|tram|monorail|funicular|ferry|aerialway|school_bus|walking_bus)$/] { set PtRouteMaster; } /* Note: Only relations, routes on way are not supported */ relation[type=route ][route =~ /^(bus|coach|share_taxi|trolleybus|train|light_rail|subway|tram|monorail|funicular|ferry|aerialway|school_bus|walking_bus)$/] { set PtRoute; } relation[gtfs:route_id][type=route_master] > * { set ChildMasterRoute_id; } *[/^(bus|coach|share_taxi|trolleybus|train|light_rail|subway|tram|monorail|funicular|ferry)$/ =~ /^(yes|no)$/][!aerialway] { set PtVehicle; } *[/^(bus|coach|share_taxi|trolleybus|train|light_rail|subway|tram|monorail|funicular|ferry)$/ !~ /yes/][!aerialway] { set noPtVehicle; } *[public_transport =~ /^(platform|stop_area|stop_position)$/] { set PtStop; } /* ----------------------- * * Rules */ /* ----------------------- * * missing tag */ /* {0.tag} without {1.key} (error level) */ /* route_master and route relations */ /* {0.tag} without {1.key} (warning level) */ /* stop_area, stop_position, platform */ *[public_transport =~ /^(platform|stop_position)$/][public_transport][!local_ref], *.PtStop[public_transport][!network] { throwWarning: tr("`{0}` without `{1}*`.", "{1.tag}", "{2.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node public_transport=stop_position"; assertMatch: "way public_transport=platform"; assertNoMatch: "node public_transport=stop_position network=a local_ref=2"; } /* route_master and route relations */ relation.PtRouteMaster[type=route_master][!colour], relation.PtRouteMaster[type=route_master][!fee], relation.PtRouteMaster[type=route_master][!gtfs:name], relation.PtRouteMaster[type=route_master][!gtfs:route_id], relation.PtRouteMaster[type=route_master][!network], relation.PtRouteMaster[type=route_master][!operator], relation.PtRouteMaster[type=route_master][!ref], relation.PtRouteMaster[type=route_master][!source], relation.PtRouteMaster[type=route_master][!website], relation.PtRoute[type=route][!colour], relation.PtRoute[type=route][!duration]["public_transport:version"=2], relation.PtRoute[type=route][!interval]["public_transport:version"=2], relation.PtRoute[type=route][!fee], relation.PtRoute[type=route][!from]["public_transport:version"=2], relation.PtRoute[type=route][!to]["public_transport:version"=2], relation.PtRoute[type=route][!gtfs:name]["public_transport:version"=1], relation.PtRoute[type=route][!gtfs:route_id], relation.PtRoute[type=route][!network], relation.PtRoute[type=route][!opening_hours], relation.PtRoute[type=route][!operator], relation.PtRoute[type=route][!ref], relation.PtRoute[type=route][!source], relation.PtRoute[type=route][!website] { throwWarning: tr("Public transport {0} relation without `{1}*`.", "{1.value}", "{2.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "relation type=route route=train"; assertMatch: "relation type=route_master route_master=bus"; /* Does not make sense with that many keys above */ /* assertNoMatch: "relation type=route route=train network:short=MVV"; * assertNoMatch: "relation type=route_master route_master=bus colour=green"; */ } /* {0.tag} without {1.key} (info level) */ /* route_master and route relations */ relation.PtRouteMaster[type=route_master][!network:short][network], relation.PtRouteMaster[type=route_master][!operator:short][operator], relation.PtRoute[type=route][!network:short][network], relation.PtRoute[type=route][!operator:short][operator] { throwOther: tr("Public transport {0} relation without `{1}*`.", "{1.value}", "{2.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "relation type=route route=train operator=A"; assertMatch: "relation type=route_master route_master=bus operator=A"; assertNoMatch: "relation type=route route=train"; assertNoMatch: "relation type=route_master route_master=bus"; } /* {0.tag} without {1.key} or {2.key} (warning level) */ /* stop_area, stop_position, platform */ *.PtStop[!ref:IFOPT][!gtfs:stop_id][public_transport] { throwWarning: tr("`{0}` without `{1}*` or `{2}*`.", "{3.tag}", "{1.tag}", "{2.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node public_transport=stop_position train=yes railway=halt"; assertNoMatch: "node public_transport=stop_position train=yes railway=stop ref:IFOPT=ch:06666:4"; } /* {0.key} without {1.key}, {2.key} or {3.key} (warning level) */ /* stop_area, stop_position, platform */ *.PtStop[ref:IFOPT ][!gtfs:feed][!network:guid][!operator:guid], *.PtStop[gtfs:stop_id][!gtfs:feed][!network:guid][!operator:guid] { throwWarning: tr("Public transport stop with `{0}=*` but none of `{1}*`, `{2}*` or `{3}*`.", "{1.key}", "{2.tag}", "{3.tag}", "{4.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node public_transport=stop_position ref:IFOPT=A"; assertNoMatch: "node public_transport=stop_position ref:IFOPT=A gtfs:feed=A"; } /* route_master and route relations */ relation["public_transport:version"=2].PtRoute[!gtfs:shape_id][!gtfs:trip_id][!gtfs:trip_id:sample], relation[gtfs:route_id].PtRouteMaster[!gtfs:feed][!network:guid][!operator:guid], relation[gtfs:route_id ].PtRoute[!gtfs:feed][!network:guid][!operator:guid], relation[gtfs:shape_id ].PtRoute[!gtfs:feed][!network:guid][!operator:guid], relation[gtfs:trip_id ].PtRoute[!gtfs:feed][!network:guid][!operator:guid], relation[gtfs:trip_id:sample].PtRoute[!gtfs:feed][!network:guid][!operator:guid] { throwWarning: tr("Public transport relation with `{0}` but none of `{1}*`, `{2}*` or `{3}*`.", "{0.tag}", "{2.tag}", "{3.tag}", "{4.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "relation type=route_master route_master=train gtfs:route_id=0-7-242-19j-1"; assertNoMatch: "relation type=route_master route_master=train gtfs:route_id=0-7-242-19j-1 network:guid=DE-BY-MVV"; assertMatch: "relation type=route route=bus gtfs:shape_id=0-7-242-19j-1.26.R"; assertNoMatch: "relation type=route_master route=bus"; } /* {0.tag} without any (warning level) */ /* stop_position */ /* ToDo: Check against parent relation vehicle type */ /* ToDo: Extend to check for parent relation value and offer fix */ /* *[public_transport =~ /platform|stop_position/][highway!=bus_stop][public_transport].noPtVehicle { */ /* needs check the other way around as access tags are problematic with ways */ node[public_transport=stop_position].noPtVehicle { throwWarning: tr("`{0}` without serving vehicle type specified.", "{0.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node public_transport=stop_position"; assertNoMatch: "node public_transport=stop_position bus=yes"; } /* platform */ /* access tags are only useful on stop_position */ area[public_transport=platform][highway].PtVehicle { throwWarning: tr("`Problematic access tag on {0} together with {1}. Only stop positions need it, according to the wiki.", "{0.tag}", "{1.tag}"); group: tr("Public Transport GTFS: suspicious tag combination"); assertMatch: "way public_transport=platform highway=platform bus=yes"; assertNoMatch: "way public_transport=platform bus=yes"; } area[public_transport=platform][!highway].PtVehicle { throwWarning: tr("`Access tag on {0}. Only stop positions need it, according to the wiki.", "{0.tag}"); group: tr("Public Transport GTFS: suspicious tag combination"); assertMatch: "way public_transport=platform bus=yes"; assertNoMatch: "way public_transport=platform"; } /* {0.tag} with any (info level) */ /* platform */ /* access tags are only useful on stop_position */ node[public_transport=platform].PtVehicle { throwOther: tr("`Serving vehicle type specified on {0}. Only stop positions need it, according to the wiki.", "{0.tag}"); group: tr("Public Transport GTFS: suspicious tag combination"); assertMatch: "node public_transport=platform bus=yes"; assertNoMatch: "node public_transport=platform"; } /* {0.tag} and {1.tag} without {2.key} (warning level) */ /* stop_position, platform */ node[public_transport=stop_position][train=yes][railway!=stop], node[public_transport=stop_position][train=yes][!ref], *[public_transport=platform][railway=platform][!ref] { throwWarning: tr("`{0}` with `{1}` but without `{2}`.", "{0.tag}", "{1.tag}", "{2.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node public_transport=stop_position train=yes railway=halt"; assertNoMatch: "node public_transport=stop_position train=yes railway=stop ref=2"; } /* {0.tag} and {1.tag} without {2.key}, {3.key} or {4.key} (warning level) */ /* One of many {0.key} but no {1.key} */ /* gtfs:release_date */ *[/(^gtfs:.+|.+:gtfs|^ref:IFOPT)$/][!gtfs:release_date] { throwWarning: tr("GTFS tag without `{0}*`.", "{1.tag}"); group: tr("Public Transport GTFS: missing tag"); assertMatch: "node gtfs:route_id=1"; assertMatch: "node note:gtfs=A"; assertMatch: "node ref:IFOPT=1"; assertNoMatch: "node ref:IFOPT=1 gtfs:release_date=2019"; } /* ----------------------- * value syntax */ /* route_id, shape_id, trip_id */ /* FIXME: Split checks per feed instead of one global regex. */ /* FIXME: Get proper syntax as regex displayed. */ *[gtfs:route_id ][gtfs:route_id !~ /^[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+(;[ ]?[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+)*$/], *[gtfs:shape_id ][gtfs:shape_id !~ /^[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR](;[ ]?[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR])*$/], *[gtfs:trip_id ][gtfs:trip_id !~ /^[1-9][0-9]{0,3}\.T[023A]\.[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR](;[ ]?[1-9][0-9]{0,3}\.T[023A]\.[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR])*$/], *[gtfs:trip_id:sample][gtfs:trip_id:sample !~ /^[1-9][0-9]{0,3}\.T[023A]\.[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR](;[ ]?[1-9][0-9]{0,3}\.T[023A]\.[0-9]{1,2}-[A-Z]?[0-9]{1,3}[A-Z]?(-[0-9A-Z])?-[js]?[1-9][0-9j]a?-[0-9]+\.[1-9][0-9]{0,2}\.[HR])*$/] { throwError: tr("Value `{0}` for `{1}=*` does not match value syntax.", "{0.value}", "{0.key}"); /* throwError: tr("Value `{0}` for `{1}=*` does not match value syntax `{2}`.", "{0.value}", "{0.key}", "{1.value}"); */ group: tr("Public Transport GTFS: value syntax"); set GtfsIdSyntax; assertMatch: "relation gtfs:route_id=7-342-j1j-1.H"; assertNoMatch: "relation gtfs:route_id=92-731-2-j21-1"; /* CH */ assertNoMatch: "relation gtfs:route_id=7-342-j1j-1"; /* DE-BW-SBG */ assertNoMatch: "relation gtfs:route_id=7-342-j1j-1;7-342-j1j-5"; assertNoMatch: "relation gtfs:route_id=90-742-B-j20-1"; /* DE-BW-SWEG */ assertNoMatch: "relation gtfs:route_id=10-11-I-j20-1"; /* DE-BW-VAG */ assertNoMatch: "relation gtfs:route_id=5-20-21a-1"; /* DE-BW-bodo */ assertMatch: "relation gtfs:shape_id=11-4-I-j20-1.23."; assertNoMatch: "relation gtfs:shape_id=11-4-I-j20-1.23.H"; /* DE-BW-VAG */ assertNoMatch: "relation gtfs:shape_id=92-R01-F-j20-1.117.R"; /* DE-SPNV */ assertNoMatch: "relation gtfs:shape_id=0-S3-E-j20-2.1.R"; /* DE-BW-SWEG */ assertNoMatch: "relation gtfs:shape_id=5-20-21a-1.40.H"; /* DE-BW-bodo */ assertMatch: "relation gtfs:trip_id=1108.T2.11-4-I-j20-1.10."; assertNoMatch: "relation gtfs:trip_id=1108.T2.11-4-I-j20-1.10.H"; /* DE-BW-VAG */ assertNoMatch: "relation gtfs:trip_id=29.T0.7-342-j1j-1.50.H"; /* DE-BW-SBG */ assertNoMatch: "relation gtfs:trip_id=2.TA.90-742-B-j20-1.4.R"; /* DE-BW-SWEG */ assertNoMatch: "relation gtfs:trip_id=21.T0.5-20-21a-1.40.H"; /* DE-BW-bodo */ } /* gtfs:feed, *:guid, gtfs:release_date */ /* FIXME: Get proper list of valid values. */ /* FIXME: Get proper syntax as regex displayed. */ *[gtfs:feed ][gtfs:feed !~ /^(([A-Z]{2}-){2}[a-zA-Z]{2}.*|[A-Z]{2}-(Alle|Flixbus)|BO-C-Cochabamba|(CH|LU)-[a-zA-Z]{3,}|CO-BOY-[A-Z].+|DE-SPNV|DE-S-und-U-Bahnen|ES-AR-Z-[A-Z].+|FR-IDF-(r|[a-zA-Z]{3,}.*)|MG-T-Antananarivo)$/], *[network:guid ][network:guid !~ /^(([A-Z]{2}-){2}[a-zA-Z]{2}.*|[A-Z]{2}-Flixbus|BO-C-Cochabamba|(CH|LU)-[a-zA-Z]{3,}|CO-BOY-[A-Z].+|ES-AR-Z-[A-Z].+|FR-IDF-(r|[a-zA-Z]{3,}.*)|MG-T-Antananarivo)$/], *[operator:guid][operator:guid !~ /^(([A-Z]{2}-){2}[a-zA-Z]{2}.*|[A-Z]{2}-Flixbus|BO-C-Cochabamba|(CH|LU)-[a-zA-Z]{3,}|CO-BOY-[A-Z].+|ES-AR-Z-[A-Z].+|FR-IDF-(r|[a-zA-Z]{3,}.*)|MG-T-Antananarivo)$/], *[gtfs:release_date][gtfs:release_date !~ /^20(2[0-4])-(0[1-9]|1[0-2])-(3[01]|[12][0-9]|0[1-9])$/] { throwError: tr("Value `{0}` for `{1}=*` does not match value syntax.", "{0.value}", "{0.key}"); /* throwError: tr("Value `{0}` for `{1}=*` does not match value syntax `{2}`.", "{0.value}", "{0.key}", "{1.value}"); */ group: tr("Public Transport GTFS: value syntax"); assertMatch: "relation gtfs:feed=DE-SH.NAH"; assertMatch: "relation gtfs:feed=df-SL-saarVV"; assertNoMatch: "relation gtfs:feed=DE-SH-NAH.SH"; assertNoMatch: "relation gtfs:feed=DE-BY-VVM-Mittelschwaben"; assertNoMatch: "relation gtfs:feed=AU-SA-Adelaide-Metro"; assertNoMatch: "relation gtfs:feed=DE-SL-saarVV"; assertMatch: "relation gtfs:release_date=2016-09-30"; assertMatch: "relation gtfs:release_date=2020-9-3"; assertMatch: "relation gtfs:release_date=2016-09-30"; assertNoMatch: "relation gtfs:release_date=2020-09-30"; } /* ref:IFOPT, gtfs:stop_id */ /* FIXME: Get proper syntax as regex displayed. */ /* *[ref:IFOPT ][ref:IFOPT !~ /^(gen:[0-9]{4}|[a-z]{2}:[0-9]{5}):[1-9][0-9]{0,4}(:[0-9]{1,2}(:([A-Z]+[ ]?)?[1-9][0-9]{0,2}[A-Z]?)?)?$/], */ /* *[gtfs:stop_id][gtfs:stop_id !~ /^(gen:[0-9]{4}|[a-z]{2}:[0-9]{4,5}):[1-9][0-9]{0,4}(:[0-9]{1,2}(:([A-Z]+[ ]?)?[1-9][0-9]{0,2}[A-Z]?)?)?$/] { */ *[ref:IFOPT ][ref:IFOPT !~ /^(gen:[0-9]{4}|[a-z]{2}:[0-9]{5}):[1-9][0-9]{0,4}(:[0-9]{1,2}(:.+)?)?$/], *[gtfs:stop_id][gtfs:stop_id !~ /^(gen:[0-9]{4}|[a-z]{2}:[0-9]{4,5}):[1-9][0-9]{0,4}(:[0-9]{1,2}(:.+)?)?$/] { throwError: tr("Value `{0}` for `{1}=*` does not match value syntax.", "{0.value}", "{0.key}"); /* throwError: tr("Value `{0}` for `{1}=*` does not match value syntax `{2}`.", "{0.value}", "{0.key}", "{1.value}"); */ group: tr("Public Transport GTFS: value syntax"); set ErrorSyntaxIFOPT; assertMatch: "node ref:IFOPT=+1"; assertMatch: "node ref:IFOPT=aa:aa:09"; assertMatch: "node ref:IFOPT=ch:3001:64883"; assertMatch: "node ref:IFOPT=gen:91888:5599:0:956R"; assertNoMatch: "node ref:IFOPT=ch:23001:64883"; assertNoMatch: "node ref:IFOPT=de:08315:6504:0:14"; assertNoMatch: "node ref:IFOPT=gen:9188:5599:0:956R"; assertNoMatch: "node ref:IFOPT=de:09162:1179:3:KIF 1"; assertNoMatch: "node ref:IFOPT=ch:23005:6"; assertNoMatch: "node gtfs:stop_id=ch:3001:64883"; } /* route_id, shape_id, trip_id */ *[gtfs:route_id ][count(split(";", tag("gtfs:route_id"))) > 1], *[gtfs:shape_id ][count(split(";", tag("gtfs:shape_id"))) > 1], *[gtfs:trip_id ][count(split(";", tag("gtfs:trip_id"))) > 1], *[gtfs:trip_id:sample][count(split(";", tag("gtfs:trip_id:sample"))) > 1] { throwOther: tr("Multiple values `{0}` for `{1}=*`.", "{0.value}", "{0.key}"); group: tr("Public Transport GTFS: value syntax"); set MultipleID; assertMatch: "relation gtfs:route_id=7-342-j1j-1;7-342-j1j-5"; assertNoMatch: "relation gtfs:route_id=7-342-j1j-1.H"; } /* old syntax ref:IFOPT */ *[ref:IFOPT ][ref:IFOPT =~ /^gen:[0-9]{4}:[1-9][0-9]{0,4}(:[0-9]{1,2}(:.+)?)?$/], *[gtfs:stop_id][gtfs:stop_id =~ /^gen:[0-9]{4}:[1-9][0-9]{0,4}(:[0-9]{1,2}(:.+)?)?$/] { throwOther: tr("Value of `{0}` is in old format.", "{0.key}"); group: tr("Public Transport GTFS: value syntax"); assertMatch: "node ref:IFOPT=gen:9188:5599:0:956R"; assertNoMatch: "node ref:IFOPT=de:09162:1179:3:KIF 1"; } /* short ref:IFOPT */ node[public_transport=stop_position][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){4}$/], node[public_transport=stop_position][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){4}$/], node[public_transport=platform ][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){4}$/], node[public_transport=platform ][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){4}$/], way[public_transport=platform ][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){3}$/], way[public_transport=platform ][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){3}$/], relation[public_transport=platform ][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){3}$/], relation[public_transport=platform ][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){3}$/] { throwWarning: tr("Incomplete `{0}` on `{1}`.", "{1.tag}", "{0.tag}"); group: tr("Public Transport GTFS: value syntax"); assertMatch: "node public_transport=platform ref:IFOPT=de:09162:1179:31"; assertNoMatch: "node public_transport=platform ref:IFOPT=de:09162:1179:53:KIF 1"; assertNoMatch: "node public_transport=platform ref:IFOPT=DDDD:09162:1179:3:KIF 1"; assertMatch: "relation public_transport=platform ref:IFOPT=de:09162:1179"; assertNoMatch: "relation public_transport=platform ref:IFOPT=de:09162:1179:30"; } way[public_transport=platform ][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){4}$/], way[public_transport=platform ][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){4}$/], relation[public_transport=platform ][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){4}$/], relation[public_transport=platform ][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){4}$/], relation[public_transport=stop_area][ref:IFOPT ]!.ErrorSyntaxIFOPT[ref:IFOPT !~ /^.+(:.+){3}$/], relation[public_transport=stop_area][gtfs:stop_id]!.ErrorSyntaxIFOPT[gtfs:stop_id !~ /^.+(:.+){3}$/] { throwOther: tr("Possibly, incomplete `{0}` on `{1}`.", "{1.tag}", "{0.tag}"); group: tr("Public Transport GTFS: value syntax"); assertMatch: "way public_transport=platform ref:IFOPT=de:09162:1179:21"; assertNoMatch: "way public_transport=platform ref:IFOPT=de:09162:1179:21:0"; assertNoMatch: "relation public_transport=platform ref:IFOPT=de:09162:1179:30:KIF 1"; assertMatch: "relation public_transport=stop_area ref:IFOPT=de:09162:1179"; assertNoMatch: "relation public_transport=stop_area ref:IFOPT=de:09162:1179:30"; } /* abbreviatons */ /* network */ *[public_transport ][network][!network:short][network =~ /^[A-Z0-9]+$/], relation.PtRouteMaster[network][!network:short][network =~ /^[A-Z0-9]+$/], relation.PtRoute[network ][!network:short][network =~ /^[A-Z0-9]+$/] { throwWarning: tr(" `{0}` looks like an abbreviation", "{1.tag}"); group: tr("Public Transport GTFS: abbreviation as value"); suggestAlternative: "`network:short=*` and a longer form for `network=*`"; fixChangeKey: "network=>network:short"; assertMatch: "relation public_transport=stop_area network=AA0AA"; assertNoMatch: "relation public_transport=stop_area network=AA0AA network:short=A"; assertNoMatch: "relation public_transport=stop_area network=Anna"; } /* operator */ *[public_transport ][operator][!operator:short][operator =~ /^[A-Z0-9]+$/], relation.PtRouteMaster[operator][!operator:short][operator =~ /^[A-Z0-9]+$/], relation.PtRoute[operator ][!operator:short][operator =~ /^[A-Z0-9]+$/] { throwWarning: tr(" `{0}` looks like an abbreviation", "{1.tag}"); group: tr("Public Transport GTFS: abbreviation as value"); suggestAlternative: "`operator:short=*` and a longer form for `operator=*`"; fixChangeKey: "operator=>operator:short"; assertMatch: "relation public_transport=stop_area operator=AA0AA"; assertNoMatch: "relation public_transport=stop_area operator=AA0AA operator:short=A"; assertNoMatch: "relation public_transport=stop_area operator=Anna"; } /* Permanet links to PTNA GTFS */ /* *[JOSM_search("ptna.openstreetmap.de/gtfs")][!JOSM_search("^.*network=([a-zA-Z]+[-]){2,}\\d{4}(-\\d\\d){2}.*$")] { */ *[url =~ /ptna\.openstreetmap\.de\/gtfs/][url !~ /(network=([a-zA-Z]+[-]){2,}|release_date=)\d{4}(-\d\d){2}/], *[website =~ /ptna\.openstreetmap\.de\/gtfs/][website !~ /(network=([a-zA-Z]+[-]){2,}|release_date=)\d{4}(-\d\d){2}/], *[source =~ /ptna\.openstreetmap\.de\/gtfs/][source !~ /(network=([a-zA-Z]+[-]){2,}|release_date=)\d{4}(-\d\d){2}/] { throwWarning: tr("Relative GTFS url in `{0}=*`. Add `{1}` behind the feed or network value.", "{0.key}", "&release_date=*"); assertMatch: "node source=\"https://ptna.openstreetmap.de/gtfs/19\""; assertNoMatch: "node source=\"https://ptna.openstreetmap.de/gtfs/DE/trips.php?network=DE-BW-VAG-2020-07-28&route_id=10-10-I-j20-1\""; assertNoMatch: "node source=\"https://ptna.openstreetmap.de/gtfs/DE/trips.php?feed=DE-BW-VAG&release_date=2020-07-28&route_id=10-10-I-j20-1\""; } /* ----------------------- * conflicting tags */ /* ref:IFOPT, gtfs:stop_id, local_ref */ /* *[public_transport][public_transport =~ /platform|stop_position/][!(tag("local_ref") == get(regexp_match("^(.+:){4}([A-Z]+[ ]?)?([1-9][0-9]{0,2}[A-Z]?)", tag("ref:IFOPT")), 3))][local_ref][ref:IFOPT], */ /* *[public_transport][public_transport =~ /platform|stop_position)][!(tag("local_ref") == get(regexp_match("^(.+:){4}([A-Z]+[ ]?)?([1-9][0-9]{0,2}[A-Z]?)", tag("gtfs:stop_id")), 3))][local_ref][ref:IFOPT] { */ *[local_ref][ref:IFOPT]!.ErrorSyntaxIFOPT[public_transport =~ /^(platform|stop_position)$/][!(tag("local_ref") == get(regexp_match("^(.+:){4}(.+)", tag("ref:IFOPT")), 2))], *[local_ref][ref:IFOPT]!.ErrorSyntaxIFOPT[public_transport =~ /^(platform|stop_position)$/][!(tag("local_ref") == get(regexp_match("^(.+:){4}(.+)", tag("gtfs:stop_id")), 2))] { throwWarning: tr("`{0}` conflicts with `{1}`.", "{0.tag}", "{1.tag}"); group: tr("Public Transport GTFS: conflicting tags"); assertMatch: "node public_transport=platform local_ref=14 ref:IFOPT=de:08315:6504:0:1"; assertMatch: "node public_transport=platform local_ref=56R ref:IFOPT=gen:9188:5599:0:956R"; assertNoMatch: "node public_transport=platform local_ref=14 ref:IFOPT=de:08315:6504:0:14"; assertNoMatch: "node public_transport=platform local_ref=14 ref:IFOPT=de:08315:6504:14"; assertNoMatch: "node public_transport=platform local_ref=956R ref:IFOPT=gen:9188:5599:0:956R"; assertNoMatch: "node public_transport=platform local_ref=1 ref:IFOPT=de:09162:1179:3:KIF 1"; } /* route_id, shape_id, trip_id */ /* FIXME: How to check proper syntax with multiple values */ *[gtfs:trip_id][gtfs:trip_id:sample][!(tag("gtfs:trip_id") == tag("gtfs:trip_id:sample"))] { throwError: tr("`{1}` and `{0}` are not equal.", "{0.tag}", "{1.tag}"); group: tr("Public Transport GTFS: conflicting tags"); assertMatch: "relation gtfs:trip_id:sample=1108.T5.11-4-I-j20-1.10.H gtfs:trip_id=1108.T2.11-4-I-j20-1.10.H"; assertNoMatch: "relation gtfs:trip_id:sample=1108.T2.11-4-I-j20-1.10.H gtfs:trip_id=1108.T2.11-4-I-j20-1.10.H"; } *[gtfs:route_id][gtfs:shape_id ]!.MultipleID!.GtfsIdSyntax[!(tag("gtfs:route_id") == get(regexp_match("^(.+)\\.[0-9]+\\.[HR]$", tag("gtfs:shape_id")), 1))], *[gtfs:route_id][gtfs:trip_id ]!.MultipleID!.GtfsIdSyntax[!(tag("gtfs:route_id") == get(regexp_match("^[1-9][0-9]{0,3}\\.T[023A]\\.(.+)\\.[0-9]+\\.[HR]$", tag("gtfs:trip_id")), 1))], *[gtfs:route_id][gtfs:trip_id:sample]!.MultipleID!.GtfsIdSyntax[!(tag("gtfs:route_id") == get(regexp_match("^[1-9][0-9]{0,3}\\.T[023A]\\.(.+)\\.[0-9]+\\.[HR]$", tag("gtfs:trip_id:sample")), 3))], *[gtfs:shape_id][gtfs:trip_id ]!.MultipleID!.GtfsIdSyntax[!(tag("gtfs:shape_id") == get(regexp_match("^[1-9][0-9]{0,3}\\.T[023A]\\.(.+)$", tag("gtfs:trip_id")), 1))], *[gtfs:shape_id][gtfs:trip_id:sample]!.MultipleID!.GtfsIdSyntax[!(tag("gtfs:shape_id") == get(regexp_match("^[1-9][0-9]{0,3}\\.T[023A]\\.(.+)$", tag("gtfs:trip_id:sample")), 1))] { throwError: tr("`{1}` is not a substring of `{0}`.", "{0.tag}", "{1.tag}"); group: tr("Public Transport GTFS: conflicting tags"); assertMatch: "relation gtfs:route_id=7-342-j1j-1 gtfs:shape_id=7-352-j1j-1.17.H"; assertNoMatch: "relation gtfs:route_id=7-342-j1j-1 gtfs:shape_id=7-342-j1j-1.17.H"; assertMatch: "relation gtfs:route_id=11-4-I-j20-1 gtfs:trip_id=1108.T2.11-5-I-j20-1.10.H"; assertNoMatch: "relation gtfs:route_id=11-4-I-j20-1 gtfs:trip_id=1108.TB2.11-5-I-j20-1.10.H"; assertNoMatch: "relation gtfs:route_id=11-4-I-j20-1 gtfs:trip_id=1108.T2.11-4-I-j20-1.10.H"; } /* release_date in url */ relation.PtRouteMaster[gtfs:release_date][url =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("url")), 1))], relation.PtRouteMaster[gtfs:release_date][website =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("website")), 1))], relation.PtRouteMaster[gtfs:release_date][source =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("source")), 1))], relation.PtRouteMaster[gtfs:release_date][url =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("url")), 1))], relation.PtRouteMaster[gtfs:release_date][website =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("website")), 1))], relation.PtRouteMaster[gtfs:release_date][source =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("source")), 1))], relation.PtRoute[gtfs:release_date][url =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("url")), 1))], relation.PtRoute[gtfs:release_date][website =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("website")), 1))], relation.PtRoute[gtfs:release_date][source =~ /ptna\.openstreetmap\.de\/gtfs.+network=([a-zA-Z]+[-]){2,}\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+network\\=.+-(\\d{4}(-\\d{2}){2}).+", tag("source")), 1))], relation.PtRoute[gtfs:release_date][url =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("url")), 1))], relation.PtRoute[gtfs:release_date][website =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("website")), 1))], relation.PtRoute[gtfs:release_date][source =~ /ptna\.openstreetmap\.de\/gtfs.+release_date=\d{4}(-\d\d){2}/][!(tag("gtfs:release_date") == get(regexp_match(".+release_date\\=(\\d{4}(-\\d{2}){2}).+", tag("source")), 1))] { throwWarning: tr("{0} and {1} in {2} differ", "{1.tag}", "{1.key}", "{2.key}"); group: tr("Public Transport GTFS: conflicting tags"); assertMatch: "relation type=route route=train gtfs:release_date=2020-09-28 source=\"https://ptna.openstreetmap.de/gtfs/DE/trips.php?feed=DE-BW-VAG&release_date=2020-07-28&route_id=10-10-I-j20-1\""; assertNoMatch: "relation type=route route=train gtfs:release_date=2020-07-28 source=\"https://ptna.openstreetmap.de/gtfs/DE/trips.php?network=DE-BW-VAG-2020-07-28&route_id=10-10-I-j20-1\""; assertNoMatch: "relation type=route route=train gtfs:release_date=2020-07-28 source=\"https://ptna.openstreetmap.de/gtfs/DE/trips.php?feed=DE-BW-VAG&release_date=2020-07-28&route_id=10-10-I-j20-1\""; } /* route_id parent */ /* FIXME: How to ckeck with multiple values? */ /* FIXME: How to display the key-value of the parent? */ relation!.MultipleID[count(split(";", parent_tag("gtfs:route_id"))) == 1] { set NoMultiId; } relation[gtfs:route_id ][!(parent_tag("gtfs:route_id") == tag("gtfs:route_id"))].NoMultiId, relation[gtfs:shape_id ][!gtfs:route_id][!(parent_tag("gtfs:route_id") == get(regexp_match("(.+)\\.\\d+\\.[HR]$", tag("gtfs:shape_id")), 1))].NoMultiId, relation[gtfs:trip_id ][!gtfs:route_id][!gtfs:shape_id][!(parent_tag("gtfs:route_id") == get(regexp_match("^\\d+\\.T[023A]\\.(.+)\\.\\d+\\.[HR]$", tag("gtfs:trip_id")), 1))].NoMultiId, relation[gtfs:trip_id:sample][!gtfs:route_id][!gtfs:shape_id][!(parent_tag("gtfs:route_id") == get(regexp_match("^\\d+\\.T[023A]\\.(.+)\\.\\d+\\.[HR]$", tag("gtfs:trip_id:sample")), 1))].NoMultiId { throwWarning: tr("`{0}` conflicts with `{1}` of the `route_master` relation.", "{0.tag}", "gtfs:route_id=*"); /* throwWarning: tr("`{0}` differs to `route_id={1}` of the `route_master` relation.", "{0.tag}", "{1.value}"); */ group: tr("Public Transport GTFS: conflicting tags"); /* assertMatch: "relation gtfs:shape_id=10-20-I-j20-1.1.R parent_tag(\"type\")=route_master parent_tag(\"gtfs:route_id\")=10-19-I-j20-1"; assertNoMatch: "relation gtfs:shape_id=10-19-I-j20-1.1.R parent_tag(\"type\")=route_master parent_tag(\"gtfs:route_id\")=10-19-I-j20-1"; */ } /* ----------------------- * Geometry */ /* stop_position */ /* FIXME: How to use assert with :class and evalexpression? */ node[public_transport=stop_position]:unconnected:in-downloaded-area, node[public_transport=stop_position]:unconnected:new { throwError: tr("`{0}` is not part of any way.", "{0.tag}"); assertMatch: "node public_transport=stop_position "; assertNoMatch: "node public_transport=stop_position :unconnected !:new !:in-downloaded-area"; assertNoMatch: "node public_transport=platform :unconnected :new"; /* assertMatch: "node public_transport=stop_position :unconnected :in-downloaded-area"; * assertMatch: "node public_transport=stop_position :unconnected :new"; * assertNoMatch: "node public_transport=stop_position :unconnected !:new !:in-downloaded-area"; * assertNoMatch: "node public_transport=platform :unconnected :new"; */ }
Rules_PublicTransportGtfs.validator.mapcss, Rules_PublicTransportGtfs.zip
Last modified
10 months ago
Last modified on 2024-02-07T18:38:24+01:00
Note:
See TracWiki
for help on using the wiki.