org.opentripplanner.transit.service.TransitModel Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.transit.service;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import gnu.trove.set.hash.TIntHashSet;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.graph_builder.DataImportIssueStore;
import org.opentripplanner.graph_builder.issues.NoFutureDates;
import org.opentripplanner.model.FeedInfo;
import org.opentripplanner.model.PathTransfer;
import org.opentripplanner.model.TimetableSnapshot;
import org.opentripplanner.model.TimetableSnapshotProvider;
import org.opentripplanner.model.calendar.CalendarService;
import org.opentripplanner.model.calendar.CalendarServiceData;
import org.opentripplanner.model.calendar.impl.CalendarServiceImpl;
import org.opentripplanner.model.transfer.DefaultTransferService;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.TransitLayerUpdater;
import org.opentripplanner.routing.impl.DelegatingTransitAlertServiceImpl;
import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.util.ConcurrentPublished;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.organization.Operator;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.updater.GraphUpdaterManager;
import org.opentripplanner.updater.configure.UpdaterConfigurator;
import org.opentripplanner.util.lang.ObjectUtils;
import org.opentripplanner.util.time.ServiceDateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Repository for Transit entities.
*/
public class TransitModel implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(TransitModel.class);
private final Collection agencies = new ArrayList<>();
private final Collection operators = new ArrayList<>();
private final Collection feedIds = new HashSet<>();
private final Map feedInfoForId = new HashMap<>();
private final Multimap noticesByElement = HashMultimap.create();
private final DefaultTransferService transferService = new DefaultTransferService();
private final HashSet transitModes = new HashSet<>();
private final Map serviceCodes = new HashMap<>();
private final Multimap transfersByStop = HashMultimap.create();
private StopModel stopModel;
private ZonedDateTime transitServiceStarts = LocalDate.MAX.atStartOfDay(ZoneId.systemDefault());
private ZonedDateTime transitServiceEnds = LocalDate.MIN.atStartOfDay(ZoneId.systemDefault());
private final transient ConcurrentPublished realtimeTransitLayer = new ConcurrentPublished<>();
private final transient Deduplicator deduplicator;
private final CalendarServiceData calendarServiceData = new CalendarServiceData();
private transient TransitModelIndex index;
private transient TimetableSnapshotProvider timetableSnapshotProvider = null;
private ZoneId timeZone = null;
private boolean timeZoneExplicitlySet = false;
private transient GraphUpdaterManager updaterManager = null;
private boolean hasTransit = false;
private boolean hasFrequencyService = false;
private boolean hasScheduledService = false;
private final Map tripPatternForId = new HashMap<>();
private final Map tripOnServiceDates = new HashMap<>();
private final Map> flexTripsById = new HashMap<>();
private transient TransitLayer transitLayer;
private transient TransitLayerUpdater transitLayerUpdater;
private transient TransitAlertService transitAlertService;
@Inject
public TransitModel(StopModel stopModel, Deduplicator deduplicator) {
this.stopModel = Objects.requireNonNull(stopModel);
this.deduplicator = deduplicator;
}
/** Constructor for deserialization. */
public TransitModel() {
this(new StopModel(), new Deduplicator());
}
/**
* Perform indexing on timetables, and create transient data structures. This used to be done in
* readObject methods upon deserialization, but stand-alone mode now allows passing graphs from
* graphbuilder to server in memory, without a round trip through serialization.
*/
public void index() {
if (index == null) {
LOG.info("Index transit model...");
// the transit model indexing updates the stop model index (flex stops added to the stop index)
this.index = new TransitModelIndex(this);
LOG.info("Index transit model complete.");
}
}
public TimetableSnapshot getTimetableSnapshot() {
return timetableSnapshotProvider == null
? null
: timetableSnapshotProvider.getTimetableSnapshot();
}
public void initTimetableSnapshotProvider(TimetableSnapshotProvider timetableSnapshotProvider) {
if (this.timetableSnapshotProvider != null) {
throw new IllegalArgumentException(
"We support only one timetableSnapshotSource, there are two implementation; one for " +
"GTFS and one for Netex/Siri. They need to be refactored to work together. This cast " +
"will fail if updaters try setup both."
);
}
this.timetableSnapshotProvider = timetableSnapshotProvider;
}
/** Data model for Raptor routing, with realtime updates applied (if any). */
public TransitLayer getTransitLayer() {
return transitLayer;
}
public void setTransitLayer(TransitLayer transitLayer) {
this.transitLayer = transitLayer;
}
/** Data model for Raptor routing, with realtime updates applied (if any). */
public TransitLayer getRealtimeTransitLayer() {
return realtimeTransitLayer.get();
}
public void setRealtimeTransitLayer(TransitLayer realtimeTransitLayer) {
this.realtimeTransitLayer.publish(realtimeTransitLayer);
}
public boolean hasRealtimeTransitLayer() {
return realtimeTransitLayer != null;
}
public DefaultTransferService getTransferService() {
return transferService;
}
// Check to see if we have transit information for a given date
public boolean transitFeedCovers(Instant time) {
return (
!time.isBefore(this.transitServiceStarts.toInstant()) &&
time.isBefore(this.transitServiceEnds.toInstant())
);
}
/**
* Adds mode of transport to transit modes in graph
*/
public void addTransitMode(TransitMode mode) {
invalidateIndex();
transitModes.add(mode);
}
/** List of transit modes that are availible in GTFS data used in this graph **/
public HashSet getTransitModes() {
return transitModes;
}
public CalendarService getCalendarService() {
// No need to cache the CalendarService, it is a thin wrapper around the data
return new CalendarServiceImpl(calendarServiceData);
}
public void updateCalendarServiceData(
boolean hasActiveTransit,
CalendarServiceData data,
DataImportIssueStore issueStore
) {
invalidateIndex();
updateTransitFeedValidity(data, issueStore);
calendarServiceData.add(data);
updateHasTransit(hasActiveTransit);
}
/**
* Get or create a serviceId for a given date. This method is used when a new trip is added from a
* realtime data update. It make sure the date is in the existing transit service period.
*
* TODO OTP2 - This is NOT THREAD-SAFE and is used in the real-time updaters, we need to fix
* - this when doing the issue #3030.
*
* @param serviceDate service date for the added service id
* @return service-id for date if it exist or is created. If the given service date is outside the
* service period {@code null} is returned.
*/
@Nullable
public FeedScopedId getOrCreateServiceIdForDate(LocalDate serviceDate) {
// Start of day
ZonedDateTime time = ServiceDateUtils.asStartOfService(serviceDate, getTimeZone());
if (!transitFeedCovers(time.toInstant())) {
return null;
}
// We make an explicit cast here to avoid adding the 'getOrCreateServiceIdForDate(..)'
// method to the {@link CalendarService} interface. We do not want to expose it because it
// is not thread-safe - and we want to limit the usage. See JavaDoc above as well.
FeedScopedId serviceId =
((CalendarServiceImpl) getCalendarService()).getOrCreateServiceIdForDate(serviceDate);
if (!serviceCodes.containsKey(serviceId)) {
// Calculating new unique serviceCode based on size (!)
final int serviceCode = serviceCodes.size();
serviceCodes.put(serviceId, serviceCode);
index
.getServiceCodesRunningForDate()
.computeIfAbsent(serviceDate, ignored -> new TIntHashSet())
.add(serviceCode);
}
return serviceId;
}
public Collection getFeedIds() {
return feedIds;
}
public Collection getAgencies() {
return agencies;
}
public FeedInfo getFeedInfo(String feedId) {
return feedInfoForId.get(feedId);
}
public void addAgency(Agency agency) {
invalidateIndex();
agencies.add(agency);
this.feedIds.add(agency.getId().getFeedId());
}
public void addFeedInfo(FeedInfo info) {
invalidateIndex();
this.feedInfoForId.put(info.getId(), info);
}
/**
* Returns the time zone for the transit model. This is used to interpret times in API requests.
* Ideally we would want to interpret times in the time zone of the geographic location where the
* origin/destination vertex or board/alight event is located. This may become necessary when we
* start making graphs with long distance train, boat, or air services.
*/
public ZoneId getTimeZone() {
return timeZone;
}
/**
* Initialize the time zone, if it has not been set previously.
*/
public void initTimeZone(ZoneId timeZone) {
if (timeZone == null || timeZone.equals(this.timeZone)) {
return;
}
invalidateIndex();
this.timeZone = ObjectUtils.requireNotInitialized(this.timeZone, timeZone);
this.timeZoneExplicitlySet = true;
}
/**
* Returns the time zone for the transit model. This is either configured in the build config, or
* from the agencies in the data, if they are on the same time zone. This is used to interpret
* times in API requests. Ideally we would want to interpret times in the time zone of the
* geographic location where the origin/destination vertex or board/alight event is located. This
* may become necessary when we start making graphs with long distance train, boat, or air
* services.
*/
public Set getAgencyTimeZones() {
Set ret = new HashSet<>();
for (Agency agency : agencies) {
ret.add(agency.getTimezone());
}
return ret;
}
public Collection getOperators() {
return operators;
}
/**
* OTP doesn't currently support multiple time zones in a single graph, unless explicitly
* configured. Check that the time zone of the added agencies are the same as the current.
* At least this way we catch the error and log it instead of silently ignoring because the
* time zone from the first agency is used
*/
public void validateTimeZones() {
if (!timeZoneExplicitlySet) {
Collection zones = getAgencyTimeZones();
if (zones.size() > 1) {
throw new IllegalStateException(
"The graph contains agencies with different time zones: %s. Please configure the one to be used in the build-config.json".formatted(
zones
)
);
}
}
}
/** transit feed validity information in seconds since epoch */
public ZonedDateTime getTransitServiceStarts() {
return transitServiceStarts;
}
public ZonedDateTime getTransitServiceEnds() {
return transitServiceEnds;
}
/**
* Allows a notice element to be attached to an object in the OTP model by its id and then
* retrieved by the API when navigating from that object. The map key is entity id:
* {@link AbstractTransitEntity#getId()}. The notice is part of the static transit data.
*/
public Multimap getNoticesByElement() {
return noticesByElement;
}
public void addNoticeAssignments(Multimap noticesByElement) {
invalidateIndex();
this.noticesByElement.putAll(noticesByElement);
}
public TransitAlertService getTransitAlertService() {
if (transitAlertService == null) {
transitAlertService = new DelegatingTransitAlertServiceImpl(this);
}
return transitAlertService;
}
public TripPattern getTripPatternForId(FeedScopedId id) {
return tripPatternForId.get(id);
}
public Map getTripOnServiceDates() {
return tripOnServiceDates;
}
/**
* Return a transit stop, a flex stop location or flex stop location group.
*/
public StopLocation getStopLocationById(FeedScopedId id) {
return stopModel.getStopLocation(id);
}
/**
* Map from GTFS ServiceIds to integers close to 0. Allows using BitSets instead of
* {@code Set