Overview
OJPP (Odprti javni potniški promet - Open Public Passenger Transport) is a community-driven effort to make Slovenian public transit data actually usable. The country’s national transit system (IJPP) provides data in fragmented, operator-specific formats with no unified real-time feed - SOAP/XML interfaces, proprietary vehicle IDs, and SIRI-VM instead of the GTFS-RT that modern transit apps expect.
OJPP bridges that gap. It aggregates schedules and real-time positions from every transit operator in Slovenia, normalizes them into open standards (GTFS, GTFS-RT, SIRI, GBFS), and serves them through a unified API that powers multiple consumer apps including brezavta.si, RTPI departure displays, and a native iOS app.
The ecosystem spans eight repositories, five languages, and covers the full stack from SIRI-VM XML parsing to a cross-platform mobile app with live vehicle tracking on a map.
The data problem
Slovenia has multiple transit operators that don’t talk to each other:
- LPP - Ljubljana city buses (their own API, their own stop IDs)
- Arriva - intercity bus services
- Marprom - Maribor city transit (yet another ID scheme)
- SŽ - Slovenian Railways (proprietary delay format, no GTFS-RT)
- Smaller carriers - Piran coastal shuttles, regional operators
The national IJPP platform aggregates some of this, but its API is designed for internal use. Real-time data arrives as SIRI-VM XML via an OAuth2-authenticated SOAP endpoint on the National Access Point (NAP). Schedules use IJPP-specific IDs that don’t map cleanly to anything else. The same physical bus stop might have completely different IDs in IJPP, LPP, and Marprom.
There’s also a growing micromobility ecosystem - Avant2Go, SHARE’Ngo, Smart City Bikes, GreenGo, Micikel, Kvik, plus international operators like JCDecaux, Nextbike, and Bolt - none of which integrate with the transit system at all.
OJPP exists to be the normalization and translation layer that the national system doesn’t provide.
Data backend (ojpp-django)
The core of the system is a Django application backed by PostgreSQL with PostGIS for spatial queries. It maintains a unified data model of the entire Slovenian transit network.
The data model
The model is designed around the stop identity problem. A Stop represents a physical location and carries ID fields for every operator system it appears in -ijpp_id, lpp_id, marprom_id. Below that, StopLocation represents a specific platform or pole at that stop (a single bus stop can have multiple platforms served by different routes), again with per-operator IDs and a PostGIS point geometry.
From there the model follows the GTFS pattern: Operator → RouteGroup → Route → Trip → StopTime. Each entity carries its operator-specific IDs alongside the unified OJPP primary key. Routes compute their first and last stop via Django computed fields. Trips carry optional LineString geometries for map rendering.
Scheduling is modeled through Schedule → TimetablePeriod + TimetableException, supporting per-day-of-week patterns with date range overrides and holiday exceptions - the kind of complex scheduling that transit operators actually use (e.g. “runs Mon-Fri except public holidays, but also runs on Dec 24”).
Vehicle tracking and trip instancing
The backend does more than store static schedules. It tracks live vehicles.
Vehicle objects carry plate numbers, operator IDs, vehicle models (with low-floor accessibility flags), photos (via a gallery field), and VIN numbers. VehicleLocation stores GPS points with bearing and timestamp, linked to the trip the vehicle is currently serving.
A trip_instancing system matches live GPS positions to scheduled trips. When a vehicle location comes in, it finds the nearest scheduled stop time that the vehicle hasn’t passed yet (using PostGIS distance queries), creates a TripInstance linking the vehicle to the trip, and records StopTimeInstance entries with actual arrival times. This lets the system compute real delays by comparing actual arrival times against scheduled times. There’s a staleness threshold - if a vehicle hasn’t been seen near a stop for 6 hours, it starts a new trip instance.
Beacon tracking
An ojpp_beacons app allows crowdsourced identification of vehicles via WiFi and BLE beacons. Users submit beacon observations (a MAC address or BLE identifier seen while riding a specific bus), and the system builds a mapping from beacon identifiers to vehicles. This enables automatic vehicle identification for users who have the app open - they can see which specific bus they’re on without relying on GPS.
Operator adapters
Data comes in through operator-specific Django apps, each running as a scheduled job via APScheduler:
- adapter_ijpp - ingests from the national IJPP system, the primary source for intercity routes and schedules
- adapter_lpp - Ljubljana city bus schedules and real-time data
- adapter_marprom - Maribor city transit, with its own internal API client
- adapter_arriva - Arriva intercity services
- adapter_nap - the National Access Point. This is the most complex adapter: it authenticates via OAuth2, posts SIRI-VM XML requests, parses the response using xsdata-generated Python bindings from the SIRI XSD schema, and maps the proprietary IJPP vehicle references back to the unified model
- adapter_piran - coastal shuttle services
Each adapter reconciles incoming data against the shared model, matching stops across ID systems and creating or updating routes, trips, and stop times.
GTFS export
The exporter_gtfs module builds a complete GTFS feed from the unified model using transitfeed, including stops, routes, trips, stop times, calendars, and calendar dates. This is what OpenTripPlanner ingests for route planning.
Admin interface
The Django admin is heavily customized with Jazzmin, Leaflet map widgets for spatial data, a gallery field for vehicle photos, AJAX datatables for large querysets, CKEditor for rich text, and django-import-export for bulk data operations. It’s the primary tool for managing the stop database, verifying operator data imports, and debugging schedule issues.
Consumer API (brezavta-api)
The consumer-facing API is a separate FastAPI service (Python, uvicorn) that sits in front of an OpenTripPlanner instance. It’s the API that the mobile app and RTPI displays talk to.
Endpoints
- Trip planning (
/plan/{modes}/{from}/{to}) - multi-modal routing (bus, train, walk, bike) with departure/arrival time, wheelchair accessibility, walk reluctance tuning, and pagination. Queries are proxied to OTP via GraphQL, with typed bindings generated by ariadne-codegen. - Stops (
/stops,/stops/{id},/stops/{id}/arrivals,/stops/{id}/schedule/{date}) - stop listing, details with grouped arrivals, real-time arrival boards filtered by date and current time, and full daily schedules. - Vehicles (
/vehicles/locations) - live GPS positions of all tracked vehicles across all operators, refreshed every 5 seconds. - Geocoding (
/geocoding/search,/geocoding/reverse_geocode) - address search via Pelias autocomplete and reverse geocoding for “where am I?” functionality, cached for 12 hours. - Micromobility (
/micromobility,/micromobility/{id}) - bike and scooter station data aggregated from the GBFS adapter, with per-station availability details. - Parking (
/parking) - parking facility availability across Slovenian cities.
An APScheduler process runs in the background to pre-fetch and cache vehicle positions and trip updates from the real-time adapters, so the API can respond quickly without blocking on upstream calls.
Real-time adapters
ijpp-rt
A Node.js/Express service that converts two proprietary real-time formats into standard GTFS-RT Protobuf feeds:
- IJPP SIRI-VM → GTFS-RT VehiclePositions - takes the XML vehicle monitoring data from the national system and converts it into standard vehicle position messages with trip IDs, route IDs, and GPS coordinates.
- SŽ proprietary format → GTFS-RT TripUpdates - parses Slovenian Railways’ custom delay data (which uses fast-xml-parser since it’s too inconsistent for strict XML parsing) and produces standard trip update messages with stop-level arrival/departure delays.
- SŽ disruptions → GTFS-RT ServiceAlerts - converts railway disruption notices into standard service alert messages.
Each endpoint serves both Protobuf (for OTP and other GTFS-RT consumers) and JSON (for debugging). This single adapter is what makes real-time bus tracking and train delay information available to any standard transit app.
gbfs-adapter
A Python service that aggregates micromobility data from across Slovenia and internationally into the GBFS (General Bikeshare Feed Specification) standard. It supports ten providers:
- Slovenian: Avant2Go, SHARE’Ngo, Smart City Bikes, GreenGo, Micikel, Kvik
- International: JCDecaux, Nextbike, Bolt (requires credentials, proxied to official endpoints)
Each provider has its own adapter class extending GBFSProvider. The adapter runs GBFS validation tests against the official validator and serves standard GBFS discovery, station information, station status, and free bike status feeds. This means the trip planner can seamlessly include bike and scooter options alongside buses and trains - the user doesn’t need to know which micromobility operator serves their area.
Consumer apps
slovenijaRT (brezavta.si)
The main user-facing app is a React SPA (Vite, TypeScript) that ships as both a PWA and native mobile apps via Capacitor (iOS and Android). It’s the interface most people interact with.
Map - the core experience is a MapLibre GL JS map showing real-time vehicle positions, transit stops, micromobility stations, and parking facilities as separate toggleable layers. Vehicle icons include bearing indicators and are color-coded by operator. Stop icons are generated at build time as spritesheets from SVG templates via a custom Node.js generator, with runtime coloring based on operator branding. The map also shows trip geometries when viewing a specific trip.
Trip planning - multi-modal routing powered by OpenTripPlanner through the brezavta-api. Users pick origin/destination (with geocoding search, GPS location, or map tap), choose transport modes, and get itineraries with step-by-step legs showing walking, bus, train, and bike segments. Each leg shows scheduled and real-time times, with delay indicators. Results include line-on-map rendering of the planned route. The planner supports wheelchair-accessible routing and walk reluctance preferences.
Stop departures - tap any stop to see a real-time arrival board with the next departures, including delay information from GTFS-RT. Arrivals are grouped by route with operator branding (colors, logos). Users can also view the full daily schedule for any stop on any date. There’s an iCal export for adding specific departures to your calendar.
Trip details - full stop-by-stop view of any trip with scheduled and real-time arrival/departure times, the vehicle serving the trip, and the route geometry on the map.
Micromobility - station details with available bikes/scooters, integrated into both the map layers and the trip planner as a transport mode.
Technical details: API bindings are auto-generated from the brezavta-api OpenAPI spec using openapi-react-query-codegen, providing typed TanStack Query hooks for every endpoint. Routing uses TanStack Router in file-based mode. The app is fully internationalized (Slovenian and English) via i18next with proper plural forms. Sentry is integrated for error tracking. The app uses MUI components and Framer Motion for transitions.
brezavta-rtpi
A lightweight Node.js/Express service that powers RTPI (Real-Time Passenger Information) displays - the kind of screens you see at bus stops showing upcoming departures. It serves both a JSON API and static HTML pages with arrival boards, currently live at voznired.si. It pre-loads stop data and trip via information at startup, then fetches real-time arrivals from the brezavta-api on demand, formatting them for low-bandwidth display screens with minimal JavaScript.
busomatik-ios (Travana)
A native iOS app (Swift) for Ljubljana bus tracking, published on the App Store. Originally built as the iOS counterpart to the Android Travana app, it provides live bus positions and stop departure information for the LPP network.
Tech stack
| Layer | Technologies |
|---|---|
| Data backend | Django 4.2, PostgreSQL + PostGIS, APScheduler, DRF, xsdata, Leaflet |
| Consumer API | FastAPI, uvicorn, OpenTripPlanner, ariadne-codegen (GraphQL) |
| Real-time adapters | Node.js, Express, GTFS-RT Protobuf, SIRI-VM, fast-xml-parser |
| Micromobility | Python, GBFS standard, per-provider adapters |
| Web/mobile app | React, Vite, TypeScript, MapLibre GL JS, MUI, Capacitor, TanStack Router/Query |
| RTPI displays | Node.js, Express |
| iOS app | Swift, UIKit |
| Standards | GTFS, GTFS-RT, SIRI, GBFS, NeTEx |
| Infrastructure | Docker, Kubernetes |

