Public Transport#
The public transport module in Polaris-Studio supports the full transit data lifecycle needed by supply model builders and scenario analysts. Users can import one or more GTFS feeds, select a service day, map-match route patterns to the roadway network when needed, preserve many GTFS fields that are not using during a POLARIS simulation, edit the resulting public transport service, and export a valid GTFS feed for use by downstream tools.
The transit route system is not intended to replace a specialized transit planning package, but it provides practical editing operations for common model-building and scenario-analysis workflows: agencies, stops, routes, patterns, trips, schedules, route geometries, capacities, and map-matching can be added or adjusted through the Python API before the network is simulated or re-exported.
Capabilities at a glance#
Import standard GTFS Schedule zip files into the Polaris supply database.
Import single-agency feeds and multi-agency feeds
Select a single service date from the feed and import the routes, trips, stop times, stops, fares, transfers, and feed metadata relevant to that date.
Preserve original GTFS identifiers and non-simulation metadata where Polaris supports them, enabling round-trip workflows with limited data loss.
Use GTFS shapes where they exist, create straight-line shapes from stops when necessary, or map-match patterns to the model roadway network.
Store raw GTFS shapes for debugging and comparison with map-matched shapes.
Apply mode-specific capacity defaults and adjust capacities by route type or by individual route.
Add agencies, stops, routes, route patterns, trips, and target departure schedules through
transit.edit.Duplicate, flip, extend, delete, and re-map-match existing route patterns.
Export selected agencies back to a GTFS zip, filtering related routes, patterns, trips, stop times, fares, transfers, and feed information so the result remains internally consistent.
Editing triggers#
A series of triggers have been implemented to greatly reduce the amount of unnecessary data leftover when deleting transit routes/patterns.
These triggers are the following:
When deleting a route:
Delete all patterns for that route
Delete all transit fare rules specific to that route
When deleting a pattern:
Delete all data from Transit_Pattern_Mapping regarding that pattern
Delete all trips from Transit_Trips for that pattern
Delete all links from Transit_Links for that pattern
Delete the sequence of links from Transit_Pattern_Links for that pattern
When deleting a trip:
Delete all scheduled arrivals and departures for that trip from Transit_Trips_Schedule
GTFS importer#
Since the main purpose of this module is to import GTFS feeds, key software operations are done on a class capable of reading a GTFS feed and querying it in memory.
This class is designed to read standard GTFS feeds and might not work with poorly formatted feeds. For more information on GTFS, please refer to GTFS reference documentation.
GTFS data preserved beyond simulation needs#
Polaris’s traffic and transit simulation only needs a subset of the
fields and files the latest GTFS Schedule reference defines. The importer nevertheless
stores many additional, simulation-irrelevant fields verbatim — e.g. agency
contact information (agency_url, agency_phone, agency_email,
agency_lang, agency_fare_url), route presentation
(route_color, route_text_color, route_url, route_sort_order),
stop accessibility metadata (stop_code, wheelchair_boarding,
location_type, platform_code, stop_timezone, tts_stop_name),
and the transfers.txt table.
Different than traditional modelling tools, Polaris strives to preserve as much of the original GTFS data as possible, with the purpose of allowing for round-trip preservation, which we define as the capability of importing a GTFS feed into Polaris, optionally edit it, and re-export it with as little data loss as possible.
This matters when Polaris is one component of a larger multi-tool workflow — for example, an analyst might use Polaris to redesign a set of existing transit routes, then hand the result of that exercise to a downstream tool that incorporates those changes into existing analytics and visualization workflows that expect fields and codes to be those existing in the GTFS feeds for existing services.
The following GTFS Schedule features are intentionally not preserved:
translations.txt, pathways.txt, levels.txt, attributions.txt,
locations.geojson, and the GTFS-Fares-V2 tables (fare_media,
fare_products, fare_leg_rules, fare_transfer_rules, areas,
stop_areas, networks, route_networks, timeframes,
rider_categories), as the need for such has not been identified. Imported
feed_info.txt publisher/contact metadata is also not preserved; GTFS exports
write a new feed_info.txt row identifying POLARIS as the feed generator and
a calendar.txt covering weekday service for the full current year.
Multi-agency feeds#
A single GTFS zip may declare multiple agencies. The importer creates one
row in Transit_Agencies per agency.txt row, each with a unique
Polaris integer agency_id. Routes are routed to their agency via the
routes.agency_id column; when omitted (allowed by the GTFS spec only
when there is exactly one agency), routes are attributed to the single
agency.
When the feed declares more than one agency, the agency argument
passed to transit.new_gtfs(...) is ignored for display names and a
warning is logged; display names come from agency.txt directly. The
description argument, however, is applied to every agency in the
feed, since it identifies the import session rather than a specific
operator.
transfers.txt is per-feed not per-agency. Exported feed_info.txt
is generated by POLARIS using the full-current-year weekday calendar because
POLARIS transit service represents a generic typical weekday rather than a
specific service-date range. Calling transit.new_gtfs(...) once per zip is still the recommended
workflow when each operator publishes its own feed; the multi-agency
support is for cases where a single feed bundles multiple operators
(common in some regional GTFS distributions).
Import process#
Although bus-in-traffic is not used by default on most of Polaris models, map-matching is still used by default for all routes (not only bus) whenever the GTFS feed being imported lacks route shapes. This is done in order to obtain route and transit_link shapes for display, as simple straight lines are less intuitive to those looking at model results.
Since the map-matching process is rather time-consuming, it is possible to turn it off and force the creation of straight-lines in the absence of route shapes with a single line of code
feed.set_allow_map_match(False)
Preventing excess speed#
During the development of multiple Polaris models, we have detected unusually high speeds for a number of route segments. As some of these segment speeds were in excess of 200 mph, a procedure to limit these speeds was put in place.
The process consists of delaying the arrival at a stop (and consequently at all subsequent stops in that trip) any time the segment speed is below a certain threshold.
The use of posted speeds is, however, impossible at that stage of the import process, as the route is yet to be map-matched to the network. For this reason, data analysis was conducted for all networks currently in existence to determine the threshold speeds to be used.
In time, these threshold speeds are mode-specific and vary according to the length of the route segment as to account for express routes.
As the distribution of segment lengths is likely to change significantly across modes and urban areas, the analysis was made for 20 bins with equal number of elements for each model/mode, rounded to the nearest 5m. We understand that this approach may cut off a possibly long tail for the segment length distribution, but we do not believe that this will have substantial effect on the GTFS processing outcome.
Chicago#
Chicago has routes for 3 GTFS modes, as follows:
GTFS mode 1 (Subway/metro)#
There are no clearly absurd route speeds, but some substantially higher speeds for the 99.5% percentile suggest that imposing some lower limits might be necessary.
Looking at the charts we have established that the appropriate speed limits to be imposed on import were approximate the following.
From Length |
to length |
Percentile |
Speed (mph) |
0 |
365 |
0.975 |
~21.9 |
365 |
480 |
0.975 |
~18.5 |
480 |
705 |
0.975 |
~23.3 |
705 |
785 |
0.975 |
~29.0 |
785 |
855 |
0.975 |
~31.3 |
855 |
1,020 |
0.985 |
~32.0 |
1,020 |
1,285 |
0.975 |
~31.9 |
1,285 |
1,600 |
0.980 |
~36.5 |
1,600 |
1,755 |
0.985 |
~39.5 |
1,755 |
MAX |
0.975 |
~55.5 |
There is a somewhat constant low maximum speed for segments shorter than 705m and another plateau of speed between 700 and 1,775m, so we simplify the table as follows:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
700 |
0.970 |
22.9 |
10.2 |
700 |
1,285 |
0.970 |
31.3 |
14.0 |
1,285 |
MAX |
0.985 |
36.4 |
16.3 |
GTFS mode 2 (Suburban rail)#
From Length |
to length |
Percentile |
Speed (mph) |
0 |
815 |
0.975 |
~30.3 |
815 |
935 |
0.975 |
~33.2 |
935 |
1,430 |
0.975 |
~42.5 |
1,430 |
1,955 |
0.975 |
~37.0 |
1,955 |
2,405 |
0.975 |
~37.0 |
2,405 |
2,965 |
0.975 |
~37.0 |
2,965 |
3,885 |
0.970 |
~43.5 |
3,885 |
5,070 |
0.975 |
~53.0 |
5,070 |
8,500 |
0.975 |
~49.0 |
8,500 |
MAX |
0.975 |
~53.5 |
The variability for these modes is a little more extreme than for the other two, but we can establish a difference in general pattern for segments under 935m between 935 and 3,885 and over 3,885m. The resulting simplified limit table is:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
1,000 |
0.975 |
31.6 |
14.1 |
1,000 |
4,000 |
0.975 |
42.5 |
19.0 |
4,000 |
MAX |
0.975 |
52.6 |
23.5 |
GTFS mode 3 (Bus)#
From Length |
to length |
Percentile |
Speed |
0 |
120 |
0.975 |
~23.0 |
120 |
155 |
0.970 |
~23.0 |
155 |
180 |
0.975 |
~23.0 |
180 |
195 |
0.975 |
~23.0 |
195 |
205 |
0.985 |
~23.0 |
205 |
210 |
0.985 |
~23.5 |
210 |
235 |
0.985 |
~26.0 |
235 |
270 |
0.970 |
~27.5 |
270 |
370 |
0.955 |
~31.0 |
370 |
MAX |
0.985 |
~49.0 |
As there is a clear constant pattern for maximum speed for the segments up to 210m, we have simplified the table and retrieved the precise values for the percentiles above.
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
210 |
0.975 |
22.7 |
10.2 |
210 |
270 |
0.975 |
26.9 |
12.0 |
270 |
370 |
0.955 |
30.4 |
13.6 |
370 |
MAX |
0.975 |
49.0 |
21.9 |
Atlanta#
Atlanta also has routes for 3 GTFS modes, as follows:
GTFS mode 0 (Tram/Light rail)#
The maximum speeds for light rail segments for Atlanta are incredibly well behaved, so we will use the maximum available speed (12.8 mph) as the threshold across all distance brackets:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
MAX |
1.000 |
12.9 |
5.8 |
GTFS mode 1 (Subway/metro)#
The maximum speeds for rail/metro segments for Atlanta are also incredibly well behaved, so we will use only a few brackets to threshold across all distance brackets:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
705 |
0.975 |
24.5 |
11.0 |
705 |
3,000 |
0.975 |
46.1 |
20.6 |
3,000 |
MAX |
0.985 |
53.3 |
23.8 |
GTFS mode 3 (Bus)#
From Length |
to length |
Percentile |
Speed |
0 |
130 |
0.985 |
~23.0 |
130 |
160 |
0.985 |
~25.0 |
160 |
190 |
0.975 |
~23.8 |
190 |
220 |
0.975 |
~23.8 |
220 |
250 |
0.975 |
~24.0 |
250 |
285 |
0.980 |
~24.5 |
285 |
325 |
0.970 |
~23.5 |
325 |
385 |
0.975 |
~25.5 |
385 |
500 |
0.985 |
~26.0 |
500 |
MAX |
0.975 |
~31.0 |
There are basically three distance segments that should be interpreted independently, which are consolidated below:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
325 |
0.975 |
23.6 |
10.6 |
325 |
500 |
0.975 |
25.2 |
11.3 |
500 |
MAX |
0.985 |
29.4 |
13.1 |
Campo#
The Austin metro model has routes for 2 GTFS modes, as follows:
GTFS mode 0 (Tram/Light rail)#
The maximum speeds for light rail segments for Austin are pretty well behaved, but also very choppy. For this reason, we have decided to use 4 distance brackets of equal size and loosely correlated with what we see on the charts above and stipulated that the quantile to use is 0.975, as that is often the point where speeds tend to start becoming unreasonable in other cities.
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
2,500 |
0.975 |
15.1 |
6.8 |
2,500 |
5,000 |
0.975 |
16.5 |
7.4 |
5,000 |
10,000 |
0.975 |
32.8 |
14.7 |
10,000 |
MAX |
0.975 |
38.6 |
17.3 |
GTFS mode 3 (Bus)#
The behavior for transit speeds for Bus in Campo follows the example for Chicago, where some clearly absurd speeds are recorded as maximum for different distance brackets.
From Length |
to length |
Percentile |
Speed |
0 |
165 |
0.975 |
~24.2 |
165 |
215 |
0.985 |
~21.0 |
215 |
255 |
0.980 |
~20.5 |
255 |
305 |
0.975 |
~20.5 |
305 |
350 |
0.975 |
~20.5 |
350 |
400 |
0.980 |
~21.5 |
400 |
465 |
0.975 |
~20.5 |
465 |
575 |
0.980 |
~22.0 |
575 |
860 |
0.975 |
~22.5 |
860 |
MAX |
0.985 |
~26.5 |
Different than Chicago, however, the constant maximum speed pattern is observed for most of the distance segment brackets, which suggests three different brackets to be used:
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
600 |
0.975 |
21.4 |
9.6 |
600 |
1,000 |
0.975 |
22.6 |
10.1 |
1,000 |
MAX |
0.985 |
26.6 |
11.9 |
Detroit#
The Detroit model has routes for 2 GTFS modes, as follows:
GTFS mode 0 (Tram/Light rail)#
The maximum speeds for light rail segments for Detroit are pretty well behaved throughout all segment distance brackets, so we have established a unique maximum speed to be applied for all segments and equal to the maximum speed registered in the dataset.
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
MAX |
1.000 |
9.7 |
4.3 |
GTFS mode 3 (Bus)#
Again, Bus presents the most inconsistencies with regards to maximum speeds.
From Length |
to length |
Percentile |
Speed |
0 |
160 |
0.97 |
~21.0 |
160 |
185 |
0.975 |
~26.0 |
185 |
200 |
0.975 |
~28.0 |
200 |
220 |
0.965 |
~25.0 |
220 |
245 |
0.965 |
~27.0 |
245 |
275 |
0.985 |
~31.2 |
275 |
305 |
0.980 |
~34.1 |
305 |
355 |
0.985 |
~39.0 |
355 |
440 |
0.975 |
~45.0 |
440 |
MAX |
0.970 |
~53.0 |
Different than other cities, Detroit speeds increase gradually for growing segment length brackets, and therefore the limits set for it must recognize that, resulting in 6 segment length brackets.
From Length |
to length |
Percentile |
Speed (mph) |
Speed (m/s) |
0 |
160 |
0.975 |
22.4 |
10.0 |
160 |
245 |
0.975 |
27.5 |
12.3 |
245 |
305 |
0.985 |
33.9 |
15.2 |
305 |
355 |
0.985 |
39.5 |
17.7 |
355 |
440 |
0.975 |
44.9 |
20.1 |
440 |
MAX |
0.970 |
53.2 |
23.7 |
Other cities#
For all other cities, the limits to be observed are the following
GTFS mode 0 (Tram/Light rail)#
From Length |
to length |
Source |
Speed (mph) |
Speed (m/s) |
0 |
MAX |
Atlanta |
12.9 |
5.8 |
GTFS mode 1 (Subway/metro)#
From Length |
to length |
Source |
Speed (mph) |
Speed (m/s) |
0 |
705 |
Atlanta |
24.5 |
11.0 |
705 |
3,000 |
Atlanta |
46.1 |
20.6 |
3,000 |
MAX |
Atlanta |
53.3 |
23.8 |
GTFS mode 3 (Bus)#
From Length |
to length |
Source |
Speed (mph) |
Speed (m/s) |
0 |
325 |
Atlanta |
23.6 |
10.6 |
325 |
500 |
Atlanta |
25.2 |
11.3 |
500 |
MAX |
Atlanta |
29.4 |
13.1 |
Map-matching#
Due to the complex nature of the Map-matching procedure, particularly in the face of network models that are simplified and often do not include roadways in which there are active transit services, access to the GTFS importer components might be needed for debugging and/or detailed analysis purposes.
As the importer improves this need should disappear, but the documentation of this component is crucial also to understand its impacts on the final model results.
Although not relevant during regular software use, the other classes employed by the GTFS importer are documented below in order to aid debugging.
Post-Import processes#
Building walk and bike networks is necessary in order to ensure access and connectivity to the transit network, but needs to be explicitly executed.
Editing public transport#
The transit.edit API provides targeted editing operations for public
transport networks that have either been imported from GTFS or created directly
in Polaris. These operations are intended for model-building corrections and
scenario design, such as adding a new route, extending an existing pattern,
changing headways, or preparing a modified service for export.
Typical editing workflow#
A common workflow is:
Import one or more GTFS feeds for the desired service day.
Inspect the resulting agencies, routes, stops, patterns, and schedules in the supply database or GIS interface.
Use
transit.editto add, copy, delete, extend, or re-map-match the public transport elements that define the scenario.Rebuild walk and bike access networks when stop access or service coverage changes.
Export the selected agencies to GTFS when the edited service needs to be reviewed or consumed by other tools.
Supported editing operations#
The editor can add a new agency, add stops, create routes, create route patterns from an ordered stop sequence, add a single trip, and enforce a target list of pattern departures inside a time horizon. Departure times are expressed in seconds from midnight. When adding trips, Polaris can derive travel times from existing trips for the same route or pattern, or use a user-provided fallback speed when no suitable data exists.
Existing service can also be adjusted. Patterns can be duplicated, optionally with their trips and schedules; duplicated patterns can be attached to another route. Patterns can be flipped to create the reverse direction, extended at the start or end with new stops, re-map-matched after geometry changes, or deleted. Routes can also be deleted, with related patterns, trips, links, schedules, and fare rules cleaned up by the database triggers described above.
Capacity editing is supported at two levels. set_capacity_by_route_type
updates capacities for all routes with a given GTFS route type, while
set_capacity_by_route_id updates one route and can include the number of
cars for rail services.
The snippet below illustrates the structure of an editing session rather than a complete model-build script.
from polaris.network.network import Network
network = Network.from_file("/path/to/supply.sqlite", run_consistency=False)
transit = network.get_transit()
agency = transit.edit.add_agency(
agency="Scenario Transit",
feed_data="scenario-build",
service_date="2026-05-28",
description="Scenario service edited in Polaris-Studio",
)
stop_a = transit.edit.add_stop(agency.agency_id, route_type=3, stop_code="SCN_A", latitude=41.88, longitude=-87.63)
stop_b = transit.edit.add_stop(agency.agency_id, route_type=3, stop_code="SCN_B", latitude=41.89, longitude=-87.62)
route_id = transit.edit.add_route(agency.agency_id, mode_id=3, route_name="Scenario Bus")
pattern_id = transit.edit.add_route_pattern(route_id, [stop_a, stop_b])
transit.edit.ensure_pattern_departures(
pattern_id,
departure_times=transit.edit.suggest_departures([{"start": 6 * 3600, "end": 9 * 3600, "headway": 900}]),
default_speed=8.0,
)
Limitations#
Polaris editing focuses on the static transit supply tables used by simulation and GTFS exchange. It does not preserve every optional GTFS Schedule extension, and it does not currently include detailed fare-products from GTFS-Fares-V2, pathway/level editing, or a full-featured visual transit planning interface.
GTFS EXPORTER#
The Polaris GTFS exporter is designed to fulfill the mandatory requirements of the standard, and is continuously tested against the MobilityData validator.
To meet these requirements while generating GTFS feeds with as much of the original data as possible, a few simplifications/assumptions are made to each of the tables.
As a standard, all transit geo-data is converted to WGS84.
Selecting agencies for export#
The exporter requires the caller to declare which agencies to bundle into the output GTFS — there is no implicit “everything in the database” mode.
transit.export_gtfs(folder='/tmp/out', agencies=['CTA', 'PACE'])
Identifiers may be either Polaris integer agency_id values or the
display strings stored in Transit_Agencies.agency. Empty lists and
unknown identifiers raise ValueError.
All rows belonging to non-selected agencies are filtered out of the exported feed: routes, patterns, trips, stop_times, fare rules, and transfers that would reference entities outside the selection are silently dropped (with a debug-level log) so the resulting feed is internally consistent.
Agencies#
The selected agencies (see “Selecting agencies for export” above) are
emitted to agency.txt. When a row’s gtfs_agency_id and
gtfs_agency_name columns were populated at import time, those original
GTFS values are emitted verbatim so the feed round-trips operator-supplied
identifiers; otherwise the writer falls back to the Polaris-generated
integer agency_id and the display agency value.
The timezone used for each agency is taken from the corresponding
Transit_Agencies.timezone column (populated by the importer from
agency.txt); when missing it falls back to a value obtained from an
online service for the geographic center of the model zones. If no data
can be retrieved, the timezone for Chicago is used. This exception is not
used when testing the software, which guarantees the resource should be
robust.
An agency URL is required by the GTFS spec; when Transit_Agencies.agency_url
is empty the writer falls back to the Polaris website as a placeholder.
Routes#
The Process of building route systems for Polaris replaces the route ID with an internal ID that does not correspond to the ID in the source GTFS used to build the Polaris Transit Route System.
For this reason, and to as much of the original data as possible, the original ID information is concatenated with the route description when the data is exported, which gets formatted as a json.
When gtfs_agency_id is preserved on Transit_Agencies, the
routes.agency_id column references that original GTFS string instead
of the Polaris-generated integer, so multi-agency feeds round-trip
correctly.
Shapes#
The cumulative segment distances are kept in meters, despite the use of WGS84 for the point coordinates.