Skip to content
Draft
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
2 changes: 1 addition & 1 deletion application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@
<dependency>
<groupId>org.onebusaway</groupId>
<artifactId>onebusaway-gtfs</artifactId>
<version>8.0.0</version>
<version>9.0.1</version>
</dependency>
<!-- Processing is used for the debug GUI (though we could probably use just Java2D) -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.opentripplanner.graph_builder.issue.api.DataImportIssueStore.NOOP;

import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.opentripplanner.ext.flex.FlexStopTimesForTest;
import org.opentripplanner.ext.flex.FlexTripsMapper;
Expand All @@ -19,7 +20,7 @@ class FlexTripsMapperTest {
@Test
void defaultTimePenalty() {
var builder = new OtpTransitServiceBuilder(SiteRepository.of().build(), NOOP);
var stopTimes = List.of(stopTime(0), stopTime(1));
var stopTimes = Stream.of(stopTime(0), stopTime(1));
builder.getStopTimesSortedByTrip().addAll(stopTimes);
var trips = FlexTripsMapper.createFlexTrips(builder, NOOP);
assertEquals("[UnscheduledTrip{F:flex-1}]", trips.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
booking_rule_id,booking_type,prior_notice_duration_min,prior_notice_duration_max,prior_notice_start_day,prior_notice_start_time,prior_notice_last_day,prior_notice_last_time,prior_notice_service_id,message,pickup_message,drop_off_message,phone_number,info_url,booking_url
booking_route_17102,0,,,,,,,,"The Downtowner provides free door-to-door transportation within the downtown area of Aspen. To schedule a ride, use the Downtowner Android/iOS mobile app. You may also request a ride by calling (877) 230-6045.",,,877-230-6045,https://www.cityofaspen.com/270/Downtowner,
booking_rule_id,booking_type,prior_notice_duration_min,prior_notice_duration_max,prior_notice_start_day,prior_notice_start_time,prior_notice_last_day,prior_notice_last_time,prior_notice_service_id,message,pickup_message,drop_off_message,phone_number,info_url,booking_url
booking_route_17102,1,120,1440,,,,,,Call reservationist to schedule.,,,(770) 528-1053,,
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled,timepoint,continuous_pickup,continuous_drop_off,pickup_booking_rule_id,drop_off_booking_rule_id,start_pickup_drop_off_window,end_pickup_drop_off_window,mean_duration_factor,mean_duration_offset,safe_duration_factor,safe_duration_offset
trip_id,arrival_time,departure_time,location_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled,timepoint,continuous_pickup,continuous_drop_off,pickup_booking_rule_id,drop_off_booking_rule_id,start_pickup_drop_off_window,end_pickup_drop_off_window,mean_duration_factor,mean_duration_offset,safe_duration_factor,safe_duration_offset
t_1289257_b_28352_tn_0,,,area_294,1,,2,2,0,0,1,1,booking_route_17102,booking_route_17102,08:00:00,23:00:00,1,9.00,1,20.00
t_1289262_b_29084_tn_0,,,area_294,1,,2,2,0,0,1,1,booking_route_17102,booking_route_17102,11:00:00,23:00:00,1,9.00,1,20.00

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import org.onebusaway.gtfs.model.IdentityBean;
import org.onebusaway.gtfs.model.RiderCategory;
import org.onebusaway.gtfs.model.Route;
import org.onebusaway.gtfs.model.ShapePoint;
import org.onebusaway.gtfs.model.StopAreaElement;
import org.onebusaway.gtfs.model.StopTime;
import org.onebusaway.gtfs.serialization.GtfsReader;
import org.onebusaway.gtfs.services.GenericMutableDao;
import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
Expand Down Expand Up @@ -53,6 +55,11 @@

public class GtfsModule implements GraphBuilderModule {

/**
* For these files we have hand-rolled streaming parsers so we skip them in OBA to conserve
* memory.
*/
public static final Set<Class<?>> SKIPPED_CLASSES = Set.of(StopTime.class, ShapePoint.class);
public static final Set<Class<?>> FARES_V2_CLASSES = Set.of(
FareProduct.class,
FareLegRule.class,
Expand Down Expand Up @@ -147,7 +154,7 @@ public void buildGraph() {
gtfsBundle.parameters().discardMinTransferTimes(),
gtfsBundle.parameters().stationTransferPreference()
);
mapper.mapStopTripAndRouteDataIntoBuilder(gtfsDao);
mapper.mapStopTripAndRouteDataIntoBuilder(gtfsDao, gtfsBundle.getCsvInputSource());

OtpTransitServiceBuilder builder = mapper.getBuilder();
var fareRulesData = mapper.fareRulesData();
Expand Down Expand Up @@ -311,6 +318,7 @@ private void addTimetableRepositoryToGraph(
private GtfsMutableRelationalDao loadBundle(GtfsBundle gtfsBundle) throws IOException {
var dao = new GtfsRelationalDaoImpl();
dao.setPackShapePoints(true);
dao.setPackStopTimes(true);
StoreImpl store = new StoreImpl(dao);
store.open();
LOG.info("reading {}", gtfsBundle.feedInfo());
Expand Down Expand Up @@ -372,7 +380,11 @@ private GtfsMutableRelationalDao loadBundle(GtfsBundle gtfsBundle) throws IOExce
* it can easily lead to graph build failures.
*/
private boolean skipEntityClass(Class<?> entityClass) {
return OTPFeature.FaresV2.isOff() && FARES_V2_CLASSES.contains(entityClass);
if (SKIPPED_CLASSES.contains(entityClass)) {
return true;
} else {
return OTPFeature.FaresV2.isOff() && FARES_V2_CLASSES.contains(entityClass);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.BookingRule;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.organization.ContactInfo;
import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
Expand All @@ -15,15 +18,22 @@
/** Responsible for mapping GTFS BookingRule into the OTP model. */
class BookingRuleMapper {

private final Map<AgencyAndId, BookingInfo> cachedBookingInfos = new HashMap<>();
private final Map<FeedScopedId, BookingInfo> cachedBookingInfos = new HashMap<>();
private final IdFactory idFactory;

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

/** Map from GTFS to OTP model, {@code null} safe. */
BookingInfo map(BookingRule rule) {
if (rule == null) {
return null;
}

return cachedBookingInfos.computeIfAbsent(rule.getId(), k ->
var id = idFactory.createId(rule.getId(), "booking rule");

return cachedBookingInfos.computeIfAbsent(id, k ->
BookingInfo.of()
.withContactInfo(contactInfo(rule))
.withBookingMethods(bookingMethods())
Expand All @@ -38,6 +48,11 @@ BookingInfo map(BookingRule rule) {
);
}

@Nullable
BookingInfo findBookingRule(FeedScopedId id) {
return cachedBookingInfos.get(id);
}

private ContactInfo contactInfo(BookingRule rule) {
return ContactInfo.of()
.withPhoneNumber(rule.getPhoneNumber())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import static org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STATION;
import static org.onebusaway.gtfs.model.Stop.LOCATION_TYPE_STOP;

import java.io.IOException;
import java.util.Collection;
import java.util.function.Function;
import org.onebusaway.csv_entities.CsvInputSource;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.services.GtfsDao;
import org.onebusaway.gtfs.services.GtfsRelationalDao;
Expand Down Expand Up @@ -134,15 +136,8 @@ public GTFSToOtpTransitServiceMapper(
routeMapper = new RouteMapper(idFactory, agencyMapper, issueStore, translationHelper);
directionMapper = new DirectionMapper(issueStore);
tripMapper = new TripMapper(idFactory, routeMapper, directionMapper, translationHelper);
bookingRuleMapper = new BookingRuleMapper();
stopTimeMapper = new StopTimeMapper(
stopMapper,
locationMapper,
locationGroupMapper,
tripMapper,
bookingRuleMapper,
translationHelper
);
bookingRuleMapper = new BookingRuleMapper(idFactory);
stopTimeMapper = new StopTimeMapper(idFactory, builder, bookingRuleMapper, translationHelper);
frequencyMapper = new FrequencyMapper(tripMapper);
fareAttributeMapper = new FareAttributeMapper(idFactory);
fareRuleMapper = new FareRuleMapper(routeMapper, fareAttributeMapper);
Expand All @@ -160,7 +155,8 @@ public FareRulesData fareRulesData() {
return fareRulesBuilder;
}

public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data) {
public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data, CsvInputSource csvSource)
throws IOException {
translationHelper.importTranslations(data.getAllTranslations(), data.getAllFeedInfos());

builder.getAgenciesById().addAll(agencyMapper.map(data.getAllAgencies()));
Expand All @@ -169,7 +165,7 @@ public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data) {
builder.getFeedInfos().addAll(feedInfoMapper.map(data.getAllFeedInfos()));
builder.getFrequencies().addAll(frequencyMapper.map(data.getAllFrequencies()));
builder.getRoutes().addAll(routeMapper.map(data.getAllRoutes()));
var shapes = shapePointMapper.map(data.getAllShapePoints());
var shapes = shapePointMapper.map(csvSource);
builder.getShapePoints().putAll(shapes);
// shape points is a large collection, so after mapping it can be cleared
data.getAllShapePoints().clear();
Expand All @@ -182,10 +178,12 @@ public void mapStopTripAndRouteDataIntoBuilder(GtfsRelationalDao data) {
builder.siteRepository().withGroupStops(locationGroupMapper.map(data.getAllLocationGroups()));
}

builder.getTripsById().addAll(tripMapper.map(data.getAllTrips()));

builder.getPathways().addAll(pathwayMapper.map(data.getAllPathways()));
builder.getStopTimesSortedByTrip().addAll(stopTimeMapper.map(data.getAllStopTimes()));
data.getAllBookingRules().forEach(bookingRuleMapper::map);
builder.getStopTimesSortedByTrip().addAll(stopTimeMapper.map(csvSource));
builder.getFlexTimePenalty().putAll(tripMapper.flexSafeTimePenalties());
builder.getTripsById().addAll(tripMapper.map(data.getAllTrips()));

fareRulesBuilder.fareAttributes().addAll(fareAttributeMapper.map(data.getAllFareAttributes()));
fareRulesBuilder.fareRules().addAll(fareRuleMapper.map(data.getAllFareRules()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.opentripplanner.gtfs.mapping;

import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import javax.annotation.Nullable;
import org.opentripplanner.utils.lang.StringUtils;

public class GtfsRow {

protected final Map<String, String> fields;

public GtfsRow(Map<String, String> fields) {
this.fields = Map.copyOf(fields);
}

public String string(String field) {
return optionalString(field).orElseThrow(() -> iae(field));
}

@Nullable
public String nullableString(String field) {
return fields.get(field);
}

public Optional<String> optionalString(String field) {
return Optional.ofNullable(fields.get(field));
}

public int integer(String field) {
return optionalInteger(field).orElseThrow(() -> iae(field));
}

public OptionalInt optionalInteger(String field) {
return optionalString(field).stream().mapToInt(Integer::parseInt).findFirst();
}

/**
* I know that 'double' is not spelled like this, but it's a reserved word.
*/
public double doubble(String field) {
return optionalDouble(field).orElseThrow(() -> iae(field));
}

public OptionalDouble optionalDouble(String field) {
return optionalString(field).stream().mapToDouble(Double::parseDouble).findFirst();
}

private IllegalArgumentException iae(String field) {
return new IllegalArgumentException(
"Field '%s' is required but not present in CSV row %s".formatted(field, fields)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@

import static org.opentripplanner.model.StopTime.MISSING_VALUE;

import javax.annotation.Nullable;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.utils.lang.StringUtils;

public class PickDropMapper {

public static PickDrop map(@Nullable String gtfsCode) {
if (StringUtils.hasNoValue(gtfsCode)) {
return PickDrop.SCHEDULED;
} else {
return map(Integer.parseInt(gtfsCode));
}
}

public static PickDrop map(int gtfsCode) {
return switch (gtfsCode) {
case 0 -> PickDrop.SCHEDULED;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
package org.opentripplanner.gtfs.mapping;

import java.util.Collection;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.units.qual.C;
import org.onebusaway.csv_entities.CsvInputSource;
import org.onebusaway.gtfs.model.ShapePoint;
import org.opentripplanner.transit.model.framework.FeedScopedId;

/** Responsible for mapping GTFS ShapePoint into the OTP model. */
class ShapePointMapper {

private static final String FILE = "shapes.txt";

private static final String SHAPE_DIST_TRAVELED = "shape_dist_traveled";
private static final String SHAPE_ID = "shape_id";
private static final String SHAPE_PT_SEQUENCE = "shape_pt_sequence";
private static final String SHAPE_PT_LAT = "shape_pt_lat";
private static final String SHAPE_PT_LON = "shape_pt_lon";

private final IdFactory idFactory;

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

Map<FeedScopedId, CompactShape> map(
Collection<org.onebusaway.gtfs.model.ShapePoint> allShapePoints
) {
Map<FeedScopedId, CompactShape> map(CsvInputSource inputSource) throws IOException {
var ret = new HashMap<FeedScopedId, CompactShape>();
for (var shapePoint : allShapePoints) {
var shapeId = idFactory.createId(shapePoint.getShapeId(), "shape point");
var shape = ret.computeIfAbsent(shapeId, id -> new CompactShape());
shape.addPoint(shapePoint);
}
new StreamingCsvReader(inputSource)
.rows(FILE)
.forEach(row -> {
var shapeId = idFactory.createId(row.string(SHAPE_ID), "shape point");
var shapeBuilder = ret.getOrDefault(shapeId, new CompactShape());

var point = new ShapePoint();
point.setSequence(row.integer(SHAPE_PT_SEQUENCE));
point.setLat(row.doubble(SHAPE_PT_LAT));
point.setLon(row.doubble(SHAPE_PT_LON));

row.optionalDouble(SHAPE_DIST_TRAVELED).ifPresent(point::setDistTraveled);

shapeBuilder.addPoint(point);
ret.put(shapeId, shapeBuilder);
});

return ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class StopMapper {

private final IdFactory idFactory;
private final Map<org.onebusaway.gtfs.model.Stop, RegularStop> mappedStops = new HashMap<>();

private final SiteRepositoryBuilder siteRepositoryBuilder;
private final TranslationHelper translationHelper;
private final Function<FeedScopedId, Station> stationLookUp;
Expand Down
Loading
Loading