Skip to content

OpenRailwayMap-vector integration#132

Open
hiddewie wants to merge 636 commits intoOpenRailwayMap:masterfrom
hiddewie:master
Open

OpenRailwayMap-vector integration#132
hiddewie wants to merge 636 commits intoOpenRailwayMap:masterfrom
hiddewie:master

Conversation

@hiddewie
Copy link
Contributor

@hiddewie hiddewie commented Jun 7, 2025

Hi!

Over the past years I have been working on a fork of the OpenRailwayMap.

The announcement can be found on the OpenRailwayMap mailing list and on the OpenStreetMap community forum.

The fork has several features, and is actively being developed by me and several more contributors:

  • Vector-based data: the map is interactive with e.g. hover and popup showing data details.
  • Map style improvements: station size, state of lines and statons, signal support for many new countries.
  • Visual UI improvements like: configurable background map, map settings and dark mode.
  • Additional layers: loading gauge and track class.
  • Daily data updates for the entire planet.
  • OpenHistoricalMap integration for historical railway infrastructure.

At the moment the fork is hosted on https://openrailwaymap.app using Fly.io. This is purely practical, it could be hosted anywhere else.

This pull request is a starter to discuss:

  • Can we integrate the fork into the Github OpenRailwayMap organization
  • Can we publish the fork on the domain openrailwaymap.org?

These are open questions, any response is appreciated!

Part of #385

Currently mobile UI elements obstruct the map.

Changes:
- On small screens: hide text on icons
- Make date selection UI togglable. Show year only when in the past.
Hide by default on small screens, show by default on large screens
- For small screens, collapse the style selection by default. When the
button is pressed the element to select a style shows.
hiddewie added 28 commits June 11, 2025 19:19
Part of #431

Hosting the UI and tiles on Fly.io is getting expensive. Instead, let
Cloudflare proxy and cache all assets and tiles. This should improve
both latency, cache hit rates and data transfer.

There will be some graceful migration period were both
openrailwaymap.fly.dev and openrailwaymap.app work, and after that a
redirect.

Additionally: HTTP/3 support :) 

![image](https://github.com/user-attachments/assets/a2e6d5e0-4a38-4559-b41f-e0bdb2424fe0)
Fixes #428 

This only works for station entrances that are part of the
`public_transport=stop_area` relation.

http://localhost:8000/#view=15.68/48.585145/7.737522

![image](https://github.com/user-attachments/assets/97a3d8bd-c0b5-409d-9916-3b7e1272dbec)

http://localhost:8000/#view=17.54/48.584975/7.735184

![image](https://github.com/user-attachments/assets/39badad2-a390-4ca2-8aa1-6bbeab05ef55)
Fixes #349

The Americana style does not work in deployed domains. This project has
no affiliation with OSM US, so we drop the reference from the example in
the background styles. The example is replaced with Stadia Maps which
works cross domain, but requires a (free) API key.
Part of #302

Move power supplies to the electrification layer.


http://localhost:8000/#view=16.69/51.352199/4.629311&style=electrification

![image](https://github.com/user-attachments/assets/32f0297a-4aea-48a7-995c-56063a3adbfa)
From discussion in
#302

This pull request adds support for catenary masts that are a power
transition point.


http://localhost:8000/#view=16.87/52.349219/13.284871&style=electrification

![image](https://github.com/user-attachments/assets/a8b42851-365d-4e9a-a772-1cea9af00ae3)

![image](https://github.com/user-attachments/assets/ee5572aa-3301-45c5-8fc2-5d7e9b71b704)
Fixes #257

Add support for the tags `train`, `subway`, `light_rail`, `monorail`,
`funicular` and `miniature` to specify which type of vehicles stop at
the station.
)

Part of #431 

This pull request redirects all users that use the
`openrailwaymap.fly.dev` domain to `openrailwaymap.app` (proxied through
Cloudflare).


![image](https://github.com/user-attachments/assets/8ac71bf1-81f9-4b11-b630-8f20ce71bba9)
When image or wikimedia commons file tags are present, show the image(s)
in the feature popup.

~Current implementation is impacted by XSS.~

Wikidata lookup does not work: CORS is not enabled for the Wikidata
lookup API. Possible the API needs to be proxied.


![image](https://github.com/user-attachments/assets/af4ed2d2-13b4-43fb-b8ff-e79fd1e43ccf)
Berlin stations changed ordering again
See discussion in
#449

https://openrailwaymap.app/#view=4.16/41.84/-94.04&style=signals

We could also change the coloring if that improves the differences or
grouping between different train protection systems.

cc @DarkTrueNinja1 can you have a look if these changes makes sense?

Before:

![image](https://github.com/user-attachments/assets/0f14ae02-1a8e-4c33-b8b6-e6ba7fe01f5a)

After:

![image](https://github.com/user-attachments/assets/abfcbcff-9b93-46aa-a827-16731278ce72)
Split off from
#454

```
http http://localhost:8000/api/replication_timestamp
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 48
Content-Type: application/json
Date: Sat, 05 Jul 2025 18:33:30 GMT
Server: nginx/1.29.0

{
    "replication_timestamp": "2025-06-28T20:20:41Z"
}
```


![image](https://github.com/user-attachments/assets/aad5f412-4b01-46d5-b6bc-4994da95a190)
Split off from
#454

This makes the proxy image domain agnostic.
~Work in progress.~ Works, and is deployed on
https://openrailwaymap.app. Fly.io deployment is shut down.

Documentation in
https://github.com/hiddewie/OpenRailwayMap-vector/blob/deploy-server/deployment/README.md

TODO:

- [ ] change config to cloud-init configuration
- [x] cron for auto updating, see
60fe463
- [x] cron for docker cleanup, see
167317f
- [x] client certificates for cloudflare -> server authentication, see
#455
- [x] remove hostname dependency in for sources, use relative path
(maplibre/maplibre-gl-js#182 (comment)),
see 1e892ea,
#457
- [x] remove fly.io config, see b58f20b
- [ ] simplify API to not include Postgres DB
- [x] remove `openrailwaymap.fly.dev` redirect from proxy, see
e5f63a9
- [x] remove rate limiting (cloudflare has ddos protection), see
2274f68
- [x] Remove CI for tiles and deploy workflows, see
53ed381
- [x] generate preset version 61319cd
- [x] generate replication timestamp, see
c29fdf5,
#456
- [x] Remove tiles script and config, see
0976222
Followup on #454

Remove Postgres from the API container, and let the API talk directly to
the database.

Verify if the API views and queries can be simplified as well.
Followup on #459

Simplify the database views because the API directly queries the
database in the server deployment.
The last nightly update failed because it tried to trigger the
deployment trigger
(https://github.com/hiddewie/OpenRailwayMap-vector/actions/runs/16092774316/job/45411619735).

Changes:
- [x] Do not trigger a deployment.
- [x] Build the proxy, API and tile images, such that the server does
not have to build them.
- [x] In the deployment, pull images, do not build them.
Fixes #282

Display rules are the same as for `railway=rail`, including the `usage`
and `service` tags.

Ferries display on the standard, loading gauge and operator layer.

https://localhost/#view=10.26/59.8379/9.0296
(https://www.openstreetmap.org/way/122886399):

![image](https://github.com/user-attachments/assets/41be9818-bc2e-4523-b4ca-d2df318564ea)
For local development.
```
docker compose up --build --watch api
```
watches the API directory to make the container automatically rebuild
and restart after making changes.
Fixes #463

(https://localhost/#view=15.47/49.40967/8.660711&style=electrification)
<img width="1586" height="675" alt="image"
src="https://github.com/user-attachments/assets/a937354b-3b2d-4458-b2f6-046ac6b479b9"
/>
hiddewie and others added 30 commits January 13, 2026 20:47
Nightly update still fails sometimes because of disk space:
https://github.com/hiddewie/OpenRailwayMap-vector/actions/runs/20937798180/job/60164501320#step:12:9.

Prune the Docker daemon to retrieve disk space before outputting the
database image.
The OSM data has been updated so the search results are returned in a
slightly different order.
Part of #413 

When a route is rendered, show the stops as well.

The popup also includes the name the reference, local reference and
whether the stop is entry/exit only.

In the future we might change the stop icon based on whether it is
entry/exit only.

Additionally in the future, we might show the stop names with top
priority on the map.
Add funicular, monorail and miniature railway routes to the import.

Part of
#413 (comment).
Updated colors for various transportation entities and added new entries
for US railroads.

---------

Co-authored-by: Hidde Wieringa <hidde@hiddewieringa.nl>
Followup of #774

Part of #413.

During import of the new route types, the SQL was not updated to allow
the new route types. See
https://github.com/hiddewie/OpenRailwayMap-vector/actions/runs/21190217967/job/60954740748.
See https://wetten.overheid.nl/BWBR0017707/2026-01-01

Signal 291c (P bord, permissief), and signal 251 (special danger
location).

See wiki
https://wiki.openstreetmap.org/wiki/OpenRailwayMap/Tagging_in_Netherlands#Main_signals

(http://localhost:8000/#view=17.4/52.225781/6.858487&style=signals):
<img width="651" height="743" alt="image"
src="https://github.com/user-attachments/assets/4874c502-0861-4e7c-825a-3e326fc7b2ab"
/>

(http://localhost:8000/#view=16.44/52.899899/4.80016&style=signals):
<img width="438" height="475" alt="image"
src="https://github.com/user-attachments/assets/a0ac4c27-c81d-4aab-8602-b818eb1c6ff5"
/>
Currently UI tests are failing:
https://github.com/hiddewie/OpenRailwayMap-vector/actions/runs/21296575618/job/61303682120.

Also document how to run them locally.

Chrome fails to create an OpenGL context, so all tests fail there.
Instead, run all the UI tests (both ARM64 and AMD64) on Firefox.
Part of #413

Part of #780

Optimize the fetching of stations. This query became very slow (order of
seconds runtime) since the addition of the list of routes to the
stations.

## Query 1: station labels

Test query:
```sql
select standard_railway_text_stations(8, 137, 98, '{"lang":"it"}')
```
which translates to
```sql
explain analyze SELECT
  ST_AsMVTGeom(way, ST_TileEnvelope(8, 137, 98), extent => 4096, buffer => 64, clip_geom => true) AS way,
  id,
  osm_id,
  osm_type,
  feature,
  state,
  station,
  station_size,
  railway_ref as label,
  name,
  COALESCE(name_tags['name:' || (('{"lang":"it"}'::jsonb)->>'lang')::text], name) as localized_name,
  count,
  uic_ref,
  operator,
  operator_color,
  network,
  position,
  wikidata,
  wikimedia_commons,
  wikimedia_commons_file,
  image,
  mapillary,
  wikipedia,
  note,
  description,
  yard_purpose,
  yard_hump,
  station_routes
FROM railway_text_stations
WHERE way && ST_TileEnvelope(8, 137, 98)
  AND name IS NOT NULL;
```

Query plan before:
```
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|Subquery Scan on railway_text_stations  (cost=64485.10..64522.71 rows=59 width=667) (actual time=1738.058..1738.275 rows=196.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|  Buffers: shared hit=152441                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|  ->  Sort  (cost=64485.10..64485.25 rows=59 width=708) (actual time=1738.035..1738.051 rows=196.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|        Sort Key: (CASE WHEN (gs.state <> 'present'::text) THEN 100 WHEN ((gs.feature = 'station'::text) AND (gs.station = 'light_rail'::text)) THEN 450 WHEN ((gs.feature = 'station'::text) AND (gs.station = 'subway'::text)) THEN 400 WHEN (gs.feature = 'station'::text) THEN 800 WHEN ((gs.feature = 'halt'::text) AND (gs.station = 'light_rail'::text)) THEN 500 WHEN (gs.feature = 'halt'::text) THEN 550 WHEN (gs.feature = 'tram_stop'::text) THEN 300 WHEN (gs.feature = 'service_station'::text) THEN 600 WHEN (gs.feature = 'yard'::text) THEN 700 WHEN (gs.feature = 'junction'::text) THEN 650 WHEN (gs.feature = 'spur_junction'::text) THEN 420 WHEN (gs.feature = 'site'::text) THEN 600 WHEN (gs.feature = 'crossover'::text) THEN 700 ELSE 50 END) DESC NULLS LAST, gs.importance DESC NULLS LAST|
|        Sort Method: quicksort  Memory: 73kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|        Buffers: shared hit=152441                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
|        ->  Merge Left Join  (cost=9.56..64483.36 rows=59 width=708) (actual time=10.438..1737.101 rows=196.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|              Merge Cond: ((gs.operator[1]) = ro.name)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|              Buffers: shared hit=152441                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|              ->  Sort  (cost=9.29..9.43 rows=59 width=449) (actual time=0.794..0.910 rows=196.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
|                    Sort Key: (gs.operator[1])                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|                    Sort Method: quicksort  Memory: 108kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|                    Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
|                    ->  Index Scan using grouped_stations_with_importance_center_index on grouped_stations_with_importance gs  (cost=0.28..7.55 rows=59 width=449) (actual time=0.076..0.313 rows=196.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|                          Index Cond: (center && '0103000020110F00000100000005000000A0922B4E777F3541066FF8FE58515141A0922B4E777F35418A4F24C138EA5141B014DB56F6E237418A4F24C138EA5141B014DB56F6E23741066FF8FE58515141A0922B4E777F3541066FF8FE58515141'::geometry)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|                          Filter: (name IS NOT NULL)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                          Rows Removed by Filter: 6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
|                          Index Searches: 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|                          Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|              ->  Index Scan using railway_operator_name on railway_operator ro  (cost=0.28..12.68 rows=733 width=31) (actual time=0.022..0.310 rows=453.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                    Index Searches: 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|                    Buffers: shared hit=302                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|              SubPlan 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|                ->  Aggregate  (cost=1092.37..1092.38 rows=1 width=32) (actual time=8.842..8.842 rows=1.00 loops=196)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|                      Buffers: shared hit=152096                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|                      ->  Seq Scan on routes r  (cost=0.00..1090.16 rows=126 width=64) (actual time=6.826..8.832 rows=1.08 loops=196)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|                            Filter: (ARRAY[osm_id] <@ gs.route_ids)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
|                            Rows Removed by Filter: 25132                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|                            Buffers: shared hit=152096                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|Planning:                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|  Buffers: shared hit=3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|Planning Time: 0.951 ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|Execution Time: 1738.698 ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
```

Query plan after:
```
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|Subquery Scan on railway_text_stations  (cost=118.14..195.27 rows=121 width=813) (actual time=3.527..3.620 rows=84.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|  Buffers: shared hit=1125                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
|  ->  Sort  (cost=118.14..118.44 rows=121 width=853) (actual time=3.512..3.523 rows=84.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
|        Sort Key: (any_value(CASE WHEN (gs.state <> 'present'::text) THEN 100 WHEN ((gs.feature = 'station'::text) AND (gs.station = 'light_rail'::text)) THEN 450 WHEN ((gs.feature = 'station'::text) AND (gs.station = 'subway'::text)) THEN 400 WHEN (gs.feature = 'station'::text) THEN 800 WHEN ((gs.feature = 'halt'::text) AND (gs.station = 'light_rail'::text)) THEN 500 WHEN (gs.feature = 'halt'::text) THEN 550 WHEN (gs.feature = 'tram_stop'::text) THEN 300 WHEN (gs.feature = 'service_station'::text) THEN 600 WHEN (gs.feature = 'yard'::text) THEN 700 WHEN (gs.feature = 'junction'::text) THEN 650 WHEN (gs.feature = 'spur_junction'::text) THEN 420 WHEN (gs.feature = 'site'::text) THEN 600 WHEN (gs.feature = 'crossover'::text) THEN 700 ELSE 50 END)) DESC NULLS LAST, (any_value(gs.importance)) DESC NULLS LAST|
|        Sort Method: quicksort  Memory: 53kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|        Buffers: shared hit=1125                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|        ->  HashAggregate  (cost=103.35..113.95 rows=121 width=853) (actual time=2.903..3.312 rows=84.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|              Group Key: gs.id, gs.center                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|              Filter: (any_value(gs.name) IS NOT NULL)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|              Batches: 1  Memory Usage: 216kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|              Buffers: shared hit=1125                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|              ->  Nested Loop Left Join  (cost=11.93..84.44 rows=122 width=490) (actual time=1.372..2.209 rows=216.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|                    Buffers: shared hit=1125                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                    ->  Hash Right Join  (cost=11.64..28.01 rows=122 width=434) (actual time=1.345..1.648 rows=216.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|                          Hash Cond: (ro.name = gs.operator[1])                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|                          Buffers: shared hit=477                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|                          ->  Index Scan using railway_operator_name on railway_operator ro  (cost=0.28..12.68 rows=733 width=31) (actual time=0.025..0.266 rows=733.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|                                Index Searches: 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|                                Buffers: shared hit=434                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|                          ->  Hash  (cost=9.84..9.84 rows=122 width=425) (actual time=1.110..1.111 rows=216.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|                                Buckets: 1024  Batches: 1  Memory Usage: 108kB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|                                Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|                                ->  Subquery Scan on gs  (cost=0.28..9.84 rows=122 width=425) (actual time=0.090..0.564 rows=216.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                                      Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                                      ->  ProjectSet  (cost=0.28..8.62 rows=122 width=525) (actual time=0.088..0.513 rows=216.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|                                            Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
|                                            ->  Index Scan using grouped_stations_with_importance_center_index on grouped_stations_with_importance  (cost=0.28..7.55 rows=61 width=449) (actual time=0.072..0.211 rows=202.00 loops=1)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|                                                  Index Cond: (center && '0103000020110F00000100000005000000A0922B4E777F3541066FF8FE58515141A0922B4E777F35418A4F24C138EA5141B014DB56F6E237418A4F24C138EA5141B014DB56F6E23741066FF8FE58515141A0922B4E777F3541066FF8FE58515141'::geometry)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|                                                  Index Searches: 1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
|                                                  Buffers: shared hit=43                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|                    ->  Index Scan using routes_osm_id_idx on routes r  (cost=0.29..0.45 rows=1 width=64) (actual time=0.002..0.002 rows=1.00 loops=216)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
|                          Index Cond: (osm_id = gs.route_id)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|                          Index Searches: 216                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|                          Buffers: shared hit=648                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|Planning Time: 1.451 ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|Execution Time: 4.138 ms                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
```

Execution time goes from 1.8 seconds to 4ms.


## Query 2: station bubbles

Less impact, because only executed at higher zooms (so much smaller
bounding box and amount of data).

Test query:
```sql
select standard_railway_grouped_stations(13, 7179, 3254)
```
which translates to
```sql
explain analyze SELECT
  gs.id,
  nullif(array_to_string(osm_ids, U&'\001E'), '') as osm_id,
  nullif(array_to_string(osm_types, U&'\001E'), '') as osm_type,
  ST_AsMVTGeom(buffered, ST_TileEnvelope(13, 7179, 3254), extent => 4096, buffer => 64, clip_geom => true) AS way,
  feature,
  state,
  station,
  railway_ref as label,
  gs.name,
  uic_ref,
  nullif(array_to_string(operator, U&'\001E'), '') as operator,
  nullif(array_to_string(network, U&'\001E'), '') as network,
  nullif(array_to_string(position, U&'\001E'), '') as position,
  COALESCE(
    ro.color,
    'hsl(' || get_byte(sha256(operator[1]::bytea), 0) || ', 100%, 30%)'
  ) as operator_color,
  (select nullif(array_to_string(array_agg(r.osm_id || U&'\001E' || coalesce(r.color, '') || U&'\001E' || coalesce(r.name, '')), U&'\001D'), '') from routes r where ARRAY[r.osm_id] <@ gs.route_ids) as station_routes,
  nullif(array_to_string(wikidata, U&'\001E'), '') as wikidata,
  nullif(array_to_string(wikimedia_commons, U&'\001E'), '') as wikimedia_commons,
  nullif(array_to_string(wikimedia_commons_file, U&'\001E'), '') as wikimedia_commons_file,
  nullif(array_to_string(image, U&'\001E'), '') as image,
  nullif(array_to_string(mapillary, U&'\001E'), '') as mapillary,
  nullif(array_to_string(wikipedia, U&'\001E'), '') as wikipedia,
  nullif(array_to_string(note, U&'\001E'), '') as note,
  nullif(array_to_string(description, U&'\001E'), '') as description
FROM grouped_stations_with_importance gs
       LEFT JOIN railway_operator ro
                 ON ro.name = operator[1]
WHERE buffered && ST_TileEnvelope(13, 7179, 3254);
```

Query plan before:
```
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|QUERY PLAN                                                                                                                                                                                                                                      |
+-------------------------------…
Fixes #787

For (grouped) stations that have no `route_ids`, also query `null`
values for the routes IDs, to allow a null-join.

Now yards and service stations and and junctions are shown again.

<img width="860" height="627" alt="image"
src="https://github.com/user-attachments/assets/1db4dd54-bb4c-43df-8bde-d4446b34841e"
/>
Hi there, having again read the instructions of railway signalling and
seeing in situ examples of signals, I had to make a (last I hope) rework
of signalling (based similarly on German Hp, Hl and Ks systems). It
highly influences the tagging of well majority of signals, but in near
future I will make adjustments.

Some of tagging was simplified, since some signals must appear in
combined signals (eg. the next semaphore shows "stop")

Minor changes were made to shunting signal (as the sign form didn't
render) and missing form of some signals (that forced writing additional
line out of template).

Thanks in advance

---------

Co-authored-by: Hidde Wieringa <hidde@hiddewieringa.nl>
Before, a single error with the filename of a single file that does not
exist.

After, all errors are concatenated together into a single large error:
```
Error: Failed to resolve promises: Error: Failed to resolve promises: Error: Failed to resolve promises: Error: ENOENT: no such file or directory, open 'symbols/pl/s6-3-main.svg', Error: Failed to resolve promises: Error: Failed to resolve promises: Error: ENOENT: no such file or directory, open 'symbols/pl/s9-13a-4.svg', Error: Failed to resolve promises: Error: Failed to resolve promises: Error: ENOENT: no such file or directory, open 'symbols/pl/s6-3.svg', Error: ENOENT: no such file or directory, open 'symbols/pl/s9-3.svg'
```
Adds preliminary route indicators.

See https://www.railsigns.uk/photos/p_route2.html#pic_priup for some
photos, and https://www.railsigns.uk/sect6page5.html for a description.

I know that there are a few tagged on the approach to Reading West from
Southcote Junction.
Though, be aware that they were added using a slightly different tag
today, before being changed (see description of commit
689f4b5).
1. Changed color of '인천교통공사' to #ff671f, one of another symbolic color
of the operator, in order to visually differentiate it from '공항철도주식회사'.
2. Moved '(주)우진메트로' to #95296f because it was wrongly categorized. (and
those three operators belong to same parent company)
3. Added '우진메트로양산'.
<img width="1096" height="1392" alt="image"
src="https://github.com/user-attachments/assets/affa432e-2aaa-474a-a7fc-38059939123d"
/>
Fixes #794

If the user has not configured the preferred track layer visualization,
the track layer will default to the gauge. The configuration UI also
shows this default. However the map did not incorporate it in all
places, showing every line as having an unknown gauge.
Fixes #739

Simplify the rendering layers for symbols in the standard layer.

The icons are all rendered using the same method.

The text is rendered below, the reference was missing in some cases.

(http://localhost:8000/#view=15.55/50.881/5.668061):
<img width="569" height="498" alt="image"
src="https://github.com/user-attachments/assets/c07faba4-9e48-4e66-9565-8417905986df"
/>

(http://localhost:8000/#view=15.04/51.02047/2.94469):
<img width="1920" height="691" alt="image"
src="https://github.com/user-attachments/assets/7b7f45e2-6861-4b83-9598-5ab52e531ecf"
/>

Additionally, the name is shown in the popup if it exists on a feature.

(http://localhost:8000/#view=16.49/52.072292/4.339282):
<img width="737" height="421" alt="image"
src="https://github.com/user-attachments/assets/1ac90a58-9d54-40f4-9c14-e467783d2c1e"
/>
After extensive research and nerd sniping multiple friends I have come
to the conclusion that, to calculate the maximum power there is a factor
of 1/sqrt(2) missing.

The reasoning is as follows:
The voltage of the overhead wire is usually specified as effective
voltage, but the current is specified as peak amplitude.
The effective power is calculated by multiplying the effective voltage
with the effective current, which can be calculated by dividing peak
amplitude by sqrt(2).
Therefore, to calculate the effective power from the available data the
formula has to be:
```
maximum effective power = effective voltage * peak maximum current / sqrt(2)
```

This is the first time I have worked with this codebase, I hope I did it
correctly.

Cheers

---------

Co-authored-by: Hidde Wieringa <hidde@hiddewieringa.nl>
<figure>
<figcaption>Screenshot of
https://openrailwaymap.app/#view=13.21/41.39035/2.15109&style=operator
on 4th February 2026</figcaption>
<img width="1920" height="918" alt="The image shows stations in the city
of Barcelona, showcasing the operator layer on OpenRailwayMap. Most show
operators are displayed in a green or blue color, with little contrast
between them."
src="https://github.com/user-attachments/assets/874caf6e-a360-4282-bca8-37179716e11e"
/>
</figure>


Currently, TMB is shown in green/blue. This PR would change the operator
color to red (the TMB logo is red, as is the Barcelona Metro logo),
creating a contrast with the neon-green FGC and green Adif.
Fixes #764

Addition of news of changes between October 2025 and January 2026. Wow,
this is a lot 😄.

In addition, the "new news" bubble will only show when the entries are
modified. So when the text, images or links in a news item are modified
the content is considered unchanged.

<img width="1430" height="1114" alt="image"
src="https://github.com/user-attachments/assets/e53e5ecb-6549-4bdc-ba15-0aea9f1d1309"
/>
Part of #413 

Changes:
- Add a new map layer: routes
- Show the amount of routes on a single line

Germany rendering with colors for the amount of routes on a line:
<img width="509" height="670" alt="image"
src="https://github.com/user-attachments/assets/2cb6eaa9-5176-4f42-bcab-0e71d85a2c9f"
/>

UK rendering:
<img width="1431" height="1116" alt="image"
src="https://github.com/user-attachments/assets/8b10a7f2-0a8d-4ee7-8014-40af095bda90"
/>

The Netherlands
(http://localhost:8000/#view=5.89/50.956/6.115&style=route):
<img width="426" height="495" alt="image"
src="https://github.com/user-attachments/assets/aff0d54c-b5ea-4500-87e9-9915ebd2152b"
/>
<img width="1034" height="1060" alt="image"
src="https://github.com/user-attachments/assets/5b1ccbe8-5cef-4d7c-b70a-58dc7495fa1f"
/>
From the documentation and tagging implemented with
#587.

The tagging is currently implemented in three countries: Belgium, Poland
and Spain
(https://wiki.openstreetmap.org/w/index.php?search=%22railway%3Asignal%3Aslope%22&title=Special%3ASearch&go=Go).

BE
(http://localhost:8000/#view=18.59/50.6071712/4.1410327&style=signals):
<img width="1038" height="610" alt="image"
src="https://github.com/user-attachments/assets/36a44ad1-7194-4779-ba36-4dd89f95e5eb"
/>

PL
(http://localhost:8000/#view=15.69/53.754716/20.503926&style=signals):
<img width="843" height="1068" alt="image"
src="https://github.com/user-attachments/assets/e1a791e1-cdcf-4753-8100-c4ba9c639ab0"
/>

ES has icons but no tagged signals.
Fixes #781.

For zooms 13 and higher, sites and junctions will not show station
bubbles, but keep showing their small indicator icon and the name, like
for lower zooms.

(http://localhost:8000/#view=11.48/51.6483/0.3384):
<img width="625" height="378" alt="image"
src="https://github.com/user-attachments/assets/142bf1ab-12ed-4d7d-a9ab-6953ce378734"
/>
<img width="625" height="378" alt="image"
src="https://github.com/user-attachments/assets/f20e6e85-8724-4dee-a99f-32a7060cb8b5"
/>
<img width="1359" height="1129" alt="image"
src="https://github.com/user-attachments/assets/4835f337-b829-4eed-97ee-49bb4b7ef93e"
/>

(http://localhost:8000/#view=14.15/51.13306/0.90364):
<img width="1359" height="1129" alt="image"
src="https://github.com/user-attachments/assets/0362ceb6-d085-4689-9810-62baca91b533"
/>

(http://localhost:8000/#view=15.5/48.204476/16.238709/22.5):
<img width="1765" height="931" alt="image"
src="https://github.com/user-attachments/assets/68640e0f-db6e-485d-9f9c-5b79de8a69f5"
/>

(http://localhost:8000/#view=15.25/48.177383/16.286372/22.5):
<img width="1765" height="931" alt="image"
src="https://github.com/user-attachments/assets/7a607c01-9bcb-4f1f-b981-4d7034d494c2"
/>


Operator layer:
<img width="954" height="594" alt="image"
src="https://github.com/user-attachments/assets/94dc9f53-2bb6-4f7f-85aa-dc3c155bfdd3"
/>


Routes layer:
<img width="954" height="594" alt="image"
src="https://github.com/user-attachments/assets/95bba9ca-88f3-48df-9291-d661bb4aaecf"
/>
…812)

Found during testing of
#808 (review).

The signal feature (determined by the `railway` tag) was not output
correctly, and for some signal icons the `example` was used in the
feature catalog, failing the lookup of the feature name.

Before:
<img width="520" height="688" alt="image"
src="https://github.com/user-attachments/assets/3dfd336b-4288-4b29-a15f-dc4e66122a44"
/>

After:
<img width="381" height="564" alt="image"
src="https://github.com/user-attachments/assets/9292ab63-eefe-43f7-bc90-d587960d8160"
/>
This adds the most common British speed signal.

Documentation:
https://wiki.openstreetmap.org/wiki/OpenRailwayMap/Tagging_in_the_United_Kingdom#Permissible_Speed_Indicator

Example node: https://www.openstreetmap.org/node/13472176904

Rendering including metric signs (black), directional arrows and signs
without speed value:
<img width="1096" height="857" alt="image"
src="https://github.com/user-attachments/assets/46a01f37-afee-48a2-8ebf-eaf4493ae2a1"
/>
Same as #806, now for Switzerland. Part of #587.

(http://localhost:8000/#view=16.62/46.677137/8.592248&style=signals):
<img width="601" height="587" alt="image"
src="https://github.com/user-attachments/assets/def5e757-a67b-4742-8eb6-58dab7cf8d77"
/>
Fixes #664 

The legend has a selection to allow the user to choose between showing
all features in the legend, or filter the features specific to a certain
country.

The countries are determined per layers. Not all layers have
country-specific features.

The legend is also enlarged a bit to allow more text to show.

The chosen option for showing all legend features or those for a
specific country, and the chosen country is stored in the configuration,
so when the page is reloaded the same country selection will be used.

Filtering the features in view is split off into #815. 

(http://localhost:8000/#view=14.4/47.53294/7.65284&style=signals):
<img width="1431" height="1114" alt="image"
src="https://github.com/user-attachments/assets/48dd0eb8-125f-41d0-bd4a-9d861c3e57a4"
/>

Operator country selection:
<img width="1431" height="743" alt="image"
src="https://github.com/user-attachments/assets/180a521e-36e1-4cf7-880e-253ca626e485"
/>

<img width="466" height="545" alt="image"
src="https://github.com/user-attachments/assets/d0578f8b-7c46-4f19-8a82-e7d955f94258"
/>

Infrastructure layer has no country-specific features, selector is
disabled:
<img width="901" height="514" alt="image"
src="https://github.com/user-attachments/assets/b1e04ba3-78c6-477e-a953-6862056d038b"
/>
This allows offline starting of the Docker services. Without an image
pin, the Docker daemon will try to fetch the (possibly updated) image
digest before starting the service.

This can be integrated with Renovate in the future, to automatically
receive pull requests to update the source images.
Fixes #784 

Followup on #810

Allow filtering the legend to show only the features in the current
view.

When the map is zoomed or panned, the legend updates itself.

Note that the speed, gauge, loading gauge and electrification railway
lines are all shown in the legend. Filtering the railway lines in the
legend on a variable value is not possible yet at the moment.

*View diff with whitespace disabled because of the large amount of
indenting changes:
https://github.com/hiddewie/OpenRailwayMap-vector/pull/815/changes?w=1*

<img width="1430" height="743" alt="image"
src="https://github.com/user-attachments/assets/7fbd58d6-ce48-4267-856e-b5019b9fb7b6"
/>

(http://localhost:8000/#view=16.88/47.574013/7.603&style=signals):
<img width="1428" height="745" alt="image"
src="https://github.com/user-attachments/assets/07ba3f93-e304-44d6-a18c-db2c6bf2e8dd"
/>

(http://localhost:8000/#view=16.54/47.475992/7.745872):
<img width="1431" height="1111" alt="image"
src="https://github.com/user-attachments/assets/bebd52da-6f8d-43f2-b405-32a02f1272e2"
/>

(http://localhost:8000/#view=16.42/47.540417/7.963811&style=operator):
<img width="1429" height="1107" alt="image"
src="https://github.com/user-attachments/assets/9064df6a-f319-4cbf-a1d7-45abefffc87d"
/>

Features with variables (speed signals)
(http://localhost:8000/#view=16.17/47.540743/7.961868&style=speed):
<img width="1430" height="1087" alt="image"
src="https://github.com/user-attachments/assets/8dde10d5-8e04-40b7-8de6-db5919f7b318"
/>
Fixes #816

Other features with the same type but of a different class were also
shown. In this case proposed highways instead of proposed railways.

(http://localhost:8000/#view=13.21/49.28597/-123.09964&date=1972):

Before:
<img width="1431" height="1114" alt="image"
src="https://github.com/user-attachments/assets/92ba946f-a138-4fcf-96c5-11d1711f9535"
/>


After:
<img width="1431" height="1114" alt="image"
src="https://github.com/user-attachments/assets/609b93e7-2c99-415f-af69-3e4eb2a07dda"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.