Skip to content
Merged
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 @@ -61,6 +61,8 @@ public class GTFSToTransitDataImportMapper {

private final TripMapper tripMapper;

private final TripSegmentMapper tripSegmentMapper;

private final BookingRuleMapper bookingRuleMapper;

private final StopTimeMapper stopTimeMapper;
Expand Down Expand Up @@ -148,6 +150,7 @@ public GTFSToTransitDataImportMapper(
);
directionMapper = new DirectionMapper(issueStore);
tripMapper = new TripMapper(idFactory, routeMapper, directionMapper, translationHelper);
tripSegmentMapper = new TripSegmentMapper(idFactory);
bookingRuleMapper = new BookingRuleMapper();
stopTimeMapper = new StopTimeMapper(
stopMapper,
Expand Down Expand Up @@ -176,7 +179,8 @@ public GTFSToTransitDataImportMapper(
issueStore,
noticeMapper,
tripMapper,
routeMapper
routeMapper,
tripSegmentMapper
);
}

Expand Down Expand Up @@ -230,6 +234,7 @@ public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data) {
.addAll(fareTransferRuleMapper.map(data.getAllFareTransferRules()));
fareRulesBuilder.stopAreas().putAll(stopAreaMapper.map(data.getAllStopAreaElements()));

tripSegmentMapper.map(data.getAllTripSegments(), builder.getStopTimesSortedByTrip());
noticeMapper.map(data.getAllNotices());
builder
.getNoticeAssignments()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.onebusaway.gtfs.model.NoticeAssignment;
import org.opentripplanner.core.model.id.FeedScopedId;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.utils.collection.ListUtils;

/**
* Maps GTFS notice_assignments.txt entries to OTP notice assignments, connecting each
* {@link Notice} to its target {@link Route} or {@link Trip}.
* {@link Notice} to its target {@link Route}, {@link Trip} or trip segment. A trip segment is
* expanded to one entry per stop time it covers.
*/
class NoticeAssignmentMapper {

Expand All @@ -26,37 +29,39 @@ class NoticeAssignmentMapper {
private final NoticeMapper noticeMapper;
private final RouteMapper routeMapper;
private final TripMapper tripMapper;
private final TripSegmentMapper tripSegmentMapper;

NoticeAssignmentMapper(
IdFactory idFactory,
DataImportIssueStore issueStore,
NoticeMapper noticeMapper,
TripMapper tripMapper,
RouteMapper routeMapper
RouteMapper routeMapper,
TripSegmentMapper tripSegmentMapper
) {
this.idFactory = idFactory;
this.issueStore = issueStore;
this.noticeMapper = noticeMapper;
this.routeMapper = routeMapper;
this.tripMapper = tripMapper;
this.tripSegmentMapper = tripSegmentMapper;
}

Multimap<AbstractTransitEntity, Notice> map(Collection<NoticeAssignment> assignments) {
Multimap<AbstractTransitEntity, Notice> result = ArrayListMultimap.create();
var notices = noticeMapper.mappedNotices();
var trips = tripMapper
.getMappedTrips()
.stream()
.collect(Collectors.toMap(Trip::getId, Function.identity()));
var routes = routeMapper.mappedRoutes();
for (var assignment : assignments) {
mapOne(assignment, noticeMapper.mappedNotices(), trips, routes).ifPresent(entry ->
result.put(entry.getKey(), entry.getValue())
);
map(assignment, notices, trips, routes).forEach(e -> result.put(e.getKey(), e.getValue()));
}
return result;
}

private Optional<Map.Entry<AbstractTransitEntity, Notice>> mapOne(
private Stream<Map.Entry<AbstractTransitEntity, Notice>> map(
NoticeAssignment assignment,
Map<FeedScopedId, Notice> notices,
Map<FeedScopedId, Trip> trips,
Expand All @@ -71,28 +76,28 @@ private Optional<Map.Entry<AbstractTransitEntity, Notice>> mapOne(
"Notice in notice assignment is missing for assignment %s",
assignment
);
return Optional.empty();
return Stream.of();
}

var recordId = idFactory.createId(assignment.getRecordId(), "NoticeAssignment.recordId");

AbstractTransitEntity entity = switch (assignment.getTableName()) {
case routes -> routes.get(recordId);
case trips -> trips.get(recordId);
case trip_segments -> null;
List<AbstractTransitEntity> entities = switch (assignment.getTableName()) {
case routes -> ListUtils.ofNullable(routes.get(recordId));
case trips -> ListUtils.ofNullable(trips.get(recordId));
case trip_segments -> List.copyOf(tripSegmentMapper.getStopTimeKeys(recordId));
};

if (entity == null) {
if (entities.isEmpty()) {
issueStore.add(
"NoticeAssignmentWithUnknownEntity",
"Could not map notice assignment %s for %s with id %s",
assignment.getId(),
assignment.getTableName(),
assignment.getRecordId()
);
return Optional.empty();
return Stream.of();
}

return Optional.of(Map.entry(entity, notice));
return entities.stream().map(entity -> Map.entry(entity, notice));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.opentripplanner.gtfs.mapping;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.onebusaway.gtfs.model.TripSegment;
import org.opentripplanner.core.model.id.FeedScopedId;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.TripStopTimes;
import org.opentripplanner.transit.model.timetable.StopTimeKey;

/**
* Maps a GTFS trip segment - a range of stops on a single trip - to the {@link StopTimeKey}s of
* every stop within that range. This allows a notice assigned to a trip segment to be attached to
* each of the individual stop times the segment covers.
* <p>
* The mapped result is stored keyed by {@code trip_segment_id} so that
* {@link NoticeAssignmentMapper} can look it up when resolving notice assignments.
*/
class TripSegmentMapper {

private final IdFactory idFactory;
private final Map<FeedScopedId, List<StopTimeKey>> mappedTripSegments = new HashMap<>();

TripSegmentMapper(IdFactory idFactory) {
this.idFactory = idFactory;
}

void map(Collection<TripSegment> segments, TripStopTimes stopTimesByTrip) {
var stopTimesByTripId = new HashMap<FeedScopedId, List<StopTime>>();
for (var trip : stopTimesByTrip.keys()) {
stopTimesByTripId.put(trip.getId(), stopTimesByTrip.get(trip));
}
for (var segment : segments) {
mappedTripSegments.put(
idFactory.createId(segment.getId(), "trip_segment_id"),
mapStopTimeKeys(segment, stopTimesByTripId)
);
}
}

/**
* The {@link StopTimeKey}s for the stops covered by the given {@code trip_segment_id}, or an empty
* list if no such trip segment was mapped.
*/
List<StopTimeKey> getStopTimeKeys(FeedScopedId tripSegmentId) {
return mappedTripSegments.getOrDefault(tripSegmentId, List.of());
}

/**
* Returns a {@link StopTimeKey} for each of the trip's stop times whose stop sequence lies within
* the segment's {@code [fromStopSequence, toStopSequence]} range. The key uses the stop time's
* index in the (sequence-ordered) list of the trip's stop times, not the GTFS
* {@code stop_sequence}, to match how {@link StopTimeKey}s are referenced elsewhere in OTP.
*/
private List<StopTimeKey> mapStopTimeKeys(
TripSegment segment,
Map<FeedScopedId, List<StopTime>> stopTimesByTripId
) {
var tripId = idFactory.createId(segment.getTripId(), "trip_id in trip segment");
var stopTimes = stopTimesByTripId.getOrDefault(tripId, List.of());
var result = new ArrayList<StopTimeKey>();
for (int i = 0; i < stopTimes.size(); i++) {
var stopSequence = stopTimes.get(i).getStopSequence();
if (
stopSequence >= segment.getFromStopSequence() && stopSequence <= segment.getToStopSequence()
) {
result.add(StopTimeKey.of(tripId, i).build());
}
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ public class StopTimeKey extends AbstractTransitEntity<StopTimeKey, StopTimeKeyB
super(builder.getId());
}

public static StopTimeKeyBuilder of(FeedScopedId tripId, int stopSequenceNumber) {
/**
* @param tripId
* @param stopPositionInPattern the 0 based position in the trip pattern, not GTFS stop_sequence
*/
public static StopTimeKeyBuilder of(FeedScopedId tripId, int stopPositionInPattern) {
return new StopTimeKeyBuilder(
new FeedScopedId(tripId.getFeedId(), tripId.getId() + "_#" + stopSequenceNumber)
new FeedScopedId(tripId.getFeedId(), tripId.getId() + "_#" + stopPositionInPattern)
);
}

Expand Down
Loading