MATSIM
SwissRailRaptorData.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.* *
3  *
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2023 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package ch.sbb.matsim.routing.pt.raptor;
22 
23 import java.util.*;
24 import java.util.function.Supplier;
25 
26 import javax.annotation.Nullable;
27 
28 import org.apache.logging.log4j.LogManager;
29 import org.apache.logging.log4j.Logger;
30 import org.matsim.api.core.v01.Coord;
31 import org.matsim.api.core.v01.Id;
32 import org.matsim.api.core.v01.IdMap;
47 import org.matsim.vehicles.Vehicle;
49 
52 
56 public class SwissRailRaptorData {
57 
58  private static final Logger log = LogManager.getLogger(SwissRailRaptorData.class);
59 
60  final RaptorStaticConfig config;
61  final int countStops;
62  final int countRouteStops;
63  final RRoute[] routes;
64  final int[] departures; // in the RAPTOR paper, this is usually called "trips", but I stick with the MATSim nomenclature
65  final Vehicle[] departureVehicles; // the vehicle used for each departure
66  final Id<Departure>[] departureIds;
67  final RRouteStop[] routeStops; // list of all route stops
68  final RTransfer[] transfers;
69  final Map<TransitStopFacility, Integer> stopFacilityIndices;
70  final Map<TransitStopFacility, int[]> routeStopsPerStopFacility;
71  final QuadTree<TransitStopFacility> stopsQT;
72  final Map<String, Map<String, QuadTree<TransitStopFacility>>> stopFilterAttribute2Value2StopsQT;
73  final OccupancyData occupancyData;
74 
75  // data needed if cached transfer construction is activated
77  final RTransfer[][] transferCache;
78 
79  private SwissRailRaptorData(RaptorStaticConfig config, int countStops,
80  RRoute[] routes, int[] departures, Vehicle[] departureVehicles, Id<Departure>[] departureIds, RRouteStop[] routeStops,
81  RTransfer[] transfers, Map<TransitStopFacility, Integer> stopFacilityIndices,
82  Map<TransitStopFacility, int[]> routeStopsPerStopFacility, QuadTree<TransitStopFacility> stopsQT,
83  OccupancyData occupancyData, IdMap<TransitStopFacility, Map<TransitStopFacility, Double>> staticTransferTimes) {
84  this.config = config;
85  this.countStops = countStops;
86  this.countRouteStops = routeStops.length;
87  this.routes = routes;
88  this.departures = departures;
89  this.departureVehicles = departureVehicles;
90  this.departureIds = departureIds;
91  this.routeStops = routeStops;
92  this.transfers = transfers;
93  this.stopFacilityIndices = stopFacilityIndices;
94  this.routeStopsPerStopFacility = routeStopsPerStopFacility;
95  this.stopsQT = stopsQT;
96  this.stopFilterAttribute2Value2StopsQT = new HashMap<>();
97  this.occupancyData = occupancyData;
98 
99  // data needed if cached transfer construction is activated
100  this.staticTransferTimes = staticTransferTimes;
101  this.transferCache = new RTransfer[routeStops.length][];
102  }
103 
104  public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Vehicles transitVehicles, RaptorStaticConfig staticConfig, Network network, OccupancyData occupancyData) {
105  log.info("Preparing data for SwissRailRaptor...");
106  long startMillis = System.currentTimeMillis();
107 
108  Map<Id<Vehicle>, Vehicle> vehicles = transitVehicles == null ? Collections.emptyMap() : transitVehicles.getVehicles();
109  int countRoutes = 0;
110  long countRouteStops = 0;
111  long countDepartures = 0;
112 
113  for (TransitLine line : schedule.getTransitLines().values()) {
114  countRoutes += line.getRoutes().size();
115  for (TransitRoute route : line.getRoutes().values()) {
116  countRouteStops += route.getStops().size();
117  countDepartures += route.getDepartures().size();
118  }
119  }
120 
121  if (countRouteStops > Integer.MAX_VALUE) {
122  throw new RuntimeException("TransitSchedule has too many TransitRouteStops: " + countRouteStops);
123  }
124  if (countDepartures > Integer.MAX_VALUE) {
125  throw new RuntimeException("TransitSchedule has too many Departures: " + countDepartures);
126  }
127 
128  int[] departures = new int[(int) countDepartures];
129  Vehicle[] departureVehicles = new Vehicle[(int) countDepartures];
130  Id<Departure>[] departureIds = new Id[(int) countDepartures];
131  RRoute[] routes = new RRoute[countRoutes];
132  RRouteStop[] routeStops = new RRouteStop[(int) countRouteStops];
133 
134  int indexRoutes = 0;
135  int indexRouteStops = 0;
136  int indexDeparture = 0;
137 
138  // enumerate TransitStopFacilities along their usage in transit routes to (hopefully) achieve a better memory locality
139  // well, I'm not even sure how often we'll need the transit stop facilities, likely we'll use RouteStops more often
140  Map<TransitStopFacility, Integer> stopFacilityIndices = new HashMap<>((int) (schedule.getFacilities().size() * 1.5));
141  // Using a LinkedHashMap instead of a regular HashMap here is necessary to have a deterministic behaviour
142  Map<TransitStopFacility, int[]> routeStopsPerStopFacility = new LinkedHashMap<>();
143 
144  boolean useModeMapping = staticConfig.isUseModeMappingForPassengers();
145  for (TransitLine line : schedule.getTransitLines().values()) {
146  List<TransitRoute> transitRoutes = new ArrayList<>(line.getRoutes().values());
147  transitRoutes.sort(Comparator.comparingDouble(tr -> getEarliestDeparture(tr).getDepartureTime())); // sort routes by earliest departure for additional performance gains
148  for (TransitRoute route : transitRoutes) {
149  int indexFirstDeparture = indexDeparture;
150  String mode = TransportMode.pt;
151  if (useModeMapping) {
152  mode = staticConfig.getPassengerMode(route.getTransportMode());
153  }
154  RRoute rroute = new RRoute(indexRouteStops, route.getStops().size(), indexFirstDeparture, route.getDepartures().size());
155  routes[indexRoutes] = rroute;
156  NetworkRoute networkRoute = route.getRoute();
157  List<Id<Link>> allLinkIds = new ArrayList<>();
158  allLinkIds.add(networkRoute.getStartLinkId());
159  allLinkIds.addAll(networkRoute.getLinkIds());
160  if (allLinkIds.size() > 1 || networkRoute.getStartLinkId() != networkRoute.getEndLinkId()) {
161  allLinkIds.add(networkRoute.getEndLinkId());
162  }
163  Iterator<Id<Link>> linkIdIterator = allLinkIds.iterator();
164  Id<Link> currentLinkId = linkIdIterator.next();
165  double distanceAlongRoute = 0.0;
166  for (TransitRouteStop routeStop : route.getStops()) {
167  while (!routeStop.getStopFacility().getLinkId().equals(currentLinkId)) {
168  if (linkIdIterator.hasNext()) {
169  currentLinkId = linkIdIterator.next();
170  Link link = network.getLinks().get(currentLinkId);
171  distanceAlongRoute += link.getLength();
172  } else {
173  distanceAlongRoute = Double.NaN;
174  break;
175  }
176  }
177  int stopFacilityIndex = stopFacilityIndices.computeIfAbsent(routeStop.getStopFacility(), stop -> stopFacilityIndices.size());
178  final int thisRouteStopIndex = indexRouteStops;
179  RRouteStop rRouteStop = new RRouteStop(thisRouteStopIndex, routeStop, line, route, mode, indexRoutes, stopFacilityIndex, distanceAlongRoute);
180  routeStops[thisRouteStopIndex] = rRouteStop;
181  routeStopsPerStopFacility.compute(routeStop.getStopFacility(), (stop, currentRouteStops) -> {
182  if (currentRouteStops == null) {
183  return new int[] { thisRouteStopIndex };
184  }
185  int[] tmp = new int[currentRouteStops.length + 1];
186  System.arraycopy(currentRouteStops, 0, tmp, 0, currentRouteStops.length);
187  tmp[currentRouteStops.length] = thisRouteStopIndex;
188  return tmp;
189  });
190  indexRouteStops++;
191  }
192  for (Departure dep : route.getDepartures().values()) {
193  departures[indexDeparture] = (int) dep.getDepartureTime();
194  departureVehicles[indexDeparture] = vehicles.get(dep.getVehicleId());
195  departureIds[indexDeparture] = dep.getId();
196  indexDeparture++;
197  }
198  Arrays.sort(departures, indexFirstDeparture, indexDeparture);
199  indexRoutes++;
200  }
201  }
202 
203  // only put used transit stops into the quad tree
204  Set<TransitStopFacility> stops = routeStopsPerStopFacility.keySet();
206  int countStopFacilities = stops.size();
207 
208  // if cached transfer calculation is active, don't generate any transfers here
209  final Map<Integer, RTransfer[]> allTransfers;
210 
211  if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) {
212  allTransfers = calculateRouteStopTransfers(schedule, stopsQT, routeStopsPerStopFacility, routeStops,
213  staticConfig);
214  } else {
215  allTransfers = Collections.emptyMap();
216  }
217 
218  long countTransfers = 0;
219  for (RTransfer[] transfers : allTransfers.values()) {
220  countTransfers += transfers.length;
221  }
222  if (countTransfers > Integer.MAX_VALUE) {
223  throw new RuntimeException("TransitSchedule has too many Transfers: " + countTransfers);
224  }
225  RTransfer[] transfers = new RTransfer[(int) countTransfers];
226  int indexTransfer = 0;
227  for (int routeStopIndex = 0; routeStopIndex < routeStops.length; routeStopIndex++) {
228  RTransfer[] stopTransfers = allTransfers.get(routeStopIndex);
229  int transferCount = stopTransfers == null ? 0 : stopTransfers.length;
230  if (transferCount > 0) {
231  RRouteStop routeStop = routeStops[routeStopIndex];
232  routeStop.indexFirstTransfer = indexTransfer;
233  routeStop.countTransfers = transferCount;
234  System.arraycopy(stopTransfers, 0, transfers, indexTransfer, transferCount);
235  indexTransfer += transferCount;
236  }
237  }
238 
239  // if adaptive transfer calculation is used, build a map for quick lookup of and collection of minimal transfer times
241  if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Adaptive)) {
242  staticTransferTimes = new IdMap<>(TransitStopFacility.class);
243 
245  while (iterator.hasNext()) {
246  iterator.next();
247 
248  // we only put the predefined transfer times here, the location-based ones will be calculated
249  // adaptively during routing
250  staticTransferTimes.computeIfAbsent(iterator.getFromStopId(), id -> new HashMap<>())
251  .put(schedule.getFacilities().get(iterator.getToStopId()), iterator.getSeconds());
252  }
253  }
254 
255  SwissRailRaptorData data = new SwissRailRaptorData(staticConfig, countStopFacilities, routes, departures, departureVehicles, departureIds, routeStops, transfers, stopFacilityIndices, routeStopsPerStopFacility, stopsQT, occupancyData, staticTransferTimes);
256 
257  long endMillis = System.currentTimeMillis();
258  log.info("SwissRailRaptor data preparation done. Took " + (endMillis - startMillis) / 1000 + " seconds.");
259  log.info("SwissRailRaptor statistics: #routes = " + routes.length);
260  log.info("SwissRailRaptor statistics: #departures = " + departures.length);
261  log.info("SwissRailRaptor statistics: #routeStops = " + routeStops.length);
262  log.info("SwissRailRaptor statistics: #stopFacilities = " + countStopFacilities);
263  log.info("SwissRailRaptor statistics: #transfers (between routeStops) = " + transfers.length);
264  return data;
265  }
266 
267  // calculate possible transfers between TransitRouteStops
268  private static Map<Integer, RTransfer[]> calculateRouteStopTransfers(TransitSchedule schedule, QuadTree<TransitStopFacility> stopsQT, Map<TransitStopFacility, int[]> routeStopsPerStopFacility, RRouteStop[] routeStops, RaptorStaticConfig config) {
269  Map<Integer, RTransfer[]> transfers = new HashMap<>(stopsQT.size() * 5);
270  double maxBeelineWalkConnectionDistance = config.getBeelineWalkConnectionDistance();
271  double beelineWalkSpeed = config.getBeelineWalkSpeed();
272  double beelineDistanceFactor = config.getBeelineWalkDistanceFactor();
273  double minimalTransferTime = config.getMinimalTransferTime();
274 
275  Map<TransitStopFacility, List<TransitStopFacility>> stopToStopsTransfers = new HashMap<>();
276 
277  // first, add transfers based on distance
278  for (TransitStopFacility fromStop : routeStopsPerStopFacility.keySet()) {
279  Coord fromCoord = fromStop.getCoord();
280  Collection<TransitStopFacility> nearbyStops = stopsQT.getDisk(fromCoord.getX(), fromCoord.getY(), maxBeelineWalkConnectionDistance);
281  stopToStopsTransfers.computeIfAbsent(fromStop, stop -> new ArrayList<>(5)).addAll(nearbyStops);
282  }
283 
284  // take the transfers from the schedule into account
286  while (iter.hasNext()) {
287  iter.next();
288  Id<TransitStopFacility> fromStopId = iter.getFromStopId();
289  TransitStopFacility fromStop = schedule.getFacilities().get(fromStopId);
290  Id<TransitStopFacility> toStopId = iter.getToStopId();
291  TransitStopFacility toStop = schedule.getFacilities().get(toStopId);
292  List<TransitStopFacility> destinationStops = stopToStopsTransfers.computeIfAbsent(fromStop, stop -> new ArrayList<>(5));
293  if (!destinationStops.contains(toStop)) {
294  destinationStops.add(toStop);
295  }
296  }
297 
298  // now calculate the transfers between the route stops
300  ArrayList<RTransfer> stopTransfers = new ArrayList<>();
301  for (Map.Entry<TransitStopFacility, List<TransitStopFacility>> e : stopToStopsTransfers.entrySet()) {
302  TransitStopFacility fromStop = e.getKey();
303  Coord fromCoord = fromStop.getCoord();
304  int[] fromRouteStopIndices = routeStopsPerStopFacility.get(fromStop);
305  Collection<TransitStopFacility> nearbyStops = e.getValue();
306  for (TransitStopFacility toStop : nearbyStops) {
307  int[] toRouteStopIndices = routeStopsPerStopFacility.get(toStop);
308  double beelineDistance = CoordUtils.calcEuclideanDistance(fromCoord, toStop.getCoord());
309  double transferTime = beelineDistance / beelineWalkSpeed;
310  if (transferTime < minimalTransferTime) {
311  transferTime = minimalTransferTime;
312  }
313 
314  transferTime = mtt.get(fromStop.getId(), toStop.getId(), transferTime);
315 
316  final double fixedTransferTime = transferTime; // variables must be effective final to be used in lambdas (below)
317 
318  for (int fromRouteStopIndex : fromRouteStopIndices) {
319  RRouteStop fromRouteStop = routeStops[fromRouteStopIndex];
320  stopTransfers.clear();
321  for (int toRouteStopIndex : toRouteStopIndices) {
322  RRouteStop toRouteStop = routeStops[toRouteStopIndex];
323  if (isUsefulTransfer(fromRouteStop, toRouteStop, maxBeelineWalkConnectionDistance, config.getOptimization())
324  && isTransferAllowed(fromRouteStop, toRouteStop)
325  ) {
326  RTransfer newTransfer = new RTransfer(fromRouteStopIndex, toRouteStopIndex, fixedTransferTime, beelineDistance * beelineDistanceFactor);
327  stopTransfers.add(newTransfer);
328  }
329  }
330  RTransfer[] newTransfers = stopTransfers.toArray(new RTransfer[0]);
331  transfers.compute(fromRouteStopIndex, (routeStopIndex, currentTransfers) -> {
332  if (currentTransfers == null) {
333  return newTransfers;
334  }
335  RTransfer[] tmp = new RTransfer[currentTransfers.length + newTransfers.length];
336  System.arraycopy(currentTransfers, 0, tmp, 0, currentTransfers.length);
337  System.arraycopy(newTransfers, 0, tmp, currentTransfers.length, newTransfers.length);
338  return tmp;
339  });
340  }
341  }
342  }
343  return transfers;
344  }
345 
346  private static boolean isUsefulTransfer(RRouteStop fromRouteStop, RRouteStop toRouteStop, double maxBeelineWalkConnectionDistance, RaptorStaticConfig.RaptorOptimization optimization) {
347  if (fromRouteStop == toRouteStop) {
348  return false;
349  }
350  // there is no use to transfer away from the first stop in a route
351  if (isFirstStopInRoute(fromRouteStop)) {
352  return false;
353  }
354  // there is no use to transfer to the last stop in a route, we can't go anywhere from there
355  if (isLastStopInRoute(toRouteStop)) {
356  return false;
357  }
358  // if the first departure at fromRouteStop arrives after the last departure at toRouteStop,
359  // we'll never get any connection here
360  if (hasNoPossibleDeparture(fromRouteStop, toRouteStop)) {
361  return false;
362  }
363  // if the stop facilities are different, and the destination stop is part
364  // of the current route, it does not make sense to transfer here
365  if (toStopIsPartOfRouteButNotSame(fromRouteStop, toRouteStop)) {
366  return false;
367  }
368  // assuming vehicles serving the exact same stop sequence do not overtake each other,
369  // it does not make sense to transfer to another route that serves the exact same upcoming stops
370  if (cannotReachAdditionalStops(fromRouteStop, toRouteStop)) {
371  return false;
372  }
374  // If one could have transferred to the same route one stop before, it does not make sense
375  // to transfer here.
376  // This optimization may lead to unexpected results in the case of OneToAllRouting ("tree"),
377  // e.g. when starting at a single stop, users would expect that the stop facility
378  // in the opposite direction could be reached within a minute or so by walk. But the algorithm
379  // would find this if the transfers are missing.
380  return !couldHaveTransferredOneStopEarlierInOppositeDirection(fromRouteStop, toRouteStop, maxBeelineWalkConnectionDistance);
381  }
382  // if we failed all other checks, it looks like this transfer is useful
383  return true;
384  }
385 
386  private static boolean isTransferAllowed(RRouteStop fromRouteStop, RRouteStop toRouteStop) {
387  return fromRouteStop.routeStop.isAllowAlighting() && toRouteStop.routeStop.isAllowBoarding();
388  }
389 
390  private static boolean isFirstStopInRoute(RRouteStop routeStop) {
391  TransitRouteStop firstRouteStop = routeStop.route.getStops().get(0);
392  return routeStop.routeStop == firstRouteStop;
393  }
394 
395  private static boolean isLastStopInRoute(RRouteStop routeStop) {
396  List<TransitRouteStop> routeStops = routeStop.route.getStops();
397  TransitRouteStop lastRouteStop = routeStops.get(routeStops.size() - 1);
398  return routeStop.routeStop == lastRouteStop;
399  }
400 
401  private static boolean hasNoPossibleDeparture(RRouteStop fromRouteStop, RRouteStop toRouteStop) {
402  Departure earliestDep = getEarliestDeparture(fromRouteStop.route);
403  Departure latestDep = getLatestDeparture(toRouteStop.route);
404  if (earliestDep == null || latestDep == null) {
405  return true;
406  }
407  double earliestArrival = earliestDep.getDepartureTime() + fromRouteStop.arrivalOffset;
408  double latestDeparture = latestDep.getDepartureTime() + toRouteStop.departureOffset;
409  return earliestArrival > latestDeparture;
410  }
411 
413  Departure earliest = null;
414  for (Departure dep : route.getDepartures().values()) {
415  if (earliest == null || dep.getDepartureTime() < earliest.getDepartureTime()) {
416  earliest = dep;
417  }
418  }
419  return earliest;
420  }
421 
422  private static Departure getLatestDeparture(TransitRoute route) {
423  Departure latest = null;
424  for (Departure dep : route.getDepartures().values()) {
425  if (latest == null || dep.getDepartureTime() > latest.getDepartureTime()) {
426  latest = dep;
427  }
428  }
429  return latest;
430  }
431 
432  private static boolean toStopIsPartOfRouteButNotSame(RRouteStop fromRouteStop, RRouteStop toRouteStop) {
433  TransitStopFacility fromStopFacility = fromRouteStop.routeStop.getStopFacility();
434  TransitStopFacility toStopFacility = toRouteStop.routeStop.getStopFacility();
435  if (fromStopFacility == toStopFacility) {
436  return false;
437  }
438  for (TransitRouteStop routeStop : fromRouteStop.route.getStops()) {
439  fromStopFacility = routeStop.getStopFacility();
440  if (fromStopFacility == toStopFacility) {
441  return true;
442  }
443  }
444  return false;
445  }
446 
447  private static boolean cannotReachAdditionalStops(RRouteStop fromRouteStop, RRouteStop toRouteStop) {
448  Iterator<TransitRouteStop> fromIter = fromRouteStop.route.getStops().iterator();
449  while (fromIter.hasNext()) {
450  TransitRouteStop routeStop = fromIter.next();
451  if (fromRouteStop.routeStop == routeStop) {
452  break;
453  }
454  }
455  Iterator<TransitRouteStop> toIter = toRouteStop.route.getStops().iterator();
456  while (toIter.hasNext()) {
457  TransitRouteStop routeStop = toIter.next();
458  if (toRouteStop.routeStop == routeStop) {
459  break;
460  }
461  }
462  // both iterators now point to the route stops where the potential transfer happens
463  while (true) {
464  boolean fromRouteHasNext = fromIter.hasNext();
465  boolean toRouteHasNext = toIter.hasNext();
466  if (!toRouteHasNext) {
467  // there are no more stops in the toRoute
468  return true;
469  }
470  if (!fromRouteHasNext) {
471  // there are no more stops in the fromRoute, but there are in the toRoute
472  return false;
473  }
474  TransitRouteStop fromStop = fromIter.next();
475  TransitRouteStop toStop = toIter.next();
476  if (fromStop.getStopFacility() != toStop.getStopFacility()) {
477  // the toRoute goes to a different stop
478  return false;
479  }
480  }
481  }
482 
483  private static boolean couldHaveTransferredOneStopEarlierInOppositeDirection(RRouteStop fromRouteStop, RRouteStop toRouteStop, double maxBeelineWalkConnectionDistance) {
484  TransitRouteStop previousRouteStop = null;
485  for (TransitRouteStop routeStop : fromRouteStop.route.getStops()) {
486  if (fromRouteStop.routeStop == routeStop) {
487  break;
488  }
489  previousRouteStop = routeStop;
490  }
491  if (previousRouteStop == null) {
492  return false;
493  }
494 
495  Iterator<TransitRouteStop> toIter = toRouteStop.route.getStops().iterator();
496  while (toIter.hasNext()) {
497  TransitRouteStop routeStop = toIter.next();
498  if (toRouteStop.routeStop == routeStop) {
499  break;
500  }
501  }
502  boolean toRouteHasNext = toIter.hasNext();
503  if (!toRouteHasNext) {
504  return false;
505  }
506 
507  TransitRouteStop toStop = toIter.next();
508  if (previousRouteStop.getStopFacility() == toStop.getStopFacility()) {
509  return true;
510  }
511 
512  double distance = CoordUtils.calcEuclideanDistance(previousRouteStop.getStopFacility().getCoord(), toStop.getStopFacility().getCoord());
513  return distance < maxBeelineWalkConnectionDistance;
514  }
515 
516  public Collection<TransitStopFacility> findNearbyStops(double x, double y, double distance) {
517  return this.stopsQT.getDisk(x, y, distance);
518  }
519 
520  public TransitStopFacility findNearestStop(double x, double y) {
521  return this.stopsQT.getClosest(x, y);
522  }
523 
531  CachingTransferProvider transferProvider = provider;
532  if (transferProvider == null) {
533  transferProvider = new CachingTransferProvider();
534  }
535  transferProvider.reset(transfer);
536  return transferProvider;
537  }
538 
539  static final class RRoute {
540  final int indexFirstRouteStop;
541  final int countRouteStops;
542  final int indexFirstDeparture;
543  final int countDepartures;
544 
545  RRoute(int indexFirstRouteStop, int countRouteStops, int indexFirstDeparture, int countDepartures) {
546  this.indexFirstRouteStop = indexFirstRouteStop;
547  this.countRouteStops = countRouteStops;
548  this.indexFirstDeparture = indexFirstDeparture;
549  this.countDepartures = countDepartures;
550  }
551  }
552 
553  static final class RRouteStop {
554  final int index;
555  final TransitRouteStop routeStop;
556  final TransitLine line;
557  final TransitRoute route;
558  final String mode;
559  final int transitRouteIndex;
560  final int stopFacilityIndex;
561  final int arrivalOffset;
562  final int departureOffset;
563  final double distanceAlongRoute;
564  int indexFirstTransfer = -1;
565  int countTransfers = 0;
566 
567  RRouteStop(int index, TransitRouteStop routeStop, TransitLine line, TransitRoute route, String mode, int transitRouteIndex, int stopFacilityIndex, double distanceAlongRoute) {
568  this.index = index;
569  this.routeStop = routeStop;
570  this.line = line;
571  this.route = route;
572  this.mode = mode;
573  this.transitRouteIndex = transitRouteIndex;
574  this.stopFacilityIndex = stopFacilityIndex;
575  this.distanceAlongRoute = distanceAlongRoute;
576  // "normalize" the arrival and departure offsets, make sure they are always well defined.
577  this.arrivalOffset = (int) routeStop.getArrivalOffset().or(routeStop::getDepartureOffset).seconds();
578  this.departureOffset = (int) routeStop.getDepartureOffset().or(routeStop::getArrivalOffset).seconds();
579  }
580  }
581 
582  public static final class RTransfer {
583  final int fromRouteStop;
584  final int toRouteStop;
585  final int transferTime;
586  final int transferDistance;
587 
588  RTransfer(int fromRouteStop, int toRouteStop, double transferTime, double transferDistance) {
589  this.fromRouteStop = fromRouteStop;
590  this.toRouteStop = toRouteStop;
591  this.transferTime = (int) Math.ceil(transferTime);
592  this.transferDistance = (int) Math.ceil(transferDistance);
593  }
594  }
595 
596  /*
597  * synchronized in order to avoid that multiple quad trees for the very same stop filter attribute/value combination are prepared at the same time
598  */
599  public synchronized void prepareStopFilterQuadTreeIfNotExistent(String stopFilterAttribute, String stopFilterValue) {
600  // if stopFilterAttribute/stopFilterValue combination exists
601  // we do not have to do anything
602  Map<String, QuadTree<TransitStopFacility>> filteredQTs =
603  this.stopFilterAttribute2Value2StopsQT.computeIfAbsent(stopFilterAttribute, key -> new HashMap<>());
604  if (filteredQTs.containsKey(stopFilterValue))
605  return;
606 
607  Set<TransitStopFacility> stops = routeStopsPerStopFacility.keySet();
608  QuadTree<TransitStopFacility> stopsQTFiltered = new QuadTree<>(stopsQT.getMinEasting(), stopsQT.getMinNorthing(), stopsQT.getMaxEasting(), stopsQT.getMaxNorthing());
609  for (TransitStopFacility stopFacility : stops) {
610  Object attr = stopFacility.getAttributes().getAttribute(stopFilterAttribute);
611  String attrValue = attr == null ? null : attr.toString();
612  if (stopFilterValue.equals(attrValue)) {
613  double x = stopFacility.getCoord().getX();
614  double y = stopFacility.getCoord().getY();
615  stopsQTFiltered.put(x, y, stopFacility);
616  }
617  }
618  filteredQTs.put(stopFilterValue, stopsQTFiltered);
619  }
620 
621  public class CachingTransferProvider implements Supplier<Transfer> {
622 
623  private RTransfer raptorTransfer = null;
624  private final Transfer transfer = new Transfer();
625 
627  }
628 
629  void reset(RTransfer raptorTransfer) {
630  this.raptorTransfer = raptorTransfer;
631  }
632 
633  @Override
634  public Transfer get() {
635  if (this.transfer.rTransfer != this.raptorTransfer) {
636  RRouteStop fromStop = SwissRailRaptorData.this.routeStops[this.raptorTransfer.fromRouteStop];
637  RRouteStop toStop = SwissRailRaptorData.this.routeStops[this.raptorTransfer.toRouteStop];
638  this.transfer.reset(this.raptorTransfer, fromStop, toStop);
639  }
640  return this.transfer;
641  }
642  }
643 
644  RTransfer[] calculateTransfers(RRouteStop fromRouteStop) {
645  // We tested this in a parallel set-up and things seem to work as they are
646  // implemented. The routing threads will access the cache as read-only an
647  // retrieve the cached stop connections. It can happen that two of them try to
648  // obtain a non-existent entry at the same time. In that case, the calculation
649  // is performed twice, but this is not critical. Then this function writes the
650  // connections into the cache. This is a replacement of one address in the array
651  // from null to a concrete value, and our tests show that this seems to appear
652  // as atomic to the using threads. However, it is not 100% excluded that there
653  // is some parallelization issue here and that we rather should shield the cache
654  // using a lock in some way. But so far, we didn't experience any problem. /sh
655  // may 2024
656 
657  RTransfer[] cache = transferCache[fromRouteStop.index];
658  if (cache != null) return cache; // we had a cache hit
659 
660  // setting up useful constants
661  final double minimalTransferTime = config.getMinimalTransferTime();
662  final double beelineWalkConnectionDistance = config.getBeelineWalkConnectionDistance();
663  final double beelineDistanceFactor = config.getBeelineWalkDistanceFactor();
664  final double beelineWalkSpeed = config.getBeelineWalkSpeed();
665  final RaptorOptimization optimization = config.getOptimization();
666 
667  // the facility from which we want to transfer
668  TransitStopFacility fromRouteFacility = fromRouteStop.routeStop.getStopFacility();
669  Collection<TransitStopFacility> transferCandidates = new LinkedList<>();
670 
671  // find transfer candidates by distance
672  transferCandidates.addAll(stopsQT.getDisk(fromRouteFacility.getCoord().getX(), fromRouteFacility.getCoord().getY(), config.getBeelineWalkConnectionDistance()));
673 
674  // find transfer candidates with predefined transfer time
675  Map<TransitStopFacility, Double> transferTimes = staticTransferTimes.get(fromRouteFacility.getId());
676 
677  if (transferTimes != null) { // some transfer times are predefined
678  transferCandidates.addAll(transferTimes.keySet());
679  }
680 
681  // now evaluate whether transfers are useful, distance, and travel time
682  List<RTransfer> transfers = new LinkedList<>();
683  for (TransitStopFacility toRouteFacility : transferCandidates) {
684  for (int toRouteStopIndex : routeStopsPerStopFacility.get(toRouteFacility)) {
685  RRouteStop toRouteStop = routeStops[toRouteStopIndex];
686 
687  double beelineDistance = CoordUtils.calcEuclideanDistance(fromRouteFacility.getCoord(), toRouteFacility.getCoord());
688  double transferTime = beelineDistance / beelineWalkSpeed;
689 
690  if (transferTime < minimalTransferTime) {
691  transferTime = minimalTransferTime;
692  }
693 
694  if (transferTimes != null) {
695  // check if we find a predefined transfer time
696  transferTime = transferTimes.getOrDefault(toRouteFacility, transferTime);
697  }
698 
699  if (SwissRailRaptorData.isUsefulTransfer(fromRouteStop, toRouteStop, beelineWalkConnectionDistance, optimization)) {
700  transfers.add(new RTransfer(fromRouteStop.index, toRouteStop.index, transferTime, beelineDistance * beelineDistanceFactor));
701  }
702  }
703  }
704 
705  // convert to array
706  RTransfer[] stopTransfers = transfers.toArray(new RTransfer[transfers.size()]);
707 
708  // save to cache (no issue regarding parallel execution because we simply set an element)
709  transferCache[fromRouteStop.index] = stopTransfers;
710  return stopTransfers;
711  }
712 }
synchronized void prepareStopFilterQuadTreeIfNotExistent(String stopFilterAttribute, String stopFilterValue)
static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Vehicles transitVehicles, RaptorStaticConfig staticConfig, Network network, OccupancyData occupancyData)
Map< Id< TransitStopFacility >, TransitStopFacility > getFacilities()
static double calcEuclideanDistance(Coord coord, Coord other)
static boolean isTransferAllowed(RRouteStop fromRouteStop, RRouteStop toRouteStop)
double get(Id< TransitStopFacility > fromStop, Id< TransitStopFacility > toStop)
static boolean cannotReachAdditionalStops(RRouteStop fromRouteStop, RRouteStop toRouteStop)
static boolean isFirstStopInRoute(RRouteStop routeStop)
TransitStopFacility findNearestStop(double x, double y)
static boolean isLastStopInRoute(RRouteStop routeStop)
static boolean isUsefulTransfer(RRouteStop fromRouteStop, RRouteStop toRouteStop, double maxBeelineWalkConnectionDistance, RaptorStaticConfig.RaptorOptimization optimization)
static QuadTree< TransitStopFacility > createQuadTreeOfTransitStopFacilities(TransitSchedule transitSchedule)
static boolean toStopIsPartOfRouteButNotSame(RRouteStop fromRouteStop, RRouteStop toRouteStop)
SwissRailRaptorData(RaptorStaticConfig config, int countStops, RRoute[] routes, int[] departures, Vehicle[] departureVehicles, Id< Departure >[] departureIds, RRouteStop[] routeStops, RTransfer[] transfers, Map< TransitStopFacility, Integer > stopFacilityIndices, Map< TransitStopFacility, int[]> routeStopsPerStopFacility, QuadTree< TransitStopFacility > stopsQT, OccupancyData occupancyData, IdMap< TransitStopFacility, Map< TransitStopFacility, Double >> staticTransferTimes)
static boolean couldHaveTransferredOneStopEarlierInOppositeDirection(RRouteStop fromRouteStop, RRouteStop toRouteStop, double maxBeelineWalkConnectionDistance)
Collection< TransitStopFacility > findNearbyStops(double x, double y, double distance)
boolean put(final double x, final double y, final T value)
Definition: QuadTree.java:86
Map< Id< Departure >, Departure > getDepartures()
static Departure getEarliestDeparture(TransitRoute route)
static Map< Integer, RTransfer[]> calculateRouteStopTransfers(TransitSchedule schedule, QuadTree< TransitStopFacility > stopsQT, Map< TransitStopFacility, int[]> routeStopsPerStopFacility, RRouteStop[] routeStops, RaptorStaticConfig config)
T getClosest(final double x, final double y)
Definition: QuadTree.java:130
CachingTransferProvider getTransferProvider(RTransfer transfer, CachingTransferProvider provider)
Map< Id< Link >, ? extends Link > getLinks()
abstract TransitStopFacility getStopFacility()
static Departure getLatestDeparture(TransitRoute route)
static boolean hasNoPossibleDeparture(RRouteStop fromRouteStop, RRouteStop toRouteStop)
Collection< T > getDisk(final double x, final double y, final double distance)
Definition: QuadTree.java:142
V put(Id< T > key, V value)
Definition: IdMap.java:141
Map< Id< TransitLine >, TransitLine > getTransitLines()
OptionalTime or(OptionalTime optionalTime)