Skip to content

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:

  1. Group shape points by shape_id, sorted by shape_pt_sequence.
  2. Map trip_id → shape_id via trips.txt.
  3. 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

  1. Parse the GeoJSON file.
  2. Group features by CodRuta (route code).
  3. For each feature:
  4. Extract coordinates: note GeoJSON uses [lng, lat] order → swap to [lat, lng].
  5. Determine direction from Sentido field:
    • 'v' or 'b' → inbound (direction 1)
    • anything else → outbound (direction 0)
  6. Apply the same 3rd-point simplification as GTFS shapes.
  7. 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.