Show me the code
Alban Peignier
February 2, 2025
NeTEx and SIRI are described as complex and painful.
But what the matter if they only require a few lines of code ?
Alban Peignier - @apeignier@mamot.fr
CPO & Engineer at enRoute
Providing Chouette SaaS & Ara SaaS
Free software for mobility data
Works on any file
XML, ZIP, any “profile”, any producer
A small XML sample or an archive of 10 Giga bytes
Even with an invalid file
As easy as in GTFS
Richer, more complete “models”
<StopPlace id="42">
<Name>Gare Routière</Name>
<Centroid>
<Location>
<Longitude>-4.618419</Longitude>
<Latitude>48.4323529</Latitude>
</Location>
</Centroid>
<PostalAddress>
<AddressLine1>8 Rue du Pont de Bois</AddressLine1>
<Town>Saint-Renan</Town>
<PostCode>29290</PostCode>
</PostalAddress>
<placeTypes>
<TypeOfPlaceRef ref='multimodalStopPlace'/>
</placeTypes>
<TransportMode>bus</TransportMode>
<StopPlaceType>onstreetBus</StopPlaceType>
<AccessibilityAssessment version="any" id="test">
<MobilityImpairedAccess>true</MobilityImpairedAccess>
<limitations>
<AccessibilityLimitation>
<WheelchairAccess>true</WheelchairAccess>
<StepFreeAccess>true</StepFreeAccess>
<EscalatorFreeAccess>true</EscalatorFreeAccess>
<LiftFreeAccess>true</LiftFreeAccess>
<AudibleSignalsAvailable>true</AudibleSignalsAvailable>
<VisualSignsAvailable>false</VisualSignsAvailable>
</AccessibilityLimitation>
</limitations>
</AccessibilityAssessment>
</StopPlace>
NeTEx allows variety …
A lot of variety
And Code doesn’t like variety
(NeTEx + xpath = epic fail)
Large ZIP file
Archive: publications-netex-du-24-10-2024.zip
Name
----
OFFRE_7bb20cb9-bb2b-4bf8-beb1-06b68a5615f9_Poissy_-_Les_Mureaux/offre_6fc38810-b245-438d-98a7-dd1e0be38124_91.xml
OFFRE_7bb20cb9-bb2b-4bf8-beb1-06b68a5615f9_Poissy_-_Les_Mureaux/offre_2222cffa-9430-499a-9a82-bc1df79ffd9d_41.xml
...
OFFRE_dd8ea746-43fd-4566-ac20-98eb8f2ad1dd_Keolis_Roissy_Pays_de_France_Ouest/calendriers.xml
lignes.xml
arrets.xml
-------
2052 files
… Or a large unique XML
Each “Profile” creates its own structure
NeTEx Resources are never “at the same place”
😥
Verbose XML files
NeTEx Profiles love “Frames”
<PublicationDelivery xmlns="http://www.netex.org.uk/netex">
<PublicationTimestamp>2024-11-06T07:08:59Z</PublicationTimestamp>
<ParticipantRef>enRoute</ParticipantRef>
<dataObjects>
<CompositeFrame id="FR:CompositeFrame:NETEX_LIGNE:LOC">
<Name>Airport - Bullfrog</Name>
<TypeOfFrameRef ref="FR:TypeOfFrame:NETEX_LIGNE:"/>
<frames>
<GeneralFrame id="FR:GeneralFrame:NETEX_LIGNE:LOC">
<TypeOfFrameRef ref="FR:TypeOfFrame:NETEX_LIGNE:"/>
<members>
What’s matter in a NeTEx file ? NeTEx ressources ?
✅ Direct access to NeTEx resources
Forget file structure
Frame, file, line, column via tags
File structure is optional
… at least for code
NeTEx Resources / “Models” are defined by the NeTEx specs
Very few varieties at this level
But much more complex to manage by code
Small example
Transformers
Process resources to return the expected form
response = client.send SIRI::LinesDiscovery::Request.new
line_id = response.annotated_line_refs.sample.line_ref
request = SIRI::EstimatedTimetable::Request.new.tap do |request|
request.lines << SIRI::LineDirection.new(line_ref: line_id)
end
puts client.send request
A real API, not just a binary file
response = client.send SIRI::StopPointsDiscovery::Request.new
stop_area_id =
response.annotated_stop_point_refs.sample.stop_point_ref
request = SIRI::StopMonitoring::Request.new(
monitoring_ref: stop_area_id
)
response = client.send request
puts response.monitored_stop_visits.first&.expected_departure_time
SIRI uses HTTP request and:
But Protocol definition and structures are the same
Code is generic, whatever the protocol setup
Subscribe
request = SIRI::Subscribe::Request.new
subscription =
SIRI::Subscribe::Request::Subscription::VehicleMonitoring.new(
subscriber_ref: credential,
subscription_identifier: SecureRandom.uuid,
initial_termination_time: Time.now + 3600,
)
request.subscription_requests << subscription
response = client.send request
Receive Notifications
🥳 Enjoy
🚌 51 47.85724615430222 1.9050266812693997 174.0 - A 11_A06_6_18:50:00 2025-02-01 18:25:57 UTC
🚌 52 47.89505287724359 1.904487986758294 358.0 - A 11_A14_6_18:53:30 2025-02-01 18:25:57 UTC
🚌 45 47.92644341158054 1.9070538704177726 343.0 - A 11_A02_6_18:37:30 2025-02-01 18:25:57 UTC
🚌 60 47.839699622076736 1.9348941865508855 24.0 - A 11_A11_6_19:18:00 2025-02-01 18:25:57 UTC
🚌 48 47.87681771498911 1.9122357153186686 151.0 - A 11_A07_6_18:58:00 2025-02-01 18:25:57 UTC
🚌 78 47.846057734606006 1.9175851740217948 304.0 - A 11_A04_6_19:09:30 2025-02-01 18:25:57 UTC
🚌 42 47.854589704018096 1.9087590153289151 129.0 - A 11_A12_6_18:42:00 2025-02-01 18:25:57 UTC
🚌 40 47.9141923657212 1.9011074285183922 150.0 - A 11_A08_6_19:14:00 2025-02-01 18:25:57 UTC
🚌 50 47.874828005835255 1.9131460393913398 355.0 - A 11_A03_6_19:01:30 2025-02-01 18:25:57 UTC
🚌 43 47.928184457723255 1.9250431206818681 270.0 - A 11_A09_6_19:30:00 2025-02-01 18:23:57 UTC
🚌 39 47.83998941602201 1.9350889202646175 207.0 - A 11_A15_6_18:34:00 2025-02-01 18:25:57 UTC
🚌 73 47.90102296279369 1.9028523233359471 165.0 - A 11_A01_6_19:06:00 2025-02-01 18:25:57 UTC
🚌 58 47.928146431321046 1.9189373270217 271.0 - A 11_A13_6_19:22:00 2025-02-01 18:25:57 UTC
🚌 57 47.83736531991079 1.9188182936256228 250.0 - A 11_A05_6_19:26:00 2025-02-01 18:25:57 UTC
🚌 41 47.908108492305004 1.904044545180632 5.0 - A 11_A10_6_18:45:30 2025-02-01 18:25:57 UTC
🚌 68 47.90188886014469 1.8982433698255263 272.0 - B 11_B02_6_18:59:15 2025-02-01 18:25:57 UTC
🚌 65 47.90027188750417 1.8842766003856504 79.0 - B 11_B11_6_19:15:00 2025-02-01 18:25:57 UTC
🚌 103 47.90945360504696 1.94377577360228 39.0 - O 11_O01_6_19:03:00 2025-02-01 18:18:57 UTC
🚌 106 47.90697549483362 1.9060892641365008 286.0 - O 11_O02_6_19:13:00 2025-02-01 18:25:57 UTC
🚌 104 47.899214821788455 1.9048839512844586 178.0 - O 11_O03_6_19:23:00 2025-02-01 18:25:57 UTC
Indeed …
Inaccuracies create local interpretations
mostly on advanced points
Works in progress to identify and resolve them
Free and open SIRI servers are too rare :(
An urban legend says that it is complicated
RequestorRef
One is missing ?
profile = Netex::Profile::French.new
target = Netex::Target.build("sample.zip", profile: profile)
target << Netex::StopPlace.new(..)
# ...
# Same code
Create a file structured as expected by the French NeTEx Profile
class MyProfile < Netex::Profile::Base
# Route NeTEx resources to the expected document
def document_for(resource)
case resource
when Netex::StopPlace, Netex::Quay, Netex::StopPlaceEntrance
document(Document::Stops)
when Netex::DayType, ...
document(Document::Calendars)
when Netex::Line, Netex::Route, ...
document(Document::Line, line_id: resource.tag[:line_id])
# ...
end
end
# Define specific XML file
class Document::Stops < Netex::Target::Document
def initialize
super "stops.xml"
frames << general_frame('STOPS') do |frame|
frame.sections.create accept: Netex::StopPlace
frame.sections.create accept: Netex::Quay
frame.sections.create accept: Netex::StopPlaceEntrance
end
end
end
I’m alive:
Respond to your first EstimatedTimetable
request:
server.receive(SIRI::EstimatedTimetable::Request) do |request|
SIRI::EstimatedTimetable::Response.new.tap do |response|
journey = SIRI::EstimatedVehicleJourney.new
# For the (near) past
journey.recorded_calls << SIRI::Call.new(...)
# For the future
journey.estimated_calls << SIRI::Call.new(...)
response.estimated_vehicle_journeys << journey
end
end
Mostly in Ruby, use in production
🚧 Under construction:
Don’t reinvent the wheel
Validate a NeTEx file … with your own rules
with Chouette Valid
Convert NeTEx to NeTEx / to GTFS
with Chouette Convert
SIRI & NeTEx Developer resources
Open source generic libraries in other languages❔
(🙏stop generating code from the XSD)
Tools and UX for NeTEx and SIRI documentation
and National Profiles documentations
More Developers in European and National Workgroups