All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.ext.siri.SiriFuzzyTripMatcher Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.ext.siri;

import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opentripplanner.transit.model.basic.SubMode;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.model.timetable.TripTimes;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.util.time.ServiceDateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.org.siri.siri20.EstimatedCall;
import uk.org.siri.siri20.EstimatedVehicleJourney;
import uk.org.siri.siri20.MonitoredVehicleJourneyStructure;
import uk.org.siri.siri20.RecordedCall;
import uk.org.siri.siri20.VehicleModesEnumeration;

/**
 * This class is used for matching TripDescriptors without trip_ids to scheduled GTFS data and to
 * feed back that information into a new TripDescriptor with proper trip_id.
 * 

* It is mainly written for Entur (Norway) data, which doesn't yet have complete ID-based matching * of SIRI messages to transit model objects. But it may be easily adaptable to other locations, as * it's mainly looking at the last stop and arrival times of the scheduled trip. The matching * process will always be applied even in places where you have good quality IDs in SIRI data and * don't need it - we'd have to add a way to disable it. *

* The same instance of this SiriFuzzyTripMatcher may appear in different SIRI updaters. Be sure * to fetch the instance at during the setup of the updaters, the initialization is not thread-safe. */ public class SiriFuzzyTripMatcher { private static final Logger LOG = LoggerFactory.getLogger(SiriFuzzyTripMatcher.class); private static SiriFuzzyTripMatcher instance; private final Map> internalPlanningCodeCache = new HashMap<>(); private final Map> startStopTripCache = new HashMap<>(); private final TransitService transitService; private boolean initialized = false; /** * Factory method used to create only one instance. *

* THIS METHOD IS NOT THREAD-SAFE AND SHOULD BE CALLED DURING THE * INITIALIZATION PROCESS. */ public static SiriFuzzyTripMatcher of(TransitService transitService) { if (instance == null) { instance = new SiriFuzzyTripMatcher(transitService); } return instance; } private SiriFuzzyTripMatcher(TransitService transitService) { this.transitService = transitService; initCache(this.transitService); } /** * Matches VehicleActivity to a set of possible Trips based on tripId */ public Set match(MonitoredVehicleJourneyStructure monitoredVehicleJourney, String feedId) { if (monitoredVehicleJourney != null) { if (monitoredVehicleJourney.getFramedVehicleJourneyRef() != null) { String datedVehicleRef = monitoredVehicleJourney .getFramedVehicleJourneyRef() .getDatedVehicleJourneyRef(); if (datedVehicleRef != null) { Trip trip = transitService.getTripForId(new FeedScopedId(feedId, datedVehicleRef)); if (trip != null) { return Set.of(trip); } } } if (monitoredVehicleJourney.getDestinationRef() != null) { String destinationRef = monitoredVehicleJourney.getDestinationRef().getValue(); ZonedDateTime arrivalTime = monitoredVehicleJourney.getDestinationAimedArrivalTime(); if (arrivalTime != null) { return getMatchingTripsOnStopOrSiblings(destinationRef, feedId, arrivalTime); } } } return Set.of(); } /** * Matches EstimatedVehicleJourney to a set of possible Trips based on tripId */ public Set match(EstimatedVehicleJourney journey, String feedId) { Set trips = null; if ( journey.getVehicleRef() != null && ( journey.getVehicleModes() != null && journey.getVehicleModes().contains(VehicleModesEnumeration.RAIL) ) ) { trips = getCachedTripsByInternalPlanningCode(journey.getVehicleRef().getValue()); } if (trips == null || trips.isEmpty()) { String serviceJourneyId = resolveDatedVehicleJourneyRef(journey); if (serviceJourneyId != null) { Trip trip = transitService.getTripForId(new FeedScopedId(feedId, serviceJourneyId)); if (trip != null) { trips = Set.of(trip); } } } if (trips == null || trips.isEmpty()) { String lastStopPoint = null; ZonedDateTime arrivalTime = null; if ( journey.getEstimatedCalls() != null && journey.getEstimatedCalls().getEstimatedCalls() != null && !journey.getEstimatedCalls().getEstimatedCalls().isEmpty() ) { // Pick last stop from EstimatedCalls List estimatedCalls = journey.getEstimatedCalls().getEstimatedCalls(); EstimatedCall lastStop = estimatedCalls.get(estimatedCalls.size() - 1); lastStopPoint = lastStop.getStopPointRef().getValue(); arrivalTime = lastStop.getAimedArrivalTime() != null ? lastStop.getAimedArrivalTime() : lastStop.getAimedDepartureTime(); } else if ( journey.getRecordedCalls() != null && journey.getRecordedCalls().getRecordedCalls() != null && !journey.getRecordedCalls().getRecordedCalls().isEmpty() ) { // No EstimatedCalls exist - pick last RecordedCall List recordedCalls = journey.getRecordedCalls().getRecordedCalls(); final RecordedCall lastStop = recordedCalls.get(recordedCalls.size() - 1); lastStopPoint = lastStop.getStopPointRef().getValue(); arrivalTime = lastStop.getAimedArrivalTime() != null ? lastStop.getAimedArrivalTime() : lastStop.getAimedDepartureTime(); } if (arrivalTime != null) { trips = getMatchingTripsOnStopOrSiblings(lastStopPoint, feedId, arrivalTime); } } return trips; } /** * Returns a match of tripIds that match the provided values. */ public List getTripIdForInternalPlanningCodeServiceDateAndMode( String internalPlanningCode, LocalDate serviceDate, TransitMode mode, SubMode transportSubmode ) { Set cachedTripsBySiriId = getCachedTripsByInternalPlanningCode(internalPlanningCode); List matches = new ArrayList<>(); for (Trip trip : cachedTripsBySiriId) { final TripPattern tripPattern = transitService.getPatternForTrip(trip); if (tripPattern.matchesModeOrSubMode(mode, transportSubmode)) { Set serviceDates = transitService .getCalendarService() .getServiceDatesForServiceId(trip.getServiceId()); if ( serviceDates.contains(serviceDate) && trip.getNetexInternalPlanningCode() != null && trip.getNetexInternalPlanningCode().equals(internalPlanningCode) ) { matches.add(trip.getId()); } } } return matches; } static Trip findTripByDatedVehicleJourneyRef( EstimatedVehicleJourney journey, String feedId, TransitService transitService ) { String serviceJourneyId = resolveDatedVehicleJourneyRef(journey); if (serviceJourneyId != null) { Trip trip = transitService.getTripForId(new FeedScopedId(feedId, serviceJourneyId)); if (trip != null) { return trip; } else { //Attempt to find trip using datedServiceJourneyId TripOnServiceDate tripOnServiceDate = transitService.getTripOnServiceDateById( new FeedScopedId(feedId, serviceJourneyId) ); if (tripOnServiceDate != null) { return tripOnServiceDate.getTrip(); } } } return null; } private void initCache(TransitService index) { if (!initialized) { for (Trip trip : index.getAllTrips()) { TripPattern tripPattern = index.getPatternForTrip(trip); if (tripPattern == null) { continue; } if (tripPattern.matchesModeOrSubMode(TransitMode.RAIL, SubMode.of("railReplacementBus"))) { if (trip.getNetexInternalPlanningCode() != null) { String internalPlanningCode = trip.getNetexInternalPlanningCode(); if (internalPlanningCodeCache.containsKey(internalPlanningCode)) { internalPlanningCodeCache.get(internalPlanningCode).add(trip); } else { Set initialSet = new HashSet<>(); initialSet.add(trip); internalPlanningCodeCache.put(internalPlanningCode, initialSet); } } } String lastStopId = tripPattern.lastStop().getId().getId(); TripTimes tripTimes = tripPattern.getScheduledTimetable().getTripTimes(trip); if (tripTimes != null) { int arrivalTime = tripTimes.getArrivalTime(tripTimes.getNumStops() - 1); String key = createStartStopKey(lastStopId, arrivalTime); if (startStopTripCache.containsKey(key)) { startStopTripCache.get(key).add(trip); } else { Set initialSet = new HashSet<>(); initialSet.add(trip); startStopTripCache.put(key, initialSet); } } } LOG.info("Built internalPlanningCode-cache [{}].", internalPlanningCodeCache.size()); LOG.info("Built start-stop-cache [{}].", startStopTripCache.size()); } initialized = true; } private static String createStartStopKey(String lastStopId, int lastStopArrivalTime) { return lastStopId + ":" + lastStopArrivalTime; } private static String resolveDatedVehicleJourneyRef(EstimatedVehicleJourney journey) { if (journey.getFramedVehicleJourneyRef() != null) { return journey.getFramedVehicleJourneyRef().getDatedVehicleJourneyRef(); } else if (journey.getDatedVehicleJourneyRef() != null) { return journey.getDatedVehicleJourneyRef().getValue(); } return null; } private Set getMatchingTripsOnStopOrSiblings( String lastStopPoint, String feedId, ZonedDateTime arrivalTime ) { int secondsSinceMidnight = ServiceDateUtils.secondsSinceStartOfService( arrivalTime, arrivalTime, transitService.getTimeZone() ); int secondsSinceMidnightYesterday = ServiceDateUtils.secondsSinceStartOfService( arrivalTime.minusDays(1), arrivalTime, transitService.getTimeZone() ); Set trips = startStopTripCache.get( createStartStopKey(lastStopPoint, secondsSinceMidnight) ); if (trips == null) { //Attempt to fetch trips that started yesterday - i.e. add 24 hours to arrival-time trips = startStopTripCache.get(createStartStopKey(lastStopPoint, secondsSinceMidnightYesterday)); } if (trips == null || trips.isEmpty()) { //SIRI-data may report other platform, but still on the same Parent-stop var stop = transitService.getRegularStop(new FeedScopedId(feedId, lastStopPoint)); if (stop != null && stop.isPartOfStation()) { // TODO OTP2 resolve stop-station split var allQuays = stop.getParentStation().getChildStops(); for (var quay : allQuays) { Set tripSet = startStopTripCache.get( createStartStopKey(quay.getId().getId(), secondsSinceMidnight) ); if (tripSet != null) { if (trips == null) { trips = tripSet; } else { trips.addAll(tripSet); } } } } } return trips; } private Set getCachedTripsByInternalPlanningCode(String vehicleRef) { if (vehicleRef == null) { return null; } return internalPlanningCodeCache.getOrDefault(vehicleRef, new HashSet<>()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy