GTFS Index Building
Parses GTFS CSV files (Cochabamba) and La Paz GeoJSON route data into in-memory lookup indexes at server startup. All GTFS search and routing functions operate against these indexes rather than querying a database.
Where It Is Used
apps/api-server/src/passenger/gtfs.service.ts
The service builds both indexes during onModuleInit() and holds them in memory for the lifetime of the server process.
Cochabamba: GTFS CSV Parsing
Files Parsed
| GTFS file | Purpose |
|---|---|
stops.txt |
Stop IDs, names, lat/lng |
routes.txt |
Route IDs and short names |
trips.txt |
Trip-to-route mapping |
stop_times.txt |
Stop sequences per trip |
shapes.txt |
Route geometry (ordered shape points) |
Index Structure
// stop_id → { name, lat, lng }
stopMap: Map<string, { name: string; lat: number; lng: number }>
// route_id → short_name
routeMap: Map<string, string>
// trip_id → route_id
tripRouteMap: Map<string, string>
// stop_id → Set<route_short_name>
stopRoutesMap: Map<string, Set<string>>
// Final searchable stop list (includes route array for sorting)
GtfsStop[]: { stopId, name, lat, lng, routes: string[] }
CSV Parser
The bundled CSV parser handles:
- Windows (\r\n) and Unix (\n) line endings
- Double-quoted field values
- Missing or empty fields
function parseCsv(content: string): Record<string, string>[] {
const lines = content.replace(/\r/g, '').split('\n').filter(Boolean);
const headers = lines[0].split(',');
return lines.slice(1).map(line => {
const values = line.split(',').map(v => v.replace(/^"|"$/g, ''));
return Object.fromEntries(headers.map((h, i) => [h, values[i] ?? '']));
});
}
Route Shape Building
Shapes are built per route direction by joining shapes.txt with trips.txt:
- Group shape points by
shape_id, sorted byshape_pt_sequence. - Map
trip_id → shape_idviatrips.txt. - Shape simplification: retain every 3rd point plus first and last to reduce payload size:
typescript points.filter((_, i) => i === 0 || i === len - 1 || i % 3 === 0)This reduces shape point count by ~67% while preserving visual fidelity for typical urban route curvature.
La Paz: GeoJSON Parsing
La Paz routes are not in GTFS format. They are stored as a bundled GeoJSON FeatureCollection and parsed separately.
GeoJSON Structure
Each Feature represents one direction of one route:
{
"type": "Feature",
"properties": { "CodRuta": "101", "Sentido": "i" },
"geometry": { "type": "LineString", "coordinates": [[lng, lat], ...] }
}
Parsing Algorithm
- Parse the GeoJSON file.
- Group features by
CodRuta(route code). - For each feature:
- Extract coordinates: note GeoJSON uses
[lng, lat]order → swap to[lat, lng]. - Determine direction from
Sentidofield:'v'or'b'→ inbound (direction 1)- anything else → outbound (direction 0)
- Apply the same 3rd-point simplification as GTFS shapes.
- Sort routes by route number (numeric sort on
CodRuta).
Direction Mapping
const direction = (f.properties.Sentido === 'v' || f.properties.Sentido === 'b') ? 1 : 0;
Memory Footprint
| Dataset | Stops | Routes | Shape points (after simplification) |
|---|---|---|---|
| Cochabamba GTFS | ~2,000 | ~80 | ~15,000 |
| La Paz GeoJSON | N/A | ~150 | ~20,000 |
Both fit comfortably in Node.js heap; no database or cache layer is needed.
Related
- GTFS Stop Search — queries built against these indexes
- GTFS Shape Matching & Polyline Clipping — uses the route shapes built here