Commit Graph
14 Commits
Author SHA1 Message Date
marvin 97a065e667 Fix Web Mercator distance distortion in stop clustering
Clustering was computing distances in EPSG:3857 (Web Mercator), which
inflates distances by ~78% at Copenhagen's latitude (55.7°N). This made
the 100m threshold actually merge stops within only 56m, and all reported
distances were inflated.

Now reprojects to EPSG:25832 (UTM 32N) for accurate meter distances,
clusters there, converts centroids back to EPSG:3857 for plotting.
2026-09-13 23:53:51 +02:00
marvin 8b2433ae93 Merge bus stops into rail stations when names match
Bus stops sharing a name with a rail station (metro/s_tog/regional) are
now skipped from bus stop markers — the rail station marker represents
that stop. 32 bus stops across 9 names (Fasanvej, Frederiksberg Allé,
Jyllingevej, Mozarts Plads, Peter Bangs Vej, Ryparken, Rådhuspladsen,
Sluseholmen, Vigerslev Allé) are absorbed.
2026-09-13 23:26:19 +02:00
marvin 8ec2eb8d9c Per-mode stop clustering thresholds
Rail stations get generous thresholds to merge all platform/entrance
duplicates; bus stays conservative:
  metro: 200m    -> 91 stops -> 44 markers (0 duplicates)
  s_tog: 400m   -> 76 stops -> 30 markers (0 duplicates)
  regional: 500m -> 27 stops ->  9 markers (0 duplicates)
  bus: 100m      -> 1610 stops -> 781 markers

stop_cluster_m is now a dict keyed by mode in styling.yaml.
2026-09-13 23:23:14 +02:00
marvin eb3cf7b7d9 Cluster same-name stops within 50m into single markers
Same-name stops within 50m (opposite sides of a road, different
platforms/entrances) are now merged into one marker at the cluster
centroid. Uses scipy hierarchical clustering (single linkage, distance
criterion).

Stop counts after clustering:
  bus:  1610 -> 892 markers
  metro:   91 ->  46 markers
  s_tog:   76 ->  34 markers
  regional: 27 ->  10 markers

Threshold configurable via styling.yaml stations.stop_cluster_m.
2026-09-13 23:15:29 +02:00
marvin e12b13cd8d Fix stop markers rendering below lines
All station and bus stop markers now use zorder = max(line zorders) + 1,
ensuring they always render on top of every transit line layer.
2026-09-13 22:50:41 +02:00
marvin a81b52a456 Add bus stop markers from stops layer
- plot_bus_stops(): loads bus stops from master.gpkg stops layer (1610
  points), draws small white-filled circles (1.5pt) at each location
- stops layer loaded in main() alongside stations
- 'bus' added to stations.styles and marker.size in styling.yaml
- bus stops respect --no-stops and --no-buses / --modes filters
- bus marker zorder sits just above bus lines
2026-09-13 22:42:05 +02:00
marvin 7dc9a15932 Make station labels opt-in; add station markers (symbols without text)
- labels: off by default (was on). Enable via --labels flag or
  styling.yaml labels.show: true. Previously --no-labels opted out.
- stations: new markers layer -- small white-filled circles with dark
  border at each station, sized by mode (metro 3.5pt, s_tog 3.0pt,
  regional 2.5pt). On by default; disable via --no-stops.
- markers respect --modes filter (only mark stations for drawn layers)
- styling.yaml: add stations section (shape/fill/edge/size/linewidth)
- classify_station() helper deduplicates the metro/s_tog/regional logic
- fix attribution to Esri (was CARTO)
2026-09-13 22:37:50 +02:00
marvin 169edee227 Switch basemap to ESRI WorldGrayCanvas (OSM tiles blocked)
OSM's tile server blocks apps without a valid identifying User-Agent
(contextily's default is a random UUID, which OSM rejects with an
'Access blocked' error tile). Switched to ESRI WorldGrayCanvas, a free
no-key light-gray basemap comparable to CartoDB Positron, and set a
proper User-Agent header (jetlag-maps/0.1) on contextily requests per
OSM tile usage policy.
2026-09-13 22:32:16 +02:00
marvin d38c280a4e Copenhagen Phase 3: render.py -> PNG/SVG/PDF map
- render.py: parameterized CLI (--modes, --no-buses, --bus-subset,
  --max-bus-routes, --dpi, --size, --format, --out, --no-basemap, --no-labels)
- basemap: OSM standard tiles via contextily (EPSG:3857)
- two-tone lines: dark casing + colour body per route, z-order
  bus->s_tog->light_rail->regional->metro
- shapeburst fade mask outside City Pass area (distance-transform alpha)
- station labels for metro + S-tog via adjustText
- title + attribution
- styling.yaml: switch basemap to OpenStreetMap.Mapnik (CartoDB now needs API key)
- .gitignore: exclude generated outputs (regenerable via render.py)
- verified: full map, --no-buses, --bus-subset, --modes, SVG all work
2026-09-13 01:27:55 +02:00
marvin 8e32ca2b1c Copenhagen Phase 2: prepare.py merges + clips + colours -> master.gpkg
- prepare.py: merge per-mode OSM lines, resolve colours
  (GTFS route_color -> OSM colour tag -> keyed palette, robust to pd.NA),
  categorise buses (A/C/S/regular via regex), clip lines+stops to area,
  write master.gpkg layers (lines/stops/stations)
- download_osm.py: add stations query (public_transport=station /
  railway=station nodes+areas, out center) -> stations.gpkg with proper
  human-readable names + subway/light_rail mode tags for labelling
- verified: 135 lines (8 metro/16 s-tog/28 regional/83 bus), 90 named
  stations, zero NaN colours
2026-09-13 01:19:34 +02:00
marvin 43c04a94f1 Copenhagen Phase 1: scaffolding + area + OSM download
- pyproject.toml with all deps (geopandas, contextily, partridge, etc.)
- config/: area.json (3 OSM boundary relations), modes.yaml (subway/s_tog/
  light_rail/regional/bus, S-tog scoped to route=light_rail network=Takst),
  styling.yaml (zorder/widths/colours/palette/mask/labels)
- build_area.py: fetch 3 relations via OSM API, stitch rings, dissolve to
  area.gpkg/area.geojson (City Pass approximation)
- download_osm.py: per-mode Overpass (mirror+backoff, out geom + recurse for
  stops), writes {mode}.gpkg (lines+stops) + query text
- download_gtfs.py / gtfs_to_geopackage.py: stubs (blocked on Rejseplanen)
- data/ gitignored (regenerable); output/ tracked
2026-09-13 01:15:25 +02:00
hulthe dc51eaf177 Work on northern italy map 2026-05-10 14:25:40 +02:00
hulthe 6d514cd69e Initialize qgis project for northern italy 2026-05-07 12:45:20 +02:00
hulthe 48c6c03a37 Add regions maps for northern italy 2026-05-07 12:45:12 +02:00