From 80c76f6414ea0c663085f4f0aa000fb4def274e9 Mon Sep 17 00:00:00 2001 From: Guillaume Campagna Date: Mon, 27 Jul 2026 16:25:40 -0400 Subject: [PATCH 1/3] Add carriage_positions.txt and station_directions.txt carriage_positions.txt lets producers recommend which carriage a rider should board to minimize walking at their destination, either when transferring to another platform or when leaving the station. Pathways describe navigation once a rider is off the train, but nothing said where to stand on the platform before boarding. Recommendations are relative to the front of the train at the arrival platform while riders board at the departure platform, so station_directions.txt records which side of each platform the front carriage stops at and the mirroring formula trip planners apply when the two platforms disagree. --- gtfs/spec/en/reference.md | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/gtfs/spec/en/reference.md b/gtfs/spec/en/reference.md index 164a9a80..0d513ee8 100644 --- a/gtfs/spec/en/reference.md +++ b/gtfs/spec/en/reference.md @@ -35,6 +35,8 @@ This document defines the format and structure of the files that comprise a GTFS - [frequencies.txt](#frequenciestxt) - [transfers.txt](#transferstxt) - [pathways.txt](#pathwaystxt) + - [station_directions.txt](#station_directionstxt) + - [carriage_positions.txt](#carriage_positionstxt) - [levels.txt](#levelstxt) - [location_groups.txt](#location_groupstxt) - [location_group_stops.txt](#location_group_stopstxt) @@ -139,6 +141,8 @@ This specification defines the following files: | [frequencies.txt](#frequenciestxt) | Optional | Headway (time between trips) for headway-based service or a compressed representation of fixed-schedule service. | | [transfers.txt](#transferstxt) | Optional | Rules for making connections at transfer points between routes. | | [pathways.txt](#pathwaystxt) | Optional | Pathways linking together locations within stations. | +| [station_directions.txt](#station_directionstxt) | **Conditionally Required** | Platform direction of the front carriage at each stop.

Conditionally Required:
- **Required** when [carriage_positions.txt](#carriage_positionstxt) is provided.
- Optional otherwise. | +| [carriage_positions.txt](#carriage_positionstxt) | Optional | Optimal carriage positioning for transfers and platform exits. | | [levels.txt](#levelstxt) | **Conditionally Required** | Levels within stations.

Conditionally Required:
- **Required** when describing pathways with elevators (`pathway_mode=5`).
- Optional otherwise. | | [location_groups.txt](#location_groupstxt) | Optional | A group of stops that together indicate locations where a rider may request pickup or drop off. | | [location_group_stops.txt](#location_group_stopstxt) | Optional | Rules to assign stops to location groups. | @@ -756,6 +760,47 @@ Pathways are intended to exhaustively define the internal access graph of a stat | `signposted_as` | Text | Optional | Public facing text from physical signage that is visible to riders.

May be used to provide text directions to riders, such as 'follow signs to '. The text in `singposted_as` should appear exactly how it is printed on the signs.

When the physical signage is multilingual, this field may be populated and translated following the example of `stops.stop_name` in the field definition of `feed_info.feed_lang`.| | `reversed_signposted_as` | Text | Optional | Same as `signposted_as`, but when the pathway is used from the `to_stop_id` to the `from_stop_id`.| +### station_directions.txt + +File: **Conditionally Required** + +Primary key (`stop_id`, `route_id`, `direction_id`) + +Defines which side of the platform the front carriage stops at. This information is used alongside [carriage_positions.txt](#carriage_positionstxt) to determine boarding recommendations. + +A record with empty `route_id` and `direction_id` defines the platform side for all trips serving the platform. Records that specify `route_id` and/or `direction_id` take priority over that default for the trips they match, which allows modeling special configurations, e.g. a platform located on a bidirectional single-track section. When several records match a trip, the most specific one applies, in the following order: (`route_id`, `direction_id`), then `route_id`, then `direction_id`, then the default record. + +Recommendations in [carriage_positions.txt](#carriage_positionstxt) are relative to the front of the train at the arrival platform, but passengers board at the departure platform. When the two platforms have opposite `front_car_position` values, trip planners mirror the recommendation with `mirrored_carriage = carriage_count - recommended_carriage + 1`. For example, a `recommended_carriage=2` out of `carriage_count=8` at the arrival platform becomes carriage 7 at the departure platform: the same physical position, counted from the other end. Data consumers should only apply a recommendation when `front_car_position` is known for both the departure and the arrival platform. + +| Field Name | Type | Presence | Description | +| ------ | ------ | ------ | ------ | +| `stop_id` | Foreign ID referencing `stops.stop_id` | **Required** | Identifies the platform.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty).

Values for `stop_id` that identify stations (`location_type=1`), entrances/exits (`location_type=2`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden. | +| `front_car_position` | Enum | **Required** | Platform side where the front carriage stops at, from the perspective of a passenger facing the track. Valid options are:

`0` - Front carriage stops on the left side of the platform.
`1` - Front carriage stops on the right side of the platform. | +| `route_id` | Foreign ID referencing `routes.route_id` | Optional | Specifies per route which side of the platform the front car stops at. Can be defined in combination with `direction_id`. | +| `direction_id` | Enum | Optional | Specifies per trip direction which side of the platform the front car stops at. Can be defined in combination with `route_id`.

Values are the same as [trips.txt](#tripstxt) `direction_id`. | + +### carriage_positions.txt + +File: **Optional** + +Primary key (`stop_id`, `to_stop_id`, `via_pathway_mode`, `carriage_count`) + +Defines optimal carriage positioning for transfers and platform exit access. Enables trip planners to recommend which carriage passengers should board to minimize walking time at their destination. + +A record describes the moment a rider steps off a train: `stop_id` is the platform where the rider alights, and `to_stop_id` is where the rider continues to, either another platform when transferring or an entrance/exit when leaving the station. The recommendation itself is applied earlier, when the rider boards. + +While [pathways.txt](#pathwaystxt) handles navigation within stations, it does not address where to stand on the platform before boarding. This file complements pathways by providing carriage-level boarding recommendations. + +Producers MAY use logical carriage divisions when exact carriage positions are unavailable. For example, `carriage_count=3` with `recommended_carriage=1` indicates "board near the front" regardless of actual vehicle length. + +| Field Name | Type | Presence | Description | +| ------ | ------ | ------ | ------ | +| `stop_id` | Foreign ID referencing `stops.stop_id` | **Required** | Identifies the arrival platform.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty).

Values for `stop_id` that identify stations (`location_type=1`), entrances/exits (`location_type=2`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden. | +| `to_stop_id` | Foreign ID referencing `stops.stop_id` | Optional | Identifies a specific destination, either a transfer platform or a station entrance/exit.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty) or an entrance/exit (`location_type=2`). Values for `stop_id` that identify stations (`location_type=1`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden.

Should belong to the same station (`stops.parent_station`) as `stop_id`, as the recommendation describes walking within a station, but may reference a location in another station when physically connected platforms are modeled separately.

When empty, the recommendation applies as a general default for the station. When provided, it gives a destination-specific recommendation that takes priority over the general default. | +| `recommended_carriage` | Positive integer | **Required** | The recommended carriage to board, numbered from the first carriage in the direction of travel, which has a value of `1`, as in GTFS-realtime `CarriageDetails.carriage_sequence`. May represent a logical position rather than an exact carriage number. Must be less than or equal to `carriage_count`. | +| `carriage_count` | Positive integer | **Required** | Number of carriage positions in this configuration. May represent logical divisions (e.g., 3 for front/middle/back) rather than actual carriage count. | +| `via_pathway_mode` | Enum | Optional | Mode of the pathway the rider takes to reach `to_stop_id`, for which `recommended_carriage` is the closest carriage. Valid options are a subset of [pathways.txt](#pathwaystxt) `pathway_mode`:

`2` - Stairs.
`4` - Escalator.
`5` - Elevator.

Riders with different accessibility needs take different pathways out of the same platform, so a producer may publish one record per mode. When empty, the recommendation applies regardless of how the rider continues. | + ### levels.txt File: **Conditionally Required** From a6616f58b4f5b5a7e16809fa12520edbb552c263 Mon Sep 17 00:00:00 2001 From: Guillaume Campagna Date: Wed, 12 Aug 2026 09:23:36 -0400 Subject: [PATCH 2/3] Rename front_carriage_position and widen carriage_positions.txt keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback on #650. front_car_position becomes front_carriage_position, matching the "carriage" wording used everywhere else in both files. carriage_positions.txt takes the whole row as its primary key. More than one carriage can be equally good for the same destination — a platform with stairs at both ends leading to the same transfer platform is served just as well by the first or the last carriage — and the old key (stop_id, to_stop_id, via_pathway_mode, carriage_count) left room for only one of them. to_stop_id also accepts generic nodes and boarding areas now; only stations stay forbidden. A junction riders continue through, or a specific boarding area on a transfer platform, are both legitimate destinations for a recommendation, and forbidding them forced producers to point at a coarser location instead. The same-station sentence drops its stops.parent_station reference, which does not hold for boarding areas: their parent is a platform, not a station. --- gtfs/spec/en/reference.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gtfs/spec/en/reference.md b/gtfs/spec/en/reference.md index 0d513ee8..d546b408 100644 --- a/gtfs/spec/en/reference.md +++ b/gtfs/spec/en/reference.md @@ -770,20 +770,20 @@ Defines which side of the platform the front carriage stops at. This information A record with empty `route_id` and `direction_id` defines the platform side for all trips serving the platform. Records that specify `route_id` and/or `direction_id` take priority over that default for the trips they match, which allows modeling special configurations, e.g. a platform located on a bidirectional single-track section. When several records match a trip, the most specific one applies, in the following order: (`route_id`, `direction_id`), then `route_id`, then `direction_id`, then the default record. -Recommendations in [carriage_positions.txt](#carriage_positionstxt) are relative to the front of the train at the arrival platform, but passengers board at the departure platform. When the two platforms have opposite `front_car_position` values, trip planners mirror the recommendation with `mirrored_carriage = carriage_count - recommended_carriage + 1`. For example, a `recommended_carriage=2` out of `carriage_count=8` at the arrival platform becomes carriage 7 at the departure platform: the same physical position, counted from the other end. Data consumers should only apply a recommendation when `front_car_position` is known for both the departure and the arrival platform. +Recommendations in [carriage_positions.txt](#carriage_positionstxt) are relative to the front of the train at the arrival platform, but passengers board at the departure platform. When the two platforms have opposite `front_carriage_position` values, trip planners mirror the recommendation with `mirrored_carriage = carriage_count - recommended_carriage + 1`. For example, a `recommended_carriage=2` out of `carriage_count=8` at the arrival platform becomes carriage 7 at the departure platform: the same physical position, counted from the other end. Data consumers should only apply a recommendation when `front_carriage_position` is known for both the departure and the arrival platform. | Field Name | Type | Presence | Description | | ------ | ------ | ------ | ------ | | `stop_id` | Foreign ID referencing `stops.stop_id` | **Required** | Identifies the platform.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty).

Values for `stop_id` that identify stations (`location_type=1`), entrances/exits (`location_type=2`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden. | -| `front_car_position` | Enum | **Required** | Platform side where the front carriage stops at, from the perspective of a passenger facing the track. Valid options are:

`0` - Front carriage stops on the left side of the platform.
`1` - Front carriage stops on the right side of the platform. | -| `route_id` | Foreign ID referencing `routes.route_id` | Optional | Specifies per route which side of the platform the front car stops at. Can be defined in combination with `direction_id`. | -| `direction_id` | Enum | Optional | Specifies per trip direction which side of the platform the front car stops at. Can be defined in combination with `route_id`.

Values are the same as [trips.txt](#tripstxt) `direction_id`. | +| `front_carriage_position` | Enum | **Required** | Platform side where the front carriage stops at, from the perspective of a passenger facing the track. Valid options are:

`0` - Front carriage stops on the left side of the platform.
`1` - Front carriage stops on the right side of the platform. | +| `route_id` | Foreign ID referencing `routes.route_id` | Optional | Specifies per route which side of the platform the front carriage stops at. Can be defined in combination with `direction_id`. | +| `direction_id` | Enum | Optional | Specifies per trip direction which side of the platform the front carriage stops at. Can be defined in combination with `route_id`.

Values are the same as [trips.txt](#tripstxt) `direction_id`. | ### carriage_positions.txt File: **Optional** -Primary key (`stop_id`, `to_stop_id`, `via_pathway_mode`, `carriage_count`) +Primary key (`*`) Defines optimal carriage positioning for transfers and platform exit access. Enables trip planners to recommend which carriage passengers should board to minimize walking time at their destination. @@ -791,12 +791,14 @@ A record describes the moment a rider steps off a train: `stop_id` is the platfo While [pathways.txt](#pathwaystxt) handles navigation within stations, it does not address where to stand on the platform before boarding. This file complements pathways by providing carriage-level boarding recommendations. +More than one carriage may be equally good for the same destination, so several records may share the same `stop_id`, `to_stop_id`, `via_pathway_mode` and `carriage_count` and differ only by `recommended_carriage`. For example, a platform with stairs at both ends leading to the same transfer platform is served equally well by carriage 1 and carriage 8 of an 8-carriage train, published as two records. + Producers MAY use logical carriage divisions when exact carriage positions are unavailable. For example, `carriage_count=3` with `recommended_carriage=1` indicates "board near the front" regardless of actual vehicle length. | Field Name | Type | Presence | Description | | ------ | ------ | ------ | ------ | | `stop_id` | Foreign ID referencing `stops.stop_id` | **Required** | Identifies the arrival platform.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty).

Values for `stop_id` that identify stations (`location_type=1`), entrances/exits (`location_type=2`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden. | -| `to_stop_id` | Foreign ID referencing `stops.stop_id` | Optional | Identifies a specific destination, either a transfer platform or a station entrance/exit.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty) or an entrance/exit (`location_type=2`). Values for `stop_id` that identify stations (`location_type=1`), generic nodes (`location_type=3`) or boarding areas (`location_type=4`) are forbidden.

Should belong to the same station (`stops.parent_station`) as `stop_id`, as the recommendation describes walking within a station, but may reference a location in another station when physically connected platforms are modeled separately.

When empty, the recommendation applies as a general default for the station. When provided, it gives a destination-specific recommendation that takes priority over the general default. | +| `to_stop_id` | Foreign ID referencing `stops.stop_id` | Optional | Identifies a specific destination: a transfer platform or one of its boarding areas, a station entrance/exit, or a generic node such as a junction riders continue through.

Must contain a `stop_id` that identifies a platform (`location_type=0` or empty), an entrance/exit (`location_type=2`), a generic node (`location_type=3`) or a boarding area (`location_type=4`). Values for `stop_id` that identify stations (`location_type=1`) are forbidden.

Should belong to the same station as `stop_id`, as the recommendation describes walking within a station, but may reference a location in another station when physically connected platforms are modeled separately.

When empty, the recommendation applies as a general default for the station. When provided, it gives a destination-specific recommendation that takes priority over the general default. | | `recommended_carriage` | Positive integer | **Required** | The recommended carriage to board, numbered from the first carriage in the direction of travel, which has a value of `1`, as in GTFS-realtime `CarriageDetails.carriage_sequence`. May represent a logical position rather than an exact carriage number. Must be less than or equal to `carriage_count`. | | `carriage_count` | Positive integer | **Required** | Number of carriage positions in this configuration. May represent logical divisions (e.g., 3 for front/middle/back) rather than actual carriage count. | | `via_pathway_mode` | Enum | Optional | Mode of the pathway the rider takes to reach `to_stop_id`, for which `recommended_carriage` is the closest carriage. Valid options are a subset of [pathways.txt](#pathwaystxt) `pathway_mode`:

`2` - Stairs.
`4` - Escalator.
`5` - Elevator.

Riders with different accessibility needs take different pathways out of the same platform, so a producer may publish one record per mode. When empty, the recommendation applies regardless of how the rider continues. | From 966b77e4b419d813da8ca422eda0de8d6259e211 Mon Sep 17 00:00:00 2001 From: Guillaume Campagna Date: Wed, 12 Aug 2026 09:33:29 -0400 Subject: [PATCH 3/3] Drop the carriage mirroring formula from station_directions.txt Review feedback on #650, point 2. recommended_carriage counts carriages from the front of the train, the same frame as GTFS-realtime CarriageDetails.carriage_sequence, so the index does not change along a trip. Mirroring it when the two platforms disagree converts it a second time and sends riders to the opposite end of the train: a recommendation of carriage 1, authored because the exit stairs are at the nose of the train, became carriage 8 whenever the boarding platform's front_carriage_position differed, which is routine for platform pairs that merely differ in track layout. Consumers need front_carriage_position for the boarding platform only, and they need it to render the recommendation at all: a bare carriage index is not something a rider can act on, so the paragraph now shows how the index becomes an end of the platform the rider is standing on. The one case where the front of the train genuinely changes mid-trip is a physical reversal, which front_carriage_position cannot express since it describes platform geometry rather than train orientation. --- gtfs/spec/en/reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtfs/spec/en/reference.md b/gtfs/spec/en/reference.md index d546b408..175e1a3a 100644 --- a/gtfs/spec/en/reference.md +++ b/gtfs/spec/en/reference.md @@ -770,7 +770,7 @@ Defines which side of the platform the front carriage stops at. This information A record with empty `route_id` and `direction_id` defines the platform side for all trips serving the platform. Records that specify `route_id` and/or `direction_id` take priority over that default for the trips they match, which allows modeling special configurations, e.g. a platform located on a bidirectional single-track section. When several records match a trip, the most specific one applies, in the following order: (`route_id`, `direction_id`), then `route_id`, then `direction_id`, then the default record. -Recommendations in [carriage_positions.txt](#carriage_positionstxt) are relative to the front of the train at the arrival platform, but passengers board at the departure platform. When the two platforms have opposite `front_carriage_position` values, trip planners mirror the recommendation with `mirrored_carriage = carriage_count - recommended_carriage + 1`. For example, a `recommended_carriage=2` out of `carriage_count=8` at the arrival platform becomes carriage 7 at the departure platform: the same physical position, counted from the other end. Data consumers should only apply a recommendation when `front_carriage_position` is known for both the departure and the arrival platform. +[carriage_positions.txt](#carriage_positionstxt) `recommended_carriage` counts carriages from the front of the train, so on its own it does not tell a rider where to stand. Consumers pair it with `front_carriage_position` for the platform the rider boards at, from the perspective of a rider facing the track: `recommended_carriage=1` is the carriage stopping at the right end of the platform when `front_carriage_position=1`, and at the left end when `front_carriage_position=0`. Recommendations should only be applied when `front_carriage_position` is known for the boarding platform. | Field Name | Type | Presence | Description | | ------ | ------ | ------ | ------ |