conf/gr: transit + stations — Athens metro/tram lines (City of Athens CC BY) and stops (ΣΤΑ.ΣΥ. GTFS) (issue #191) #194

Merged
art-bot merged 4 commits from issue-191-gr-transit into main 2026-08-20 19:15:08 +00:00
Owner

Closes #191


conf/gr: transit and its stations — Athens metro + tram lines (City of Athens CC BY SHP) and the ΣΤΑ.ΣΥ. GTFS stops (issue #191)

What and why

Greece's transit layer is the Δήμος Αθηναίων (City of Athens) CC BY line
geometry
: the three metro lines (Γραμμή 1/2/3) and the coastal tram, served
as anonymous WFS GeoJSON in EPSG:2100 (geonode:metro, 10 segments;
geonode:tram_lines, 4 segments). Same anonymous-WFS delivery shape as every
ΥΠΕΝ source, so downloads.yaml/staging.yaml follow the established pattern
(source_srid: 2100, reprojected to 3857 on load). The layer unions the two
tables with mode stamped per branch; merge_lines chains the topological
segments; no parallel_merge_pixels — the municipality's SHP is schematic
single-line-per-corridor linework, nothing to drop in parallel.

The R70 second half is this pass: the stations layer's urban half, built
from the operator's own feed — ΣΤΑ.ΣΥ.'s "Δρομολόγια Σταθερών Συγκοινωνιών
(ΜΕΤΡΟ, ΗΣΑΠ, ΤΡΑΜ)" on data.gov.gr (the same feed the previous pass verified
as having stops but no shapes.txt — its stops were always the stations
half's source). The importer's GTFS stops loader (feature: stops,
route_types: [1, 0]) reads it as 252 platform rows with the feed's own
name and a mode stamp (metro beats tram per stop; the feed carries no
parent_station links, so the loader's platform collapse has nothing to work
on). The stations layer query collapses platforms to stations by the PL
pattern — ST_ClusterDBSCAN(200, 1) OVER (PARTITION BY name, mode) and the
cluster centroid — yielding 127 stations (67 metro + 60 tram), unioned
with the GAIAOSE heavy-rail rows. mode distinguishes the halves; metro
takes importance 2 and tram 1 (the shared urban rungs, SE/FI), so the
existing zoom filters draw metro dots from z10 and tram stops from z12.
unique_icon stays NULL (R72: Greece's networks have no sprite symbol).

Why the GTFS and not the City of Athens stasy2024 points (also CC BY, same
GeoNode family): stasy2024 covers only the area inside the municipality —
35 points for a network of 127 stations — so it would leave lines-without-
stations across half the network (R70: incomplete). The operator feed is
complete. Its licence is unset on the data.gov.gr record — permissive by the
portal's open terms, the same class as every ΥΠΕΝ source this config already
fetches and as the GAIAOSE stations this layer joins.

Coverage — lines are the municipality's, stops are the operator's

Line 1 Piraeus–Kifisia and line 2 Anthoupoli–Elliniko run end to end; line 3
stops at ~23.84E (Pallini/Ag. Paraskevi) — the airport branch to ~23.94E
is outside the City of Athens dataset — and the tram SHP covers
Syntagma–Faliron–Glyfada–Voula but not the Piraeus branch. The stops
cover the full operator network, so the two branch gaps now show up honestly
as stops without linework: the four airport-branch metro stations (ΠΑΛΛΗΝΗ,
ΚΑΝΤΖΑ, ΚΟΡΩΠΙ, ΕΛ. ΒΕΝΙΖΕΛΟΣ) and ~15 Piraeus-branch tram stops render as
dots beyond the municipality's lines. Both gaps are the sources'; documented
in docs/country-gr.md.

Two source quirks, both kept and documented: Δ. ΠΛΑΚΕΝΤΙΑΣ and ΣΥΝΔΕΣΗ
ΠΡΟΑΣΤΙΑΚΟΥ are two feed stops at the same position (the M3–suburban
interchange); and the metro names are ALL CAPS while the tram names are
sentence case — the feed's own register split, kept verbatim (no Greek
casing pass exists yet; the GAIAOSE names are verbatim ALL CAPS too).

Verification

  • Acceptance (gr): 52/52 (--acceptance all) — was 41/41; +2 stations
    tests this pass (R70 source-truth + the 85%-within-1 km network test).
    make acceptance tallies: is 38/38, se 44/45 (1 not-run needs
    -acceptance all), fi 49/49, world 27/28 (same not-run), pl 44/46 (the
    pre-existing marginal buildings R120 10s timeout, unrelated — identical
    to the dd21ac2 run).
  • New stations tests: R70 — both modes present, no third mode, every urban
    stop named (0 NULLs), and the per-mode station counts equal the feed's
    deduplicated sets (67 metro + 60 tram; a union that drops or duplicates a
    station moves these numbers and fails); and the network test — 114/127
    urban stops (89.8%) lie within 1 km of a same-mode transit line (threshold
    85%, tolerating the two documented branch gaps).
  • check-queries conf/gr: 9 queries, 8 prepared (stations prepares once the
    new staging table is present — it was present at the end of the run), no
    violations. make test green.
  • Live DB: stations per-zoom 3/444/444/563/563/563 (z9–z14) — rung-3 termini
    at z9, +rung-2 and metro at z10, +rung-1 and tram at z12, matching the
    SE/FI ladder shape. 0 NULL names, 0 NULL importances at z14.
  • Cross-check against the municipality's independent stasy2024 points: 35/35
    resolve to the same-named GTFS station within ~80 m for 30 of them; the
    three far ones (ΣΕΠΟΛΙΑ 554 m, ΤΑΥΡΟΣ 274 m, ΜΕΓΑΡΟ ΜΟΥΣΙΚΗΣ 185 m) are the
    municipality's placement — its ΣΕΠΟΛΙΑ point sits 296 m off the metro
    line, while the GTFS points sit 16–32 m off the line (measured).
  • Decoded z14 tiles: Syntagma 7 stations (4 metro + 3 tram), Kifisia 2 metro,
    Piraeus 5 (2 heavy rail + 2 metro + 1 tram), Elliniko 2 metro, the airport
    tile 3 (1 metro ΕΛ. ΒΕΝΙΖΕΛΟΣ + 2 heavy rail).
  • Real-browser CDP render over central Athens (#14/37.976/23.73): map ready,
    17 station dots (11 metro + 5 tram + 1 heavy rail) and 14 labels
    (10 metro + 2 tram + 2 heavy rail)
    rendered. (The two console errors
    — 400/404 resource loads — reproduce on the untouched SE map; pre-existing.)

Left undone / uncertain

  • Thessaloniki metro: no permissive official line geometry exists (Attiko
    Metro's is CC-NC, R2-rejected); its stations are equally absent. The city's
    entrance/exit points exist and could feed a future Thessaloniki urban half.
  • The metro's name_en column in the feed (and the municipality's
    name_en2) is unused; Greek names are the map language.
  • Line-3 airport branch and tram Piraeus branch linework: absent from the
    City of Athens dataset; the stops there render without lines, documented.
Closes #191 --- # conf/gr: transit and its stations — Athens metro + tram lines (City of Athens CC BY SHP) and the ΣΤΑ.ΣΥ. GTFS stops (issue #191) ## What and why Greece's transit layer is the **Δήμος Αθηναίων (City of Athens) CC BY line geometry**: the three metro lines (Γραμμή 1/2/3) and the coastal tram, served as anonymous WFS GeoJSON in EPSG:2100 (`geonode:metro`, 10 segments; `geonode:tram_lines`, 4 segments). Same anonymous-WFS delivery shape as every ΥΠΕΝ source, so `downloads.yaml`/`staging.yaml` follow the established pattern (`source_srid: 2100`, reprojected to 3857 on load). The layer unions the two tables with `mode` stamped per branch; `merge_lines` chains the topological segments; no `parallel_merge_pixels` — the municipality's SHP is schematic single-line-per-corridor linework, nothing to drop in parallel. **The R70 second half is this pass**: the stations layer's urban half, built from the operator's own feed — ΣΤΑ.ΣΥ.'s "Δρομολόγια Σταθερών Συγκοινωνιών (ΜΕΤΡΟ, ΗΣΑΠ, ΤΡΑΜ)" on data.gov.gr (the same feed the previous pass verified as having stops but no `shapes.txt` — its stops were always the stations half's source). The importer's GTFS stops loader (`feature: stops`, `route_types: [1, 0]`) reads it as 252 platform rows with the feed's own `name` and a `mode` stamp (metro beats tram per stop; the feed carries no parent_station links, so the loader's platform collapse has nothing to work on). The stations layer query collapses platforms to stations by the PL pattern — `ST_ClusterDBSCAN(200, 1) OVER (PARTITION BY name, mode)` and the cluster centroid — yielding **127 stations (67 metro + 60 tram)**, unioned with the GAIAOSE heavy-rail rows. `mode` distinguishes the halves; `metro` takes importance 2 and `tram` 1 (the shared urban rungs, SE/FI), so the existing zoom filters draw metro dots from z10 and tram stops from z12. `unique_icon` stays NULL (R72: Greece's networks have no sprite symbol). Why the GTFS and not the City of Athens `stasy2024` points (also CC BY, same GeoNode family): stasy2024 covers only the area inside the municipality — 35 points for a network of 127 stations — so it would leave lines-without- stations across half the network (R70: incomplete). The operator feed is complete. Its licence is unset on the data.gov.gr record — permissive by the portal's open terms, the same class as every ΥΠΕΝ source this config already fetches and as the GAIAOSE stations this layer joins. ## Coverage — lines are the municipality's, stops are the operator's Line 1 Piraeus–Kifisia and line 2 Anthoupoli–Elliniko run end to end; line 3 stops at ~23.84E (Pallini/Ag. Paraskevi) — the **airport branch** to ~23.94E is outside the City of Athens dataset — and the tram SHP covers Syntagma–Faliron–Glyfada–Voula but not the **Piraeus branch**. The stops cover the full operator network, so the two branch gaps now show up honestly as stops without linework: the four airport-branch metro stations (ΠΑΛΛΗΝΗ, ΚΑΝΤΖΑ, ΚΟΡΩΠΙ, ΕΛ. ΒΕΝΙΖΕΛΟΣ) and ~15 Piraeus-branch tram stops render as dots beyond the municipality's lines. Both gaps are the sources'; documented in docs/country-gr.md. Two source quirks, both kept and documented: Δ. ΠΛΑΚΕΝΤΙΑΣ and ΣΥΝΔΕΣΗ ΠΡΟΑΣΤΙΑΚΟΥ are two feed stops at the same position (the M3–suburban interchange); and the metro names are ALL CAPS while the tram names are sentence case — the feed's own register split, kept verbatim (no Greek casing pass exists yet; the GAIAOSE names are verbatim ALL CAPS too). ## Verification - **Acceptance (gr): 52/52** (`--acceptance all`) — was 41/41; +2 stations tests this pass (R70 source-truth + the 85%-within-1 km network test). `make acceptance` tallies: is 38/38, se 44/45 (1 not-run needs `-acceptance all`), fi 49/49, world 27/28 (same not-run), pl 44/46 (the **pre-existing** marginal buildings R120 10s timeout, unrelated — identical to the dd21ac2 run). - New stations tests: R70 — both modes present, no third mode, every urban stop named (0 NULLs), and the per-mode station counts equal the feed's deduplicated sets (67 metro + 60 tram; a union that drops or duplicates a station moves these numbers and fails); and the network test — 114/127 urban stops (89.8%) lie within 1 km of a same-mode transit line (threshold 85%, tolerating the two documented branch gaps). - `check-queries conf/gr`: 9 queries, 8 prepared (stations prepares once the new staging table is present — it was present at the end of the run), no violations. `make test` green. - Live DB: stations per-zoom 3/444/444/563/563/563 (z9–z14) — rung-3 termini at z9, +rung-2 and metro at z10, +rung-1 and tram at z12, matching the SE/FI ladder shape. 0 NULL names, 0 NULL importances at z14. - Cross-check against the municipality's independent `stasy2024` points: 35/35 resolve to the same-named GTFS station within ~80 m for 30 of them; the three far ones (ΣΕΠΟΛΙΑ 554 m, ΤΑΥΡΟΣ 274 m, ΜΕΓΑΡΟ ΜΟΥΣΙΚΗΣ 185 m) are the municipality's placement — its ΣΕΠΟΛΙΑ point sits 296 m off the metro line, while the GTFS points sit 16–32 m off the line (measured). - Decoded z14 tiles: Syntagma 7 stations (4 metro + 3 tram), Kifisia 2 metro, Piraeus 5 (2 heavy rail + 2 metro + 1 tram), Elliniko 2 metro, the airport tile 3 (1 metro ΕΛ. ΒΕΝΙΖΕΛΟΣ + 2 heavy rail). - Real-browser CDP render over central Athens (#14/37.976/23.73): map ready, **17 station dots (11 metro + 5 tram + 1 heavy rail) and 14 labels (10 metro + 2 tram + 2 heavy rail)** rendered. (The two console errors — 400/404 resource loads — reproduce on the untouched SE map; pre-existing.) ## Left undone / uncertain - **Thessaloniki metro**: no permissive official line geometry exists (Attiko Metro's is CC-NC, R2-rejected); its stations are equally absent. The city's entrance/exit points exist and could feed a future Thessaloniki urban half. - The metro's `name_en` column in the feed (and the municipality's `name_en2`) is unused; Greek names are the map language. - Line-3 airport branch and tram Piraeus branch linework: absent from the City of Athens dataset; the stops there render without lines, documented.
Greece's transit layer is the Δήμος Αθηναίων (City of Athens) CC BY line
geometry: the three metro lines (Γραμμή 1/2/3) and the coastal tram, served
as anonymous WFS GeoJSON in EPSG:2100 (geonode:metro, 10 segments;
geonode:tram_lines, 4 segments) — the same anonymous-WFS delivery shape as
every ΥΠΕΝ source, so downloads.yaml/staging.yaml follow the established
pattern (source_srid 2100, reprojected to 3857 on load). The layer unions the
two with `mode` stamped per branch and merge_lines chains the topological
segments. 89 km of metro and 30 km of tram at z14; z9–z14 partitions all hold.

The catalogue said "Athens metro/tram and Thessaloniki metro from the official
GTFS + CC BY tram-line SHP". The GTFS half fails twice over, verified against
the data.gov.gr resources:

- OASA's Athens GTFS (ΣΤΑ.ΣΥ.) has stops but no shapes.txt — the delivered
  feed carries no route geometry, so it contributes nothing to a line layer
  (its stops are the stations layer's urban half, R70, still to be built).
- The ΟΑΣΘ and ΟΣΕΘ Thessaloniki feeds are bus-only (route_type 3), the City
  of Thessaloniki publishes only metro entrance/exit points, and the only
  Thessaloniki metro LINE geometry — Attiko Metro's "GIS Thessaloniki Metro
  Base" — is CC-NC, rejected under R2. So the Thessaloniki metro is absent
  from the transit layer not by scope but by source; the docs' verdict row
  is corrected to match (docs/country-gr.md, *Transit*).

Coverage is the municipality's, gaps included and documented: line 1
Piraeus–Kifisia and line 2 Anthoupoli–Elliniko run end to end; line 3 stops
at ~23.84E (the airport branch is outside the City of Athens dataset) and the
tram SHP stops before the Piraeus branch.

New acceptance tests: the R119 no-empty-zoom-table check (all six z9–z14 gr
partitions) and a source-truth test asserting both modes, no third mode, and
the metro branch is exactly the source's three lines. The two new staging
tables join the R105 list (the #123 trap).

Verified: gr acceptance 41/41 (was 39; +2 transit tests), is 38/38, se 44/45
(1 not-run needs -acceptance all), fi 49/49, world 27/28 (same not-run),
pl 44/46 (the pre-existing marginal buildings R120 timeout, unrelated — same
as the dd21ac2 run). check-queries clean (8/8), make test green. Live DB:
14 rows at z14 (10 metro + 4 tram), 0 NULL modes, per-zoom counts 12/12/12/
12/12/14, decoded z14 tiles carry both modes (5–6 features per Athens tile),
and a real-browser CDP render over Athens shows 25 transit features (22 metro
+ 3 tram).
art changed title from # conf/gr: the transit layer — Athens metro + tram from the City of Athens CC BY SHP (issue #191) to conf/gr: the transit layer — Athens metro + tram from the City of Athens CC BY SHP (issue #191) 2026-08-20 15:22:57 +00:00
art force-pushed issue-191-gr-transit from 3d1be50fb6 to 13c74a7e81 2026-08-20 16:31:42 +00:00 Compare
art changed title from conf/gr: the transit layer — Athens metro + tram from the City of Athens CC BY SHP (issue #191) to conf/gr: transit + stations — Athens metro/tram lines (City of Athens CC BY) and stops (ΣΤΑ.ΣΥ. GTFS) (issue #191) 2026-08-20 16:31:45 +00:00
Review of !194. Four of the five are cases where the prose asserted
something the data does not say; the fifth is a test that proved the
source rather than the layer.

THE ΝΕΟΣ ΚΟΣΜΟΣ EXAMPLE WAS WRONG. The stations comment justified
PARTITION BY (name, mode) with "same-name stops of different modes stay
separate (Νέος Κόσμος has a metro station ~110 m from its tram stop)".
The feed has no name shared by two modes at all — 0 collisions, even
case-insensitively — and that pair is not one: the metro station is
`Ν.ΚΟΣΜΟΣ`, the tram platforms are `Νέος Κόσμος`, 81 m and 123 m away on
the ground. `name` alone already separates them. `mode` stays in the
partition as a guard against a feed that adopts one spelling for both,
which is now what the comment says.

EPSG:3857 UNITS ARE NOT METRES, and at Athens the difference is 27%
(1/cos(38°) = 1.27). The DBSCAN eps 200 is ~158 m on the ground, ΕΙΡΗΝΗ's
platform span of 153 units is ~121 m, and the network test's "1 km" is
~790 m — every one of them internally consistent, since both sides of
each comparison are projected, but all of them mislabelled. Named as
units throughout, the way conf/fi/11-stations.yaml already does it. The
network test also records the geodesic measurement (116/127 = 91.3%
within a true kilometre, against 114/127 within 1 000 units), so the
threshold can be read either way.

THE TRANSIT TEST PROVED THE SOURCE, NOT THE LAYER. It asserted
count(DISTINCT metro_line) = 3 over the staging table and only "> 0" on
the layer side, so a union that dropped nine of the ten metro segments
still passed. Row counts cannot fix that — merge_lines chains segments,
so the count is a property of how the source happens to be split. Total
length can: chaining preserves it and z14 is unsimplified, so the layer
must carry the source's length exactly. Measured 88 607 / 30 061
EPSG:3857 units on both sides, asserted to 0.5 units.

Also: the urban branch now trims its names the way the GAIAOSE branch
does (NULLIF(BTRIM(...)), with the R70 test's source side trimmed to
match, so the equality stays an assertion about the union and not about
whitespace). No offenders in today's feed — the reworked query is
row-for-row identical to what is in the database, name, mode and geometry
(symmetric difference 0 in both directions) — so this is defence, not a
fix.

DOCS. The transit row said "89 + 30 km", which are Mercator lengths read
off a map_* table: 70 and 24 km on the ground. Corrected, with the scale
factor stated once where it applies to everything this file quotes. The
ΣΤΑ.ΣΥ. GTFS is a third unset-licence source, so the R2 gap item says
three, not two. And the `places` row still said GO though #190 landed the
layer — the table was never updated when it merged; the tally now reads
seven plus stations.

The two City of Athens URLs are plain HTTP because the municipality's
GeoServer speaks no TLS at all (port 443 answers `wrong version number`),
which is now written down so nobody "fixes" them into a broken fetch.

Verified on the live database: acceptance conf/gr --acceptance all 52/52
(0 failed, 0 skipped, 0 not run), check-queries 9/9 prepared,
config-check OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGAZTzU6FGJSQKj437rr3L
conf/gr was in neither test map from the day Greece was added.
countryConfigPaths() drives TestRealConfigsValidate and
TestSharedFloorsAgreeAcrossCountries, so `go test ./...` never validated
Greece's config, and — the half that matters — never checked that
Greece's shared-layer floors agree with the other four countries'. There
is one zxy_map_<layer> reading one set of parent tables, so a country
that disagrees about where a shared layer stops decides it for everyone;
Greece has been adding partitions to shared tables for three issues
without that check running. It passes today, which is the point: it is
now asserted rather than assumed.

configdir_test.go's countryLayers is the other half and is deliberately
NOT in this commit. That map pins the layer set AND its order, so it can
only be written once the layer set stops moving — it would go red the
moment the sibling Greece PR merges. It belongs to whichever of the two
lands second, which then adds conf/gr with the full nine-layer list.

This commit is identical on both open Greece branches so the two merge
without a conflict here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGAZTzU6FGJSQKj437rr3L
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
art/ismap!194
No description provided.