Skip to content

Commit

Permalink
Merge pull request #5966 from HSLdevcom/car-ferry-changes
Browse files Browse the repository at this point in the history
Add car ferry functionality
  • Loading branch information
optionsome authored Oct 29, 2024
2 parents 17de4e5 + 02aaf42 commit 1377722
Show file tree
Hide file tree
Showing 23 changed files with 397 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,7 @@ public enum GraphQLPlanAccessMode {
BICYCLE,
BICYCLE_PARKING,
BICYCLE_RENTAL,
CAR,
CAR_DROP_OFF,
CAR_PARKING,
CAR_RENTAL,
Expand Down Expand Up @@ -1625,6 +1626,7 @@ public enum GraphQLPlanDirectMode {
public enum GraphQLPlanEgressMode {
BICYCLE,
BICYCLE_RENTAL,
CAR,
CAR_PICKUP,
CAR_RENTAL,
FLEX,
Expand Down Expand Up @@ -1927,6 +1929,7 @@ public void setGraphQLWalk(GraphQLWalkPreferencesInput walk) {

public enum GraphQLPlanTransferMode {
BICYCLE,
CAR,
WALK,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static StreetMode map(GraphQLTypes.GraphQLPlanAccessMode mode) {
case BICYCLE -> StreetMode.BIKE;
case BICYCLE_RENTAL -> StreetMode.BIKE_RENTAL;
case BICYCLE_PARKING -> StreetMode.BIKE_TO_PARK;
case CAR -> StreetMode.CAR;
case CAR_RENTAL -> StreetMode.CAR_RENTAL;
case CAR_PARKING -> StreetMode.CAR_TO_PARK;
case CAR_DROP_OFF -> StreetMode.CAR_PICKUP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static StreetMode map(GraphQLTypes.GraphQLPlanEgressMode mode) {
return switch (mode) {
case BICYCLE -> StreetMode.BIKE;
case BICYCLE_RENTAL -> StreetMode.BIKE_RENTAL;
case CAR -> StreetMode.CAR;
case CAR_RENTAL -> StreetMode.CAR_RENTAL;
case CAR_PICKUP -> StreetMode.CAR_PICKUP;
case FLEX -> StreetMode.FLEXIBLE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,10 @@ private static void validateStreetModes(JourneyRequest journey) {
"If BICYCLE is used for access, egress or transfer, then it should be used for all."
);
}
if (modes.contains(StreetMode.CAR) && modes.size() != 1) {
throw new IllegalArgumentException(
"If CAR is used for access, egress or transfer, then it should be used for all."
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class TransferModeMapper {

public static StreetMode map(GraphQLTypes.GraphQLPlanTransferMode mode) {
return switch (mode) {
case CAR -> StreetMode.CAR;
case BICYCLE -> StreetMode.BIKE;
case WALK -> StreetMode.WALK;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

class RequestModesMapper {

private static final Predicate<StreetMode> IS_BIKE = m -> m == StreetMode.BIKE;
private static final Predicate<StreetMode> IS_BIKE_OR_CAR = m ->
m == StreetMode.BIKE || m == StreetMode.CAR;
private static final String accessModeKey = "accessMode";
private static final String egressModeKey = "egressMode";
private static final String directModeKey = "directMode";
Expand All @@ -27,7 +28,10 @@ static RequestModes mapRequestModes(Map<String, ?> modesInput) {
ensureValueAndSet(accessMode, mBuilder::withAccessMode);
ensureValueAndSet((StreetMode) modesInput.get(egressModeKey), mBuilder::withEgressMode);
ensureValueAndSet((StreetMode) modesInput.get(directModeKey), mBuilder::withDirectMode);
Optional.ofNullable(accessMode).filter(IS_BIKE).ifPresent(mBuilder::withTransferMode);
// The only cases in which the transferMode isn't WALK are when the accessMode is either BIKE or CAR.
// In these cases, the transferMode is the same as the accessMode. This check is not strictly necessary
// if there is a need for more freedom for specifying the transferMode.
Optional.ofNullable(accessMode).filter(IS_BIKE_OR_CAR).ifPresent(mBuilder::withTransferMode);

return mBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
import org.opentripplanner.transit.model.network.CarAccess;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
Expand Down Expand Up @@ -87,25 +88,12 @@ public void linkTransitStops(Graph graph, TimetableRepository timetableRepositor
LOG.info(progress.startMessage());

Set<StopLocation> stopLocationsUsedForFlexTrips = Set.of();

if (OTPFeature.FlexRouting.isOn()) {
stopLocationsUsedForFlexTrips =
timetableRepository
.getAllFlexTrips()
.stream()
.flatMap(t -> t.getStops().stream())
.collect(Collectors.toSet());

stopLocationsUsedForFlexTrips.addAll(
stopLocationsUsedForFlexTrips
.stream()
.filter(GroupStop.class::isInstance)
.map(GroupStop.class::cast)
.flatMap(g -> g.getChildLocations().stream().filter(RegularStop.class::isInstance))
.toList()
);
stopLocationsUsedForFlexTrips = getStopLocationsUsedForFlexTrips(timetableRepository);
}

Set<StopLocation> stopLocationsUsedForCarsAllowedTrips = timetableRepository.getStopLocationsUsedForCarsAllowedTrips();

for (TransitStopVertex tStop : vertices) {
// Stops with pathways do not need to be connected to the street network, since there are explicit entrances defined for that
if (tStop.hasPathways()) {
Expand All @@ -120,7 +108,10 @@ public void linkTransitStops(Graph graph, TimetableRepository timetableRepositor
StopLinkType linkType = StopLinkType.WALK_ONLY;

if (
OTPFeature.FlexRouting.isOn() && stopLocationsUsedForFlexTrips.contains(tStop.getStop())
(
OTPFeature.FlexRouting.isOn() && stopLocationsUsedForFlexTrips.contains(tStop.getStop())
) ||
stopLocationsUsedForCarsAllowedTrips.contains(tStop.getStop())
) {
linkType = StopLinkType.WALK_AND_CAR;
}
Expand Down Expand Up @@ -366,6 +357,26 @@ private VehicleParking removeVehicleParkingEntranceVertexFromGraph(
}
}

private Set<StopLocation> getStopLocationsUsedForFlexTrips(
TimetableRepository timetableRepository
) {
Set<StopLocation> stopLocations = timetableRepository
.getAllFlexTrips()
.stream()
.flatMap(t -> t.getStops().stream())
.collect(Collectors.toSet());

stopLocations.addAll(
stopLocations
.stream()
.filter(GroupStop.class::isInstance)
.map(GroupStop.class::cast)
.flatMap(g -> g.getChildLocations().stream().filter(RegularStop.class::isInstance))
.toList()
);
return stopLocations;
}

private enum StopLinkType {
/**
* Only ensure that the link leads to a walkable edge.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.opentripplanner.gtfs.mapping;

import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.transit.model.network.CarAccess;

/**
* Model car access for GTFS trips.
*/
class CarAccessMapper {

public static CarAccess mapForTrip(Trip rhs) {
int carsAllowed = rhs.getCarsAllowed();
return switch (carsAllowed) {
case 1 -> CarAccess.ALLOWED;
case 2 -> CarAccess.NOT_ALLOWED;
default -> CarAccess.UNKNOWN;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ private Trip doMap(org.onebusaway.gtfs.model.Trip rhs) {
lhs.withShapeId(AgencyAndIdMapper.mapAgencyAndId(rhs.getShapeId()));
lhs.withWheelchairBoarding(WheelchairAccessibilityMapper.map(rhs.getWheelchairAccessible()));
lhs.withBikesAllowed(BikeAccessMapper.mapForTrip(rhs));
lhs.withCarsAllowed(CarAccessMapper.mapForTrip(rhs));

var trip = lhs.build();
mapSafeTimePenalty(rhs).ifPresent(f -> flexSafeTimePenalties.put(trip, f));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.BikeAccess;
import org.opentripplanner.transit.model.network.CarAccess;
import org.opentripplanner.transit.model.network.RoutingTripPattern;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripTimes;
Expand All @@ -20,6 +21,8 @@ public class RouteRequestTransitDataProviderFilter implements TransitDataProvide

private final boolean requireBikesAllowed;

private final boolean requireCarsAllowed;

private final boolean wheelchairEnabled;

private final WheelchairPreferences wheelchairPreferences;
Expand All @@ -41,6 +44,7 @@ public class RouteRequestTransitDataProviderFilter implements TransitDataProvide
public RouteRequestTransitDataProviderFilter(RouteRequest request) {
this(
request.journey().transfer().mode() == StreetMode.BIKE,
request.journey().transfer().mode() == StreetMode.CAR,
request.wheelchair(),
request.preferences().wheelchair(),
request.preferences().transit().includePlannedCancellations(),
Expand All @@ -53,6 +57,7 @@ public RouteRequestTransitDataProviderFilter(RouteRequest request) {
// This constructor is used only for testing
public RouteRequestTransitDataProviderFilter(
boolean requireBikesAllowed,
boolean requireCarsAllowed,
boolean wheelchairEnabled,
WheelchairPreferences wheelchairPreferences,
boolean includePlannedCancellations,
Expand All @@ -61,6 +66,7 @@ public RouteRequestTransitDataProviderFilter(
List<TransitFilter> filters
) {
this.requireBikesAllowed = requireBikesAllowed;
this.requireCarsAllowed = requireCarsAllowed;
this.wheelchairEnabled = wheelchairEnabled;
this.wheelchairPreferences = wheelchairPreferences;
this.includePlannedCancellations = includePlannedCancellations;
Expand Down Expand Up @@ -97,10 +103,12 @@ public boolean tripPatternPredicate(TripPatternForDate tripPatternForDate) {
public boolean tripTimesPredicate(TripTimes tripTimes, boolean withFilters) {
final Trip trip = tripTimes.getTrip();

if (requireBikesAllowed) {
if (bikeAccessForTrip(trip) != BikeAccess.ALLOWED) {
return false;
}
if (requireBikesAllowed && bikeAccessForTrip(trip) != BikeAccess.ALLOWED) {
return false;
}

if (requireCarsAllowed && trip.getCarsAllowed() != CarAccess.ALLOWED) {
return false;
}

if (wheelchairEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ public enum StreetMode implements DocumentedEnum<StreetMode> {
SCOOTER_RENTAL(Feature.ACCESS, Feature.EGRESS, Feature.WALKING, Feature.SCOOTER, Feature.RENTING),
/**
* Car only
* <p>
* Direct mode only.
*/
CAR(Feature.ACCESS, Feature.DRIVING),
CAR(Feature.ACCESS, Feature.TRANSFER, Feature.EGRESS, Feature.DRIVING),
/**
* Start in the car, drive to a parking area, and walk the rest of the way.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.opentripplanner.transit.model.network;

/**
* This represents the state of whether bikes are allowed on board trips (or routes).
* <p>
* GTFS codes:
* 0 = unknown / unspecified, 1 = bikes allowed, 2 = bikes NOT allowed
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.opentripplanner.transit.model.network;

/**
* This represents the state of whether cars are allowed on board trips.
* <p>
* GTFS codes:
* 0 = unknown / unspecified, 1 = cars allowed, 2 = cars NOT allowed
*/
public enum CarAccess {
UNKNOWN,
NOT_ALLOWED,
ALLOWED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.framework.LogInfo;
import org.opentripplanner.transit.model.network.BikeAccess;
import org.opentripplanner.transit.model.network.CarAccess;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.organization.Operator;

Expand All @@ -38,6 +39,7 @@ public final class Trip extends AbstractTransitEntity<Trip, TripBuilder> impleme
private final TransitMode mode;
private final Direction direction;
private final BikeAccess bikesAllowed;
private final CarAccess carsAllowed;
private final Accessibility wheelchairBoarding;

private final SubMode netexSubmode;
Expand Down Expand Up @@ -76,6 +78,7 @@ public final class Trip extends AbstractTransitEntity<Trip, TripBuilder> impleme
: route.getNetexSubmode();
this.direction = requireNonNullElse(builder.getDirection(), Direction.UNKNOWN);
this.bikesAllowed = requireNonNullElse(builder.getBikesAllowed(), route.getBikesAllowed());
this.carsAllowed = requireNonNullElse(builder.getCarsAllowed(), CarAccess.UNKNOWN);
this.wheelchairBoarding =
requireNonNullElse(builder.getWheelchairBoarding(), Accessibility.NO_INFORMATION);
this.netexAlteration = requireNonNullElse(builder.getNetexAlteration(), TripAlteration.PLANNED);
Expand Down Expand Up @@ -156,6 +159,10 @@ public BikeAccess getBikesAllowed() {
return bikesAllowed;
}

public CarAccess getCarsAllowed() {
return carsAllowed;
}

public Accessibility getWheelchairBoarding() {
return wheelchairBoarding;
}
Expand Down Expand Up @@ -217,6 +224,7 @@ public boolean sameAs(Trip other) {
Objects.equals(this.shapeId, other.shapeId) &&
Objects.equals(this.direction, other.direction) &&
Objects.equals(this.bikesAllowed, other.bikesAllowed) &&
Objects.equals(this.carsAllowed, other.carsAllowed) &&
Objects.equals(this.wheelchairBoarding, other.wheelchairBoarding) &&
Objects.equals(this.netexAlteration, other.netexAlteration)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.opentripplanner.transit.model.framework.AbstractEntityBuilder;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.BikeAccess;
import org.opentripplanner.transit.model.network.CarAccess;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.organization.Operator;

Expand All @@ -21,6 +22,7 @@ public class TripBuilder extends AbstractEntityBuilder<Trip, TripBuilder> {
private FeedScopedId shapeId;
private Direction direction;
private BikeAccess bikesAllowed;
private CarAccess carsAllowed;
private Accessibility wheelchairBoarding;
private String gtfsBlockId;
private String netexInternalPlanningCode;
Expand All @@ -44,6 +46,7 @@ public class TripBuilder extends AbstractEntityBuilder<Trip, TripBuilder> {
this.shapeId = original.getShapeId();
this.direction = original.getDirection();
this.bikesAllowed = original.getBikesAllowed();
this.carsAllowed = original.getCarsAllowed();
this.wheelchairBoarding = original.getWheelchairBoarding();
this.netexInternalPlanningCode = original.getNetexInternalPlanningCode();
}
Expand Down Expand Up @@ -151,11 +154,20 @@ public BikeAccess getBikesAllowed() {
return bikesAllowed;
}

public CarAccess getCarsAllowed() {
return carsAllowed;
}

public TripBuilder withBikesAllowed(BikeAccess bikesAllowed) {
this.bikesAllowed = bikesAllowed;
return this;
}

public TripBuilder withCarsAllowed(CarAccess carsAllowed) {
this.carsAllowed = carsAllowed;
return this;
}

public Accessibility getWheelchairBoarding() {
return wheelchairBoarding;
}
Expand Down
Loading

0 comments on commit 1377722

Please sign in to comment.