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

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

There is a newer version: 2.6.0
Show 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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.graph_builder.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.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.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.EntityById;
import org.opentripplanner.transit.model.framework.FeedScopedId;
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.service.StopModel;
import org.opentripplanner.transit.service.StopModelBuilder;
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 EntityById<>();

  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 StopModelBuilder stopModelBuilder;

  private final Multimap noticeAssignments = ArrayListMultimap.create();

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

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

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

  private final Multimap shapePoints = ArrayListMultimap.create();

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

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

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

  private final TripStopTimes stopTimesByTrip = new TripStopTimes();

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

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

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

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

  private final Multimap tripPatterns = ArrayListMultimap.create();

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

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

  private final Multimap groupsOfRoutesByRouteId = ArrayListMultimap.create();

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

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

  private final DataImportIssueStore issueStore;

  public OtpTransitServiceBuilder(DataImportIssueStore issueStore) {
    this.stopModelBuilder = StopModel.of();
    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 StopModelBuilder stopModelBuilder() {
    return stopModelBuilder;
  }

  public EntityById getGroupsOfStationsById() {
    return stopModelBuilder().groupOfStationById();
  }

  public EntityById getMultiModalStationsById() {
    return stopModelBuilder().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 EntityById getStations() {
    return stopModelBuilder().stationById();
  }

  public EntityById getStops() {
    return stopModelBuilder().regularStopsById();
  }

  public EntityById getEntrances() {
    return entrancesById;
  }

  public EntityById getPathwayNodes() {
    return pathwayNodesById;
  }

  public EntityById getBoardingAreas() {
    return boardingAreasById;
  }

  public EntityById getAreaStops() {
    return stopModelBuilder().areaStopById();
  }

  public EntityById getGroupStops() {
    return stopModelBuilder().groupStopById();
  }

  public TripStopTimes getStopTimesSortedByTrip() {
    return stopTimesByTrip;
  }

  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()
    );
  }

  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.");
  }

  /**
   * 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<>();

    for (Map.Entry e : tripPatterns.entries()) {
      TripPattern ptn = e.getValue();
      ptn.removeTrips(t -> !tripsById.containsKey(t.getId()));
      if (ptn.scheduledTripsAsStream().findAny().isEmpty()) {
        removePatterns.add(e);
      }
    }
    for (Map.Entry it : removePatterns) {
      tripPatterns.remove(it.getKey(), it.getValue());
      issueStore.add(
        "RemovedEmptyTripPattern",
        "Removed trip pattern %s as it contains no trips",
        it.getValue().getId()
      );
    }
    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 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