Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow SIRI's StopPointRef to refer to a NeTEx scheduled stop point #6397

Open
wants to merge 6 commits into
base: dev-2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.model.transfer.ConstrainedTransfer;
import org.opentripplanner.transit.model.basic.Notice;
Expand All @@ -15,6 +16,7 @@
import org.opentripplanner.transit.model.site.Entrance;
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.timetable.Trip;
import org.opentripplanner.transit.service.SiteRepository;

Expand Down Expand Up @@ -77,4 +79,9 @@ public interface OtpTransitService {
* transit services if they are outside the configured 'transitServiceStart' and 'transitServiceEnd'
*/
boolean hasActiveTransit();

/**
* @see org.opentripplanner.transit.service.TimetableRepository#findStopByScheduledStopPoint(FeedScopedId)
*/
Map<FeedScopedId, RegularStop> stopsByScheduledStopPoint();
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public class OtpTransitServiceBuilder {

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

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

private final DataImportIssueStore issueStore;

public OtpTransitServiceBuilder(SiteRepository siteRepository, DataImportIssueStore issueStore) {
Expand Down Expand Up @@ -278,6 +280,13 @@ public List<VehicleParking> vehicleParkings() {
return vehicleParkings;
}

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

public OtpTransitService build() {
return new OtpTransitServiceImpl(this);
}
Expand Down Expand Up @@ -317,6 +326,13 @@ public void limitServiceDays(ServiceDateInterval periodLimit) {
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.
*/
Expand Down Expand Up @@ -449,7 +465,7 @@ private boolean transferTripReferencesDoNotExist(ConstrainedTransfer t) {
}

/**
* Return {@code true} if the the point is a trip-transfer-point and the trip reference is
* Return {@code true} if the point is a trip-transfer-point and the trip reference is
* missing.
*/
private boolean transferPointTripReferenceDoesNotExist(TransferPoint point) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.opentripplanner.transit.model.site.Entrance;
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.timetable.Trip;
import org.opentripplanner.transit.service.SiteRepository;

Expand Down Expand Up @@ -70,6 +71,7 @@ class OtpTransitServiceImpl implements OtpTransitService {
private final Collection<Trip> trips;

private final Collection<FlexTrip<?, ?>> flexTrips;
private final Map<FeedScopedId, RegularStop> stopsByScheduledStopPoint;

/**
* Create a read only version of the {@link OtpTransitService}.
Expand All @@ -91,6 +93,8 @@ class OtpTransitServiceImpl implements OtpTransitService {
this.tripPatterns = immutableList(builder.getTripPatterns().values());
this.trips = immutableList(builder.getTripsById().values());
this.flexTrips = immutableList(builder.getFlexTripsById().values());
this.stopsByScheduledStopPoint =
Collections.unmodifiableMap(builder.stopsByScheduledStopPoints());
}

@Override
Expand Down Expand Up @@ -186,6 +190,14 @@ public boolean hasActiveTransit() {
return serviceIds.size() > 0;
}

/**
* @see org.opentripplanner.transit.service.TimetableRepository#findStopByScheduledStopPoint(FeedScopedId)
*/
@Override
public Map<FeedScopedId, RegularStop> stopsByScheduledStopPoint() {
return stopsByScheduledStopPoint;
}

/* Private Methods */

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public void buildGraph() {
// - have operators and notice assignments.
timetableRepository.addOperators(otpService.getAllOperators());
timetableRepository.addNoticeAssignments(otpService.getNoticeAssignments());
timetableRepository.addScheduledStopPointMapping(otpService.stopsByScheduledStopPoint());

AddTransitEntitiesToGraph.addToGraph(
otpService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,24 @@ private void parseFrameList(List<JAXBElement<? extends Common_VersionFrameStruct
}

private void parseCommonFrame(Common_VersionFrameStructure value) {
if (value instanceof ResourceFrame) {
parse((ResourceFrame) value, new ResourceFrameParser());
} else if (value instanceof ServiceCalendarFrame) {
parse((ServiceCalendarFrame) value, new ServiceCalendarFrameParser());
} else if (value instanceof TimetableFrame) {
parse((TimetableFrame) value, new TimeTableFrameParser());
} else if (value instanceof ServiceFrame) {
parse((ServiceFrame) value, new ServiceFrameParser(netexIndex.flexibleStopPlaceById));
} else if (value instanceof SiteFrame) {
parse((SiteFrame) value, new SiteFrameParser(ignoredFeatures));
} else if (!ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame) {
parse((FareFrame) value, new FareFrameParser());
} else if (value instanceof CompositeFrame) {
if (value instanceof ResourceFrame frame) {
parse(frame, new ResourceFrameParser());
} else if (value instanceof ServiceCalendarFrame frame) {
parse(frame, new ServiceCalendarFrameParser());
} else if (value instanceof TimetableFrame frame) {
parse(frame, new TimeTableFrameParser());
} else if (value instanceof ServiceFrame frame) {
parse(frame, new ServiceFrameParser(netexIndex.flexibleStopPlaceById));
} else if (value instanceof SiteFrame frame) {
parse(frame, new SiteFrameParser(ignoredFeatures));
} else if (!ignoredFeatures.contains(FARE_FRAME) && value instanceof FareFrame fareFrame) {
parse(fareFrame, new FareFrameParser());
} else if (value instanceof CompositeFrame frame) {
// We recursively parse composite frames and content until there
// is no more nested frames - this is accepting documents which
// are not withing the specification, but we leave this for the
// document schema validation - not a OTP responsibility
parseCompositeFrame((CompositeFrame) value);
parseCompositeFrame(frame);
} else if (value instanceof GeneralFrame || value instanceof InfrastructureFrame) {
NetexParser.informOnElementIntentionallySkipped(LOG, value);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.logging.MaxCountLogger;
import org.opentripplanner.netex.index.NetexEntityIndex;
Expand Down Expand Up @@ -136,13 +137,11 @@ void setResultOnIndex(NetexEntityIndex index) {
index.networkIdByGroupOfLineId.addAll(networkIdByGroupOfLineId);
}

private void parseStopAssignments(StopAssignmentsInFrame_RelStructure stopAssignments) {
private void parseStopAssignments(@Nullable StopAssignmentsInFrame_RelStructure stopAssignments) {
if (stopAssignments == null) return;

for (JAXBElement<?> stopAssignment : stopAssignments.getStopAssignment()) {
if (stopAssignment.getValue() instanceof PassengerStopAssignment) {
var assignment = (PassengerStopAssignment) stopAssignment.getValue();

if (stopAssignment.getValue() instanceof PassengerStopAssignment assignment) {
if (assignment.getQuayRef() == null) {
PASSENGER_STOP_ASSIGNMENT_LOGGER.info(
"PassengerStopAssignment with empty quay ref is dropped. Assigment: {}",
Expand All @@ -153,9 +152,8 @@ private void parseStopAssignments(StopAssignmentsInFrame_RelStructure stopAssign
String stopPointRef = assignment.getScheduledStopPointRef().getValue().getRef();
quayIdByStopPointRef.put(stopPointRef, quayRef);
}
} else if (stopAssignment.getValue() instanceof FlexibleStopAssignment) {
} else if (stopAssignment.getValue() instanceof FlexibleStopAssignment assignment) {
if (OTPFeature.FlexRouting.isOn()) {
FlexibleStopAssignment assignment = (FlexibleStopAssignment) stopAssignment.getValue();
String flexibleStopPlaceRef = assignment.getFlexibleStopPlaceRef().getRef();

// TODO OTP2 - This check belongs to the mapping or as a separate validation
Expand All @@ -176,7 +174,7 @@ private void parseStopAssignments(StopAssignmentsInFrame_RelStructure stopAssign
}
}

private void parseRoutes(RoutesInFrame_RelStructure routes) {
private void parseRoutes(@Nullable RoutesInFrame_RelStructure routes) {
if (routes == null) return;

for (JAXBElement<?> element : routes.getRoute_()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) {
mapTripPatterns(serviceIds);
mapNoticeAssignments();

mapScheduledStopPointsToQuays();
mapVehicleParkings();

addEntriesToGroupMapperForPostProcessingLater();
Expand Down Expand Up @@ -528,6 +529,18 @@ private void addEntriesToGroupMapperForPostProcessingLater() {
}
}

private void mapScheduledStopPointsToQuays() {
currentNetexIndex
.getQuayIdByStopPointRef()
.localKeys()
.forEach(id -> {
var sspid = idFactory.createId(id);
var stopId = idFactory.createId(currentNetexIndex.getQuayIdByStopPointRef().lookup(id));
var stop = Objects.requireNonNull(transitBuilder.getStops().get(stopId));
transitBuilder.addStopByScheduledStopPoint(sspid, stop);
});
}

private void mapVehicleParkings() {
var mapper = new VehicleParkingMapper(idFactory, issueStore);
currentNetexIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,11 @@ public boolean containsTrip(FeedScopedId id) {
return this.timetableRepositoryIndex.containsTrip(id);
}

@Override
public Optional<RegularStop> findStopByScheduledStopPoint(FeedScopedId scheduledStopPoint) {
return timetableRepository.findStopByScheduledStopPoint(scheduledStopPoint);
}

/**
* Returns a list of Trips that match the filtering defined in the request.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ public class TimetableRepository implements Serializable {

private transient TransitAlertService transitAlertService;

private final Map<FeedScopedId, RegularStop> stopsByScheduledStopPointRefs = new HashMap<>();

@Inject
public TimetableRepository(SiteRepository siteRepository, Deduplicator deduplicator) {
this.siteRepository = Objects.requireNonNull(siteRepository);
Expand Down Expand Up @@ -453,6 +455,28 @@ public void addTripPattern(FeedScopedId id, TripPattern tripPattern) {
tripPatternForId.put(id, tripPattern);
}

public void addScheduledStopPointMapping(Map<FeedScopedId, RegularStop> mapping) {
stopsByScheduledStopPointRefs.putAll(mapping);
}

/**
* Return the stop that is associated with the NeTEx concept of a scheduled stop point.
* <p>
* The scheduled stop point which is a "location-independent" stop that schedule systems provide
* which in turn can be later be resolved to an actual stop.
* <p>
* This way two schedule systems can use their own IDs for scheduled stop points but the stop (the
* actual physical infrastructure) is the same.
* <p>
* SIRI feeds are encouraged to refer to scheduled stop points in an EstimatedCall's stopPointRef
* but the specs are unclear and the reality on the ground very mixed.
*
* @link <a href="https://public.3.basecamp.com/p/TcEEP5WrNZJPBxrJU9GAjint">NeTEx Basecamp discussion</a>
*/
public Optional<RegularStop> findStopByScheduledStopPoint(FeedScopedId scheduledStopPoint) {
return Optional.ofNullable(stopsByScheduledStopPointRefs.get(scheduledStopPoint));
}

/**
* TripPatterns used to be reached through hop edges, but we're not creating on-board transit
* vertices/edges anymore.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ List<TripTimeOnDate> findTripTimeOnDate(
*/
boolean containsTrip(FeedScopedId id);

/**
* @see TimetableRepository#findStopByScheduledStopPoint(FeedScopedId)
*/
Optional<RegularStop> findStopByScheduledStopPoint(FeedScopedId scheduledStopPoint);

/**
* Returns a list of {@link RegularStop}s that lay within a bounding box and match the other criteria
* in the request object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ public TripOnServiceDate resolveTripOnServiceDate(
);
}

@Nullable
public TripOnServiceDate resolveTripOnServiceDate(
String serviceJourneyId,
LocalDate serviceDate
@Nullable LocalDate serviceDate
leonardehrenfried marked this conversation as resolved.
Show resolved Hide resolved
) {
if (serviceDate == null) {
return null;
Expand Down Expand Up @@ -157,11 +158,12 @@ public LocalDate resolveServiceDate(FramedVehicleJourneyRefStructure vehicleJour
* departure from the first stop, only the Date-part is actually used, and is defined to
* represent the actual serviceDate. The time and zone part is ignored.
*/
public LocalDate resolveServiceDate(ZonedDateTime originAimedDepartureTime) {
@Nullable
public LocalDate resolveServiceDate(@Nullable ZonedDateTime originAimedDepartureTime) {
leonardehrenfried marked this conversation as resolved.
Show resolved Hide resolved
if (originAimedDepartureTime == null) {
return null;
}
// This grab the local-date from timestamp passed into OTP ignoring the time and zone
// This grabs the local-date from timestamp passed into OTP ignoring the time and zone
// information. An alternative is to use the transit model zone:
// 'originAimedDepartureTime.withZoneSameInstant(transitService.getTimeZone())'

Expand All @@ -172,7 +174,8 @@ public LocalDate resolveServiceDate(ZonedDateTime originAimedDepartureTime) {
* Resolve a {@link Trip} by resolving a service journey id from FramedVehicleJourneyRef ->
* DatedVehicleJourneyRef.
*/
public Trip resolveTrip(FramedVehicleJourneyRefStructure journey) {
@Nullable
public Trip resolveTrip(@Nullable FramedVehicleJourneyRefStructure journey) {
leonardehrenfried marked this conversation as resolved.
Show resolved Hide resolved
if (journey != null) {
return resolveTrip(journey.getDatedVehicleJourneyRef());
}
Expand All @@ -184,10 +187,15 @@ public Trip resolveTrip(String serviceJourneyId) {
}

/**
* Resolve a {@link RegularStop} from a quay id.
* Resolve a {@link RegularStop} from a scheduled stop point or quay id.
*
* @see org.opentripplanner.transit.service.TimetableRepository#findStopByScheduledStopPoint(FeedScopedId)
*/
public RegularStop resolveQuay(String quayRef) {
return transitService.getRegularStop(resolveId(quayRef));
public RegularStop resolveQuay(String stopPointRef) {
var id = resolveId(stopPointRef);
return transitService
.findStopByScheduledStopPoint(id)
.orElseGet(() -> transitService.getRegularStop(id));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.opentripplanner.framework.application.OtpFileNames.BUILD_CONFIG_FILENAME;
import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id;

import java.util.Map;
import org.junit.jupiter.api.Test;
import org.opentripplanner.ConstantsForTests;
import org.opentripplanner._support.time.ZoneIds;
Expand All @@ -12,8 +14,10 @@
import org.opentripplanner.model.Timetable;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.test.support.ResourceLoader;
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.timetable.Trip;

class TimetableRepositoryTest {
Expand Down Expand Up @@ -107,4 +111,13 @@ void validateTimeZonesWithExplicitTimeZone() {
Timetable timetable = timetableRepositoryIndex.getPatternForTrip(trip).getScheduledTimetable();
assertEquals(20 * 60 - 60 * 60, timetable.getTripTimes(trip).getDepartureTime(0));
}

@Test
void scheduledStopPoints() {
var repo = new TimetableRepository();
var sspId = id("ssp-1");
var stop = TimetableRepositoryForTest.of().stop("stop-1").build();
repo.addScheduledStopPointMapping(Map.of(sspId, stop));
assertEquals(stop, repo.findStopByScheduledStopPoint(sspId).get());
}
}
Loading
Loading