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

org.opentripplanner.model.impl.OtpTransitServiceBuilder Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.model.impl;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
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 java.util.stream.Collectors;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.gtfs.mapping.StaySeatedNotAllowed;
import org.opentripplanner.model.FeedInfo;
import org.opentripplanner.model.Frequency;
import org.opentripplanner.model.OtpTransitService;
import org.opentripplanner.model.ShapePoint;
import org.opentripplanner.model.Timetable;
import org.opentripplanner.model.TripStopTimes;
import org.opentripplanner.model.calendar.CalendarServiceData;
import org.opentripplanner.model.calendar.ServiceCalendar;
import org.opentripplanner.model.calendar.ServiceCalendarDate;
import org.opentripplanner.model.calendar.ServiceDateInterval;
import org.opentripplanner.model.calendar.impl.CalendarServiceDataFactoryImpl;
import org.opentripplanner.model.transfer.ConstrainedTransfer;
import org.opentripplanner.model.transfer.TransferPoint;
import org.opentripplanner.routing.api.request.framework.TimePenalty;
import org.opentripplanner.service.vehicleparking.model.VehicleParking;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.DefaultEntityById;
import org.opentripplanner.transit.model.framework.EntityById;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.framework.ImmutableEntityById;
import org.opentripplanner.transit.model.network.GroupOfRoutes;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.network.StopPattern;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.organization.Branding;
import org.opentripplanner.transit.model.organization.Operator;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.model.site.BoardingArea;
import org.opentripplanner.transit.model.site.Entrance;
import org.opentripplanner.transit.model.site.FareZone;
import org.opentripplanner.transit.model.site.GroupOfStations;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.MultiModalStation;
import org.opentripplanner.transit.model.site.Pathway;
import org.opentripplanner.transit.model.site.PathwayNode;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
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.SiteRepository;
import org.opentripplanner.transit.service.SiteRepositoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is responsible for building a {@link OtpTransitService}. The instance returned by the
 * {@link #build()} method is read only, and this class provide a mutable collections to construct a
 * {@link OtpTransitService} instance.
 */
public class OtpTransitServiceBuilder {

  private static final Logger LOG = LoggerFactory.getLogger(OtpTransitServiceBuilder.class);

  private final EntityById agenciesById = new DefaultEntityById<>();

  private final List calendarDates = new ArrayList<>();

  private final List calendars = new ArrayList<>();

  private final List feedInfos = new ArrayList<>();

  private final List frequencies = new ArrayList<>();

  private final SiteRepositoryBuilder siteRepositoryBuilder;

  private final Multimap noticeAssignments =
    ArrayListMultimap.create();

  private final EntityById operatorsById = new DefaultEntityById<>();

  private final List pathways = new ArrayList<>();

  private final EntityById routesById = new DefaultEntityById<>();

  private final Multimap shapePoints = ArrayListMultimap.create();

  private final EntityById entrancesById = new DefaultEntityById<>();

  private final EntityById pathwayNodesById = new DefaultEntityById<>();

  private final EntityById boardingAreasById = new DefaultEntityById<>();

  private final TripStopTimes stopTimesByTrip = new TripStopTimes();

  private final Map flexTimePenalties = new HashMap<>();

  private final EntityById fareZonesById = new DefaultEntityById<>();

  private final List transfers = new ArrayList<>();

  private final List staySeatedNotAllowed = new ArrayList<>();

  private final EntityById tripsById = new DefaultEntityById<>();

  private final Multimap tripPatterns = ArrayListMultimap.create();

  private final EntityById> flexTripsById = new DefaultEntityById<>();

  private final EntityById brandingsById = new DefaultEntityById<>();

  private final Multimap groupsOfRoutesByRouteId =
    ArrayListMultimap.create();

  private final EntityById tripOnServiceDates = new DefaultEntityById<>();

  private final EntityById groupOfRouteById = new DefaultEntityById<>();

  private final List vehicleParkings = new ArrayList<>();

  private final Map stopsByScheduledStopPoints = new HashMap<>();

  private final DataImportIssueStore issueStore;

  public OtpTransitServiceBuilder(SiteRepository siteRepository, DataImportIssueStore issueStore) {
    this.siteRepositoryBuilder = siteRepository.withContext();
    this.issueStore = issueStore;
  }

  /* Accessors */

  public EntityById getAgenciesById() {
    return agenciesById;
  }

  public List getCalendarDates() {
    return calendarDates;
  }

  public List getCalendars() {
    return calendars;
  }

  public List getFeedInfos() {
    return feedInfos;
  }

  public List getFrequencies() {
    return frequencies;
  }

  public SiteRepositoryBuilder siteRepository() {
    return siteRepositoryBuilder;
  }

  public ImmutableEntityById getGroupsOfStationsById() {
    return siteRepositoryBuilder.groupOfStationById();
  }

  public ImmutableEntityById getMultiModalStationsById() {
    return siteRepositoryBuilder.multiModalStationById();
  }

  /**
   * get multimap of Notices by the TransitEntity id (Multiple types; hence the Serializable).
   * Entities that might have Notices are Routes, Trips, Stops and StopTimes.
   */
  public Multimap getNoticeAssignments() {
    return noticeAssignments;
  }

  public EntityById getOperatorsById() {
    return operatorsById;
  }

  public List getPathways() {
    return pathways;
  }

  public EntityById getRoutes() {
    return routesById;
  }

  public Multimap getShapePoints() {
    return shapePoints;
  }

  public ImmutableEntityById getStations() {
    return siteRepositoryBuilder.stationById();
  }

  public ImmutableEntityById getStops() {
    return siteRepositoryBuilder.regularStopsById();
  }

  public EntityById getEntrances() {
    return entrancesById;
  }

  public EntityById getPathwayNodes() {
    return pathwayNodesById;
  }

  public EntityById getBoardingAreas() {
    return boardingAreasById;
  }

  public ImmutableEntityById getAreaStops() {
    return siteRepositoryBuilder.areaStopById();
  }

  public ImmutableEntityById getGroupStops() {
    return siteRepositoryBuilder.groupStopById();
  }

  public TripStopTimes getStopTimesSortedByTrip() {
    return stopTimesByTrip;
  }

  public Map getFlexTimePenalty() {
    return flexTimePenalties;
  }

  public EntityById getFareZonesById() {
    return fareZonesById;
  }

  public List getTransfers() {
    return transfers;
  }

  public List getStaySeatedNotAllowed() {
    return staySeatedNotAllowed;
  }

  public EntityById getTripsById() {
    return tripsById;
  }

  public Multimap getTripPatterns() {
    return tripPatterns;
  }

  public EntityById> getFlexTripsById() {
    return flexTripsById;
  }

  public EntityById getBrandingsById() {
    return brandingsById;
  }

  public Multimap getGroupsOfRoutesByRouteId() {
    return groupsOfRoutesByRouteId;
  }

  public EntityById getGroupOfRouteById() {
    return groupOfRouteById;
  }

  public EntityById getTripOnServiceDates() {
    return tripOnServiceDates;
  }

  public CalendarServiceData buildCalendarServiceData() {
    return CalendarServiceDataFactoryImpl.createCalendarServiceData(
      getCalendarDates(),
      getCalendars()
    );
  }

  /**
   * The list of parking lots contained in the transit data (so far only NeTEx).
   * Note that parking lots can also be sourced from OSM data as well as realtime updaters.
   */
  public List vehicleParkings() {
    return vehicleParkings;
  }

  /**
   * @see org.opentripplanner.transit.service.TimetableRepository#findStopByScheduledStopPoint(FeedScopedId)
   */
  public Map stopsByScheduledStopPoints() {
    return stopsByScheduledStopPoints;
  }

  public OtpTransitService build() {
    return new OtpTransitServiceImpl(this);
  }

  /**
   * Limit the transit service to a time period removing calendar dates and services outside the
   * period. If a service is start before and/or ends after the period then the service is modified
   * to match the period.
   */
  public void limitServiceDays(ServiceDateInterval periodLimit) {
    if (periodLimit.isUnbounded()) {
      LOG.info("Limiting transit service is skipped, the period is unbounded.");
      return;
    }

    LOG.warn("Limiting transit service days to time period: {}", periodLimit);

    int orgSize = calendarDates.size();
    calendarDates.removeIf(c -> !periodLimit.include(c.getDate()));
    logRemove("ServiceCalendarDate", orgSize, calendarDates.size(), "Outside time period.");

    List keepCal = new ArrayList<>();
    for (ServiceCalendar calendar : calendars) {
      if (calendar.getPeriod().overlap(periodLimit)) {
        calendar.setPeriod(calendar.getPeriod().intersection(periodLimit));
        keepCal.add(calendar);
      }
    }

    orgSize = calendars.size();
    if (orgSize != keepCal.size()) {
      calendars.clear();
      calendars.addAll(keepCal);
      logRemove("ServiceCalendar", orgSize, calendars.size(), "Outside time period.");
    }
    removeEntitiesWithInvalidReferences();
    LOG.info("Limiting transit service days to time period complete.");
  }

  /**
   * Add a mapping from a scheduled stop point to the regular stop.
   */
  public void addStopByScheduledStopPoint(FeedScopedId sspid, RegularStop stop) {
    stopsByScheduledStopPoints.put(sspid, stop);
  }

  /**
   * Find all serviceIds in both CalendarServices and CalendarServiceDates.
   */
  Set findAllServiceIds() {
    Set serviceIds = new HashSet<>();
    for (ServiceCalendar calendar : getCalendars()) {
      serviceIds.add(calendar.getServiceId());
    }
    for (ServiceCalendarDate date : getCalendarDates()) {
      serviceIds.add(date.getServiceId());
    }
    return serviceIds;
  }

  private static void logRemove(String type, int orgSize, int newSize, String reason) {
    if (orgSize == newSize) {
      return;
    }
    LOG.info("{} of {} {}(s) removed. Reason: {}", orgSize - newSize, orgSize, type, reason);
  }

  /**
   * Check all relations and remove entities which reference none existing entries. This may happen
   * as a result of inconsistent data or by deliberate removal of elements in the builder.
   */
  private void removeEntitiesWithInvalidReferences() {
    removeTripsWithNoneExistingServiceIds();
    removeStopTimesForNoneExistingTrips();
    fixOrRemovePatternsWhichReferenceNoneExistingTrips();
    removeTransfersForNoneExistingTrips();
    removeTripOnServiceDateForNonExistingTrip();
  }

  /** Remove all trips which reference none existing service ids */
  private void removeTripsWithNoneExistingServiceIds() {
    Set serviceIds = findAllServiceIds();
    int orgSize = tripsById.size();
    tripsById.removeIf(
      t -> !serviceIds.contains(t.getServiceId()),
      t ->
        issueStore.add(
          "RemovedMissingServiceIdTrip",
          "Removed trip %s as service id %s does not exist",
          t.getId(),
          t.getServiceId()
        )
    );
    logRemove("Trip", orgSize, tripsById.size(), "Trip service id does not exist.");
  }

  /** Remove all stopTimes which reference none existing trips */
  private void removeStopTimesForNoneExistingTrips() {
    int orgSize = stopTimesByTrip.size();
    stopTimesByTrip.removeIf(t -> !tripsById.containsKey(t.getId()));
    logRemove("StopTime", orgSize, stopTimesByTrip.size(), "StopTime trip does not exist.");
  }

  /** Remove none existing trips from patterns and then remove empty patterns */
  private void fixOrRemovePatternsWhichReferenceNoneExistingTrips() {
    int orgSize = tripPatterns.size();
    List> removePatterns = new ArrayList<>();
    List updatedPatterns = new ArrayList<>();

    for (Map.Entry e : tripPatterns.entries()) {
      TripPattern ptn = e.getValue();
      Set tripTimesToBeRemoved = ptn
        .getScheduledTimetable()
        .getTripTimes()
        .stream()
        .filter(tripTimes -> !tripsById.containsKey(tripTimes.getTrip().getId()))
        .collect(Collectors.toUnmodifiableSet());
      if (!tripTimesToBeRemoved.isEmpty()) {
        removePatterns.add(e);
        Timetable updatedTimetable = ptn
          .getScheduledTimetable()
          .copyOf()
          .removeAllTripTimes(tripTimesToBeRemoved)
          .build();
        TripPattern updatedPattern = ptn.copy().withScheduledTimeTable(updatedTimetable).build();
        if (!updatedTimetable.getTripTimes().isEmpty()) {
          updatedPatterns.add(updatedPattern);
        } else {
          issueStore.add(
            "RemovedEmptyTripPattern",
            "Removed trip pattern %s as it contains no trips",
            updatedPattern.getId()
          );
        }
      }
    }
    for (Map.Entry it : removePatterns) {
      tripPatterns.remove(it.getKey(), it.getValue());
    }
    for (TripPattern tripPattern : updatedPatterns) {
      tripPatterns.put(tripPattern.getStopPattern(), tripPattern);
    }
    logRemove("TripPattern", orgSize, tripPatterns.size(), "No trips for pattern exist.");
  }

  /** Remove all transfers which reference none existing trips */
  private void removeTransfersForNoneExistingTrips() {
    int orgSize = transfers.size();
    transfers.removeIf(this::transferTripReferencesDoNotExist);
    logRemove("Trip", orgSize, transfers.size(), "Transfer to/from trip does not exist.");
  }

  /**
   * Remove TripOnServiceDates if there are no trips using them
   */
  private void removeTripOnServiceDateForNonExistingTrip() {
    int orgSize = tripOnServiceDates.size();
    for (TripOnServiceDate tripOnServiceDate : tripOnServiceDates.values()) {
      if (!tripsById.containsKey(tripOnServiceDate.getTrip().getId())) {
        logRemove(
          "TripOnServiceDate",
          orgSize,
          tripOnServiceDates.size(),
          "Trip for TripOnServiceDate does not exist."
        );
      }
    }
  }

  /** Return {@code true} if the from/to trip reference is none null, but do not exist. */
  private boolean transferTripReferencesDoNotExist(ConstrainedTransfer t) {
    return (
      transferPointTripReferenceDoesNotExist(t.getFrom()) ||
      transferPointTripReferenceDoesNotExist(t.getTo())
    );
  }

  /**
   * Return {@code true} if the point is a trip-transfer-point and the trip reference is
   * missing.
   */
  private boolean transferPointTripReferenceDoesNotExist(TransferPoint point) {
    if (!point.isTripTransferPoint()) {
      return false;
    }
    var trip = point.asTripTransferPoint().getTrip();
    return !tripsById.containsKey(trip.getId());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy