The following extensions for MATSim are provided by the Swiss Federal Railways (SBB, Schweizerische Bundesbahnen):
To use the extensions along your MATSim code, follow these two steps:
Step 1. Add the JitPack repository to your pom.xml
.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Step 2. Add the dependency
<dependency>
<groupId>com.github.SchweizerischeBundesbahnen</groupId>
<artifactId>matsim-sbb-extensions</artifactId>
<version>0.10.x</version>
</dependency>
The SwissRailRaptor is a fast public transport router. It is based on the RAPTOR algorithm (Delling et al, 2012, Round-Based Public Transit Routing), and applies several optimizations, namely in the selection which transfers need to be added and which transfers can be left out without influencing the outcome of the routing-process.
Actual performance gains vary by scenario, but are typically in the order of one to two magnitudes compared to the default pt router in MATSim. When applied to the complete public transport schedule of Switzerland (including trains, buses, trams, ships, cable-cars, ...), SwissRailRaptor was 95 times faster than MATSim's default pt router. In smaller scenarios, SwissRailRaptor was measured to be between 20 - 30 times faster. Memory consumption of SwissRailRaptor should also be at least one magnitude lower when compared to MATSim's default router, as should be the pre-processing time to initialize the router.
SwissRailRaptor can act as a drop-in replacement for the pt router included in MATSim by default
when it is used without further configuration, re-using the configuration parameters
from the default transitRouter
config group. A special config group is available that allows to
configure advanced features of the SwissRailRaptor not available in MATSim's default pt router (see
below).
A major difference to the default transit router in MATSim is the fact that SwissRailRaptor does not repeat the transit schedule after 24 hours when searching for a route, but only takes the actual departure times as specified in the schedule into account. This is due to the fact that not all schedules have a periodicity of 24 hours. When applied in MATSim, it can result in agents no longer finding a route when departing late at night. Considering such agents would have gotten stuck in the simulation anyway due to no scheduled pt vehicles running at that time the next day, this should not pose any real problem.
Have a look at the class ch.sbb.matsim.RunSBBExtension
included in the repository to see
how to enable SwissRailRaptor when running MATSim.
Besides acting as a drop-in replacement for MATSim's default pt router, SwissRailRaptor provides additional features that need special configuration to be activated.
By default, all legs leading from the start coordinate to the first transit stop, or leading from the last transit stop to the destination coordinate, are assumed to be undertaken by walking. But SwissRailRaptor also support choosing different modes for these access and egress legs.
Other modes, e.g. bike, usually have a higher speed, and thus transit stops with a larger distance to the start or destination coordinate should be taken into account than just those reachable by a sensible walking duration. In order to reduce the number of potential start and destination stops when increasing the search radius, SwissRailRouter allows to filter the stops based on stops' attributes.
To use intermodal access and egress legs and configure the allowed modes and stops, add
the following config module to your config.xml
:
<module name="swissRailRaptor">
<param name="useIntermodalAccessEgress" value="true" />
<paramset type="intermodalAccessEgress">
<param name="mode" value="walk" />
<param name="radius" value="1000" />
</paramset>
<paramset type="intermodalAccessEgress">
<param name="mode" value="bike" />
<param name="radius" value="3000" />
<param name="linkIdAttribute" value="accessLinkId_bike" />
<param name="personFilterAttribute" value="hasBike" />
<param name="personFilterValue" value="true" />
<param name="stopFilterAttribute" value="bikeAccessible" />
<param name="stopFilterValue" value="true" />
</paramset>
</module>
In the above example, intermodal access and egress is enabled (useIntermodalAccessEgress=true
)
and two modes are configured for it: walk
and bike
. Walk can be used by all agents
(no personFilterAttribute
defined) and uses all transit stops (no stopFilterAttribute
defined) within a radius
of 1000 around the start or destination coordinates. Bike can only be used by agents having a person
attribute hasBike
with the value of true
, and uses only transit stops that have an attribute named
bikeAccessible
with the value true
. If bike is routed on the network, it's possible that
no route can be calculated from an activity's link to the transit stop links, e.g. if the transit
stop is a train station and the assigned link refers to a "rail"-link which is not connected to
the bike-network. In such cases, a transit stop attribute can be specified that contains the
linkId to (or from) which a route with the given mode should be routed (linkIdAttribute
).
Additional modes could be configured by adding corresponding parameter sets of type intermodalAccessEgress
.
Note that when intermodal access and egress is enabled in SwissRailRaptor, walk
must be
configured as well, as the settings from the default transitRouter
config group will be
ignored.
If intermodal access and egress legs are created, the default MainModeIdentifier might not
recognize such trips as pt trips. Therefore, an adapted MainModeIdentifier must be used.
SwissRailRaptorModule
enables such an adapted one, so it should work out of the box. If you combine
the intermodal SwissRailRaptor with other MATSim extensions, also requiring custom
MainModeIdentifiers, make sure to provide an implementation combining the different
requirements correctly.
Range queries, sometimes also named profile queries, search for possible connections within a time window instead of finding only one connection that arrives with least cost based on a fixed departure time. As MATSim still requires a single route in the end to be assigned to the agent, a route must be selected from the returned route set to be assigned to the agent.
To configure SwissRailRaptor to first search for a pt route within a time window, and then select a matching route, use the following configuration parameters:
<module name="swissRailRaptor">
<param name="useRangeQuery" value="true" />
<paramset type="rangeQuerySettings">
<param name="maxEarlierDeparture_sec" value="600" />
<param name="maxLaterDeparture_sec" value="900" />
<param name="subpopulations" value="" /> <!-- an empty value applies to every agent, comma-separated list of multiple subpopulations possible -->
</paramset>
<paramset type="routeSelector">
<param name="betaTravelTime" value="1" />
<param name="betaDepartureTime" value="1" />
<param name="betaTransferCount" value="300" />
<param name="subpopulations" value="" /> <!-- an empty value applies to every agent, comma-separated list of multiple subpopulations possible -->
</paramset>
</module>
The default route selection algorithm (ch.sbb.matsim.routing.pt.raptor.ConfigurableRaptorRouteSelector
)
supports selecting a route based on a calculated score that depends on the total travel time, the number
of transfers, and the deviation from the desired departure time:
score = betaDepartureTime * abs(desiredDepartureTime - effectiveDepartureTime)
+ betaTravelTime * totalTravelTime
+ betaTransfer * transferCount
The route with the best (lowest) score will be chosen and returned as a series of legs, to be integrated into the agents plan. If multiple routes share the same best score, a random one of this set will be selected.
Once a route was selected from the calculated choice set, the end time of the previous activity is adapted to ensure an optimal departure time for the chosen connection. It is possible to provide multiple settings for different subpopulations. This allows to have one group of agents to be flexible in their departure time choice, while others are not.
Be aware that range queries infer a large performance penalty!
Note that the original departure time for this trip will not be adapted! It may thus happen that an agent misses the planned pt departure if it departs earlier than the desired departure time, or that the agents waits too long at a stop. This is currently due to some limitations in MATSim and we hope to be able to optimally adjust the previous activity's end time (and thus the departure time) in the future.
Instead of using the built-in, configurable route selection algorithm, a custom implementation
of the interface ch.sbb.matsim.routing.pt.raptor.RaptorRouteSelector
can be provided:
// somewhere in your main method, where you set up your controler:
controler.addOverridingModule(new SwissRailRaptorModule());
controler.addOverridingModule(new AbstractModule() {
@Override
public void install() {
bind(RaptorRouteSelector.class).to(MyCustomRouteSelector.class);
}
});
This allows one to implement more complex choice behaviors.
By default, the pt router creates legs with mode pt
. In some cases, it is necessary to
sub-divide the public transport services. This might be the case when some services operate
at special (or strongly different) prices or speeds. For example, slow but luxury class tourist
trains, high-speed trains that require a special ticket, or long distance coaches that are cheaper
but slower than a comparable train service. In all such cases it might be necessary to apply
different costs to the different services in order to find realistic routes. Also, to stay
consistent, the different costs should be used for the scoring of the executed plans.
SwissRailRaptor supports differentiating pt sub-modes by mapping the transportMode of transit
lines and routes (in the following referred as "route mode") to "passenger modes". The costs for using
such passenger modes can then be configured in the normal planCalcScore
configuration group.
To configure the passenger mode mappings, add the following section to your config.xml
:
<module name="swissRailRaptor">
<param name="useModeMappingForPassengers" value="true" />
<paramset type="modeMapping">
<param name="routeMode" value="train" />
<param name="passengerMode" value="rail" />
</paramset>
<paramset type="modeMapping">
<param name="routeMode" value="tram" />
<param name="passengerMode" value="rail" />
</paramset>
<paramset type="modeMapping">
<param name="routeMode" value="bus" />
<param name="passengerMode" value="road" />
</paramset>
</module>
In the example above, it is assumed that in transitSchedule.xml
the modes train
, tram
and bus
are used as transport modes for the operating services. During route search, the
scoring parameters for the modes rail
and road
are used to calculate the costs of using
the respective lines. In the resulting legs that make up the found route, the modes rail
and road
will be used as well instead of the default pt
that is used by MATSim's default
pt router to describe public transport legs. This implies that next to providing the scoring
parameters for the passenger modes (in the example above rail
and road
), these passenger
modes must also be listed as transit modes in the transit configuration, so they will be
correctly recognized and handled as pt passenger legs:
<module name="transit">
<param name="transitModes" value="rail,road" />
</module>
In some scenarios, costs to use public transport may differ from agent to agent. The most likely application is the combination with pt sub-modes described above: Some agents might have a season ticket that only applies to certain lines, while other agents don't have such a season ticket. Or agents might have different values of travel time based on their income, and thus prefer different services of competing ones.
In order to support such scenarios, SwissRailRaptor provides the interface
ch.sbb.matsim.routing.pt.raptor.RaptorParametersForPerson
which allows to specify the
parameters used for each routing request depending on the agent requesting a route.
By default, a simple implementation ch.sbb.matsim.routing.pt.raptor.DefaultRaptorParametersForPerson
is used that returns the same parameters for every request. In order to use a more specialized
implementation, bind your implementation of the RaptorParametersForPerson
interface as follows:
// somewhere in your main method, where you set up your controler:
controler.addOverridingModule(new SwissRailRaptorModule());
controler.addOverridingModule(new AbstractModule() {
@Override
public void install() {
bind(RaptorParametersForPerson.class).to(MyPersonSpecificRaptorParameters.class);
}
});
Make sure to bind your implementation after installing the SwissRailRaptorModule
in order to
actually overwrite the default binding for RaptorParametersForPerson
.
The default pt router in MATSim applies a fixed cost term for each transfer during route search. This can lead to problems, as empirical data shows that perceived costs for transfers depend the total travel time: A transfer during an urban commute of a total of 15 minutes is perceived with a lower disutility than a transfer during a long-distance journey of 2 hours.
SwissRailRaptor supports transfer costs based on the total travel of a route, with additional minimal and maximal boundaries for the transfer costs:
<module name="swissRailRaptor">
<param name="transferPenaltyBaseCost" value="0.5" />
<param name="transferPenaltyCostPerTravelTimeHour" value="1.2" />
<param name="transferPenaltyMinCost" value="1.0" />
<param name="transferPenaltyMaxCost" value="5.0" />
</module>
If the transferPenaltyCostPerTravelTimeHour
is configured differently from 0.0
,
transfer costs during route search are calculated as:
singleTransferCost = transferPenaltyBaseCost + (totalTravelTime/3600) * transferPenaltyCostPerTravelTimeHour;
if (singleTransferCost < transferPenaltyMinCost) singleTransferCost = transferPenaltyMinCost;
if (singleTransferCost > transferPenaltyMaxCost) singleTransferCost = transferPenaltyMaxCost;
totalTransferCost = numberOfTransfers * singleTransferCost
If transferPenaltyCostPerTravelTimeHour
is set to 0.0
, each transfer costs -utilityOfLineSwitch
to stay backwards compatible with the default transit router. All other transferPenalty
-parameters
are ignored in this case. If the cost-per-traveltime-hour is set
differently from 0.0
, utilityOfLineSwitch
will be ignored and the aforementioned transferPenaltyBaseCost
be used instead.
Assuming a travel time disutility of 6 utils per hour, combined with opportunity costs of another 6 utils per hour would result in a total travel time disutility of 12 utils per hour. The configured value of 1.2 in the example above would thus correspond to a single transfer having a (non-fixed) cost comparable to 10% of the total travel time.
In some cases, one might be interested in the travel times not only between two coordinates,
but from one (or a few) stop to all others. SwissRailRaptor provides the method
SwissRailRaptor.calcTree(...)
for this purpose which returns a map containing for each
transit stop a number of inicators like travel time or number of transfers to reach that
stop from the originating stop.
If you plan to use this method, make sure to set
RaptorStaticConfig.setOptimization(RaptorOptimization.OneToAllRouting)
before
calling SwissRailRaptorData.create(...)
.
The deterministic pt simulation is a QSim engine, handling the movement of public transport vehicles
in MATSim. The default TransitQSimEngine
simulates all pt vehicles on the queue-based network. While
this works well for buses that share the road-infrastructure with private car traffic, it has some drawbacks
when simulating railway transportation. Most notably, trains don't always run at the highest speed
allowed on links (rails) in reality, often resulting in early arrivals when being simulated.
The deterministic pt simulation does not simulate the pt vehicles on the queue network, but uses its own data structure and "teleports" the vehicles from stop to stop according to the departure and arrival times specified in the schedule. Thus, the vehicles operate strictly according to the transit schedule, hence the name "deterministic" pt simulation.
It is possible to configure the deterministic pt simulation in a way that not all pt vehicles are simulated deterministically, but that some (e.g. buses) are still simulated on the queue network and are thus able to interact with private car traffic.
To use the deterministic pt simulation, a few things need to be taken into account:
-
transportMode of TransitRoutes
When specifying TransitRoutes in a transit schedule, provide a meaningful
transportMode
to the routes:<transitLine id="1"> <transitRoute id="1"> <transportMode>train</transportMode> <routeProfile>...</routeProfile> ... </transitRoute> </transitLine>
The
transportMode
specified in the transit routes is used to determine whether the vehicles serving that route should be simulated using the deterministic pt simulation, or on the queue network. By using modes like train, bus, metro you can specify which of those should be simulated deterministically (e.g. train and metro), and which should be simulated on the network (e.g. bus).Do not use
pt
as a transportMode in transit routes. This interferes with the modept
that passengers use to specify that they want to use a public transport service. The deterministic simulation will throw an exception if a transit route with modept
should be simulated deterministically. -
config.xml
You need an additional config module in your
config.xml
:<module name="SBBPt" > <param name="deterministicServiceModes" value="train,metro" /> <param name="createLinkEventsInterval" value="10" /> </module>
The first parameter
deterministicServiceModes
lists all transportModes of transit routes that should be simulated deterministically. Multiple modes are separated by a comma in the parameter's value. All transportModes of transit routes not specified in this list will be simulated on the queue network as usual.The second parameter
createLinkEventsInterval
specifies in which iteration LinkEnter- and LinkLeave-events should be generated for vehicles simulated by the deterministic pt engine. As pt vehicles are teleported between stops by the deterministic pt simulation, they do not create any Link-events by default. But for visualization or analysis purposes it might still be useful to have such events as if the vehicles were actually driving along the links. Set the parameter to0
to disable the creation of link-events. If the parameter is set to a value >0, the deterministic pt simulation will create appropriate Link-events every n-th iteration, similar to the controller'swriteEventsInterval
.
Have a look at the class ch.sbb.matsim.RunSBBExtension
included in the repository to see
how to enable the deterministic pt simulation when running MATSim.
Skim matrices describe performance or supply indicators for travelling from one region to another region. Examples are travel times using car or public transport, travel distances, but also number of transfers required when travelling with public transport or the perceived frequency of transit services.
MATSim typically calculates routes and travel times individually for each agent, based on specific coordinates. For analysis purposes, comparison to alternative models or for the preparation of the initial demand, it can be necessary to have travel times, travel distances and other travel performance indicators not only for individual agents, but aggregations of these values between spatial zones of a model.
This repository contains code to calculate a variety of skim matrices based on MATSim data:
- for private car traffic:
- travel time, routed on network (loaded or unloaded)
- travel distances, routed on network (loaded or unloaded)
- for public transport:
- travel time (from departure at first stop to arrival at last stop of a trip)
- access time (from origin to first stop)
- egress time (from last stop to destination)
- number of transfers required
- average adaption time (how many minutes needs an agent to wait or leave early in average to catch the next best service)
- perceived service frequency
- percentage of the distance covered with rail-based transportation within a public transport trip
- percentage of the travel time spent in rail-based transportation within a public transport trip
- general:
- bee-line distances between zones
To calculate skim matrices, you need:
- required: shape-file containing the zones to be differentiated in the model
- required: network
- optionally: events, if travel times on a loaded network should be calculated
- recommended: facilities, to sample locations within zones; alternatively nodes from the network can be used
- required: transit schedule
The class ch.sbb.matsim.analysis.skims.CalculateSkimMatrices
is the main class that
exposes some helpful methods to calculate skim matrices. It contains a main method that accepts
arguments to be directly run, alternatively the main method can act as a template for customized
skim matrix calculations.
The basic template to calculate skim matrices looks as follows:
CalculateSkimMatrices skims = new CalculateSkimMatrices(zonesShapeFilename, zonesIdAttributeName, outputDirectory, numberOfThreads);
skims.calculateSamplingPointsPerZoneFromFacilities(facilitiesFilename, numberOfPointsPerZone, r, facility -> 1.0);
// alternative if you don't have facilities:
// skims.calculateSamplingPointsPerZoneFromNetwork(networkFilename, numberOfPointsPerZone, r);
skims.calculateNetworkMatrices(networkFilename, eventsFilename, timesCar, config, null, link -> true);
skims.calculatePTMatrices(networkFilename, transitScheduleFilename, earliestTime, latestTime, config, null, (line, route) -> route.getTransportMode().equals("train"));
skims.calculateBeelineMatrix();
The methods all write the skim matrices directly into files in the specified output directory.
To work with the matrices, use the classes FloatMatrix
(the actual matrix) and FloatMatrixIO
(code related to reading/writing matrices). The class MatricesToXY
gives an example on how
to combine the values of multiple matrices into one table (CSV).
Be aware that the calculation of skim matrices for a large number of zones can take several hours and can require large amounts of memory, despite optimized algorithms (see section Technical Background below).
If you use your own class to call the methods in CalculateSkimMatrices
, you will have to
provide a few custom functions:
- To select optimal locations per zone, a random draw from weighted facilities is performed.
You need to specify a function returning a weight for each facility (see method
CalculateSkimMatrices.calculateSamplingPointsPerZoneFromFacilities(...)
). If in doubt, you can return the same weight of 1 for every facility to have an unweighted random draw. - To assign selected locations to the road network, not every link might be suited (e.g. it
is considered bad practise to start or end trips directly on highway-links). Thus you
need to specify a function returning
true
for every link that could be used as start or end of a trip, andfalse
otherwise (see methodCalculateSkimMatrices.calculateNetworkMatrices(...)
). If in doubt, just returntrue
for every link. - In order to calculate the share of rail-based transportation, the algorithm needs to know
which transit lines and routes qualify as rail-based. You need to provide a function returning
true
for every transit line/route that should be counted as rail-based, andfalse
in other cases (see methodCalculateSkimMatrices.calculatePTMatrices(...)
). If in doubt, just return eithertrue
orfalse
for every request. In such a case, the calculated rail-based shares will all be either 1 or 0.
In some cases, especially with public transport, cases might occur where no trip
can be calculated between two zones. In such a case, the skim matrices contain the value
0
in the case of the perceived transit frequency, and Infinity
for all other matrices.
Strictly speaking, travel indicators cannot be calculated between zones, only between specific locations. To calculate meaningful indicators for travels between zones, average values have to be calculated that should represent actual averages as close as possible.
The implemented algorithms are based on the following concept:
- Choose
n
locations for each zone. - For each zone-pair (origin – destination), calculate all
n*n
possible trips. - Average the
n*n
values into one value for the skim matrix
In order to get representative averages, the n
locations need to be carefully chosen.
In our experiments, we achieved the best results by sampling from the facilities, where
each facility (=potential location for activities to take place) has a weight (e.g. home
locations have a weight of 2.0, work locations according to the number of work places
they offer in full-time equivalents). This is the reason why by default the calculation
of skims requires facilities. If your model does not use facilities, there is alternative
code available that samples from the nodes of a network instead of from facilities, based
on the observation that there are more nodes in a network in central locations where also
more activities take place than in remote locations.
Also, actual travel times will be different throughout the day. It is thus advised to calculate performance indices not only for a single point of time, but for multiple points of time and take the average of these.
Calculating n*n
trips for each m*m
zone-pairs can result in a very
large number of calculations needed. E.g. with 1000 zones and 5 locations per zones,
it would require 25 million trips to be calculated. If the calculation is repeated for a
total of 4 different points in time, 100 million trips would need to be calculated.
By calculating full least-cost-path trees instead of single trips, the number of trip-calculations
can be reduced to n*m
per point of time, bringing a massive reduction in computation time, but
with a slight increase in memory usage. In addition, most parts of the skim calculation
are multi-threaded and benefit from additional cpu-cores.