Quickstart#
The one-call pipeline#
transitio.fetch turns an area of interest into everything a routing tool
needs — a cropped OpenStreetMap extract plus validated GTFS feeds:
import transitio
result = transitio.fetch("Helsinki")
The area of interest can be a place name (geocoded via Nominatim), a shapely
geometry, a GeoDataFrame/GeoSeries, or a (minx, miny, maxx, maxy) bounding
box in WGS84. The pipeline:
downloads the smallest OpenStreetMap extract covering the area and crops it to the AOI geometry,
discovers every GTFS feed overlapping the area in the Mobility Database (official feeds first, then by spatial specificity),
downloads each feed, crops it to the AOI, and validates it with the canonical notice codes,
returns the artefact paths together with per-feed merged reports and a
(feed id, reason)record for everything it skipped.
result.osm_pbf # pathlib.Path of the cropped .osm.pbf
result.feeds # list of GTFS zip paths
result.reports # per-feed merged validation reports (dicts)
result.repairs # per-feed repair fix logs (empty without repair=True)
result.skipped # [(feed id, reason), ...]
Useful options:
result = transitio.fetch(
"Helsinki",
when="2026-09-01", # feeds must serve this day
modes=["rail", "tram"], # keep only feeds serving these modes
repair=True, # apply the gtfstidy-contract repair
)
Handing off to cafein or pyrosm#
net = result.to_cafein() # routable cafein.TransportNetwork
osm = result.to_pyrosm() # pyrosm.OSM reader over the extract
to_cafein() forwards keyword arguments to
cafein.TransportNetwork.from_gtfs, so e.g. result.to_cafein(ultra=True)
works as expected.
Using the pieces separately#
Every pipeline stage is a standalone function:
db = transitio.MobilityDatabase() # catalog client
feeds = db.search_feeds(aoi=(24.6, 60.1, 25.2, 60.4))
dataset = db.dataset_for(feeds[0], when="2026-09-01")
path = db.download(dataset) # cached, checksum-verified
hosted = db.validation_report(dataset) # canonical-validator report
pbf = transitio.fetch_pbf((24.6, 60.1, 25.2, 60.4))
validation = transitio.validate_feed(path) # canonical notice codes
transitio.repair_feed(path, "repaired.zip") # logged, conservative
transitio.crop_feed(path, "cropped.zip", aoi=(24.6, 60.1, 25.2, 60.4))
report = transitio.report.build_report(
validation, hosted=hosted
)
print(transitio.report.render_markdown(report))
Reading the validation report#
Notices follow the canonical gtfs-validator codes and severities, so local and hosted results merge into one document. Each notice group records whether it was seen locally, by the hosted validator, or both:
for group in report["notices"]:
print(group["code"], group["severity"], group["source"],
group["totalNotices"])
report["summary"] carries the severity totals, the computed service window
(the actual calendar activity, not the published range), row counts and the
provenance block of the downloaded dataset.