metro entrance merge is non-transitive: a chain of three groups emits a mislabelled, misplaced station #69

Open
opened 2026-08-06 19:00:57 +00:00 by art-bot · 0 comments
Collaborator

Follow-up from the #66 review (per-country transit stops). Latent, not currently firing — #66 merges as-is.

What

The metro_merged CTE in conf/plmap.yaml collapses same-station entrance groups whose centroids sit within 250 m. Each group name independently picks the largest group within 250 m as its winner:

metro_merged AS (
  SELECT a.nazwa,
         (SELECT b.nazwa FROM metro b
          WHERE ST_DWithin(a.c, b.c, 250)
          ORDER BY b.n DESC, b.nazwa
          LIMIT 1) AS win
  FROM metro a
)

That relation is not transitive, and the final GROUP BY m.win assumes it is.

The failure

Three entrance groups A (5 entrances), B (10), C (12), where A–B and B–C are both under 250 m but A–C is over:

  • A → win = B (largest within 250 m of A)
  • B → win = C
  • C → win = C

Grouping by win emits two stations:

  • one named B, positioned at the centroid of A's entrances
  • one named C, holding B's and C's entrances

So a station gets the wrong name and the wrong position — worse than the duplicate the merge exists to prevent.

Current status: does not fire

Measured against the live pl_oikm:

 metro_names | emitted_stations | merged_away | chain_broken
-------------+------------------+-------------+--------------
          39 |               38 |           1 |            0

The only merge is the one the code was written for:

      nazwa       | n |   win
------------------+---+----------
 Dworzec Wileński | 1 | Wileński

Two groups, symmetric, no chain. Warsaw's 38 stations are correct today.

Why fix it anyway

The comment in plmap.yaml presents this as a general recipe:

The entrance-collapse recipe (general — applies to any entrance-type dataset): when a source carries the entrances of a station rather than the station itself, join all same-named points that are near each other into one point at the average of their positions. […] Same recipe would apply to any future named-entrance source.

It is documented as reusable, so the next dataset to use it — a second Polish metro, or any other named-entrance source — inherits a bug that Warsaw's geometry happens not to expose. A three-station chain within 500 m is entirely ordinary for a city-centre interchange.

Suggested shape

Cluster the group centroids instead of doing pairwise nearest-largest, then take the modal name per cluster:

metro_merged AS (
  SELECT nazwa, n,
         ST_ClusterDBSCAN(c, 250, 1) OVER () AS mc
  FROM metro
)
-- then: win = first_value(nazwa) OVER (PARTITION BY mc ORDER BY n DESC, nazwa)

DBSCAN's transitive closure is exactly the semantics the comment describes ("join all same-named points that are near each other"), and it is the same primitive the tram and rail branches already use — one fewer bespoke pattern in the file. Worth re-checking Wileński still merges and the 38-station count holds after the change.

Follow-up from the #66 review (per-country transit stops). Latent, not currently firing — #66 merges as-is. ## What The `metro_merged` CTE in `conf/plmap.yaml` collapses same-station entrance groups whose centroids sit within 250 m. Each group name independently picks the *largest* group within 250 m as its winner: ```sql metro_merged AS ( SELECT a.nazwa, (SELECT b.nazwa FROM metro b WHERE ST_DWithin(a.c, b.c, 250) ORDER BY b.n DESC, b.nazwa LIMIT 1) AS win FROM metro a ) ``` That relation is not transitive, and the final `GROUP BY m.win` assumes it is. ## The failure Three entrance groups A (5 entrances), B (10), C (12), where A–B and B–C are both under 250 m but A–C is over: - A → `win` = B (largest within 250 m of A) - B → `win` = C - C → `win` = C Grouping by `win` emits **two** stations: - one named **B**, positioned at the centroid of **A's** entrances - one named C, holding B's and C's entrances So a station gets the wrong name *and* the wrong position — worse than the duplicate the merge exists to prevent. ## Current status: does not fire Measured against the live `pl_oikm`: ``` metro_names | emitted_stations | merged_away | chain_broken -------------+------------------+-------------+-------------- 39 | 38 | 1 | 0 ``` The only merge is the one the code was written for: ``` nazwa | n | win ------------------+---+---------- Dworzec Wileński | 1 | Wileński ``` Two groups, symmetric, no chain. Warsaw's 38 stations are correct today. ## Why fix it anyway The comment in `plmap.yaml` presents this as a general recipe: > **The entrance-collapse recipe (general — applies to any entrance-type dataset): when a source carries the entrances of a station rather than the station itself, join all same-named points that are near each other into one point at the average of their positions.** […] Same recipe would apply to any future named-entrance source. It is documented as reusable, so the next dataset to use it — a second Polish metro, or any other named-entrance source — inherits a bug that Warsaw's geometry happens not to expose. A three-station chain within 500 m is entirely ordinary for a city-centre interchange. ## Suggested shape Cluster the group centroids instead of doing pairwise nearest-largest, then take the modal name per cluster: ```sql metro_merged AS ( SELECT nazwa, n, ST_ClusterDBSCAN(c, 250, 1) OVER () AS mc FROM metro ) -- then: win = first_value(nazwa) OVER (PARTITION BY mc ORDER BY n DESC, nazwa) ``` DBSCAN's transitive closure is exactly the semantics the comment describes ("join all same-named points that are near each other"), and it is the same primitive the tram and rail branches already use — one fewer bespoke pattern in the file. Worth re-checking Wileński still merges and the 38-station count holds after the change.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#69
No description provided.