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

Add car ferry functionality #5966

Merged
merged 16 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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 @@ -1492,6 +1492,7 @@ public enum GraphQLPlanAccessMode {
BICYCLE,
BICYCLE_PARKING,
BICYCLE_RENTAL,
CAR,
CAR_DROP_OFF,
CAR_PARKING,
CAR_RENTAL,
Expand Down Expand Up @@ -1575,6 +1576,7 @@ public enum GraphQLPlanDirectMode {
public enum GraphQLPlanEgressMode {
BICYCLE,
BICYCLE_RENTAL,
CAR,
CAR_PICKUP,
CAR_RENTAL,
FLEX,
Expand Down Expand Up @@ -1877,6 +1879,7 @@ public void setGraphQLWalk(GraphQLWalkPreferencesInput walk) {

public enum GraphQLPlanTransferMode {
BICYCLE,
CAR,
WALK,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package org.opentripplanner.apis.gtfs.mapping;

import javax.annotation.Nonnull;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed;
import org.opentripplanner.transit.model.network.BikeAccess;

public class BikesAllowedMapper {

@Nonnull
public static GraphQLBikesAllowed map(@Nonnull BikeAccess bikesAllowed) {
public static GraphQLBikesAllowed map(BikeAccess bikesAllowed) {
return switch (bikesAllowed) {
case UNKNOWN -> GraphQLBikesAllowed.NO_INFORMATION;
case ALLOWED -> GraphQLBikesAllowed.ALLOWED;
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);
habrahamsson-skanetrafiken marked this conversation as resolved.
Show resolved Hide resolved

return mBuilder.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex;
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 @@ -83,25 +84,12 @@ public void linkTransitStops(Graph graph, TransitModel transitModel) {
LOG.info(progress.startMessage());

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

if (OTPFeature.FlexRouting.isOn()) {
stopLocationsUsedForFlexTrips =
transitModel
.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(transitModel);
}

Set<StopLocation> stopLocationsUsedForCarsAllowedTrips = transitModel.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 @@ -116,7 +104,10 @@ public void linkTransitStops(Graph graph, TransitModel transitModel) {
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 @@ -335,6 +326,24 @@ private VehicleParking removeVehicleParkingEntranceVertexFromGraph(
}
}

private Set<StopLocation> getStopLocationsUsedForFlexTrips(TransitModel transitModel) {
Set<StopLocation> stopLocations = transitModel
.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
habrahamsson-skanetrafiken marked this conversation as resolved.
Show resolved Hide resolved
*/
public enum CarAccess {
UNKNOWN,
NOT_ALLOWED,
ALLOWED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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 Down Expand Up @@ -46,6 +47,7 @@ public final class Trip extends AbstractTransitEntity<Trip, TripBuilder> impleme

private final Direction direction;
private final BikeAccess bikesAllowed;
private final CarAccess carsAllowed;
private final Accessibility wheelchairBoarding;

private final String gtfsBlockId;
Expand All @@ -65,6 +67,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 @@ -150,6 +153,11 @@ public BikeAccess getBikesAllowed() {
return bikesAllowed;
}

@Nonnull
habrahamsson-skanetrafiken marked this conversation as resolved.
Show resolved Hide resolved
public CarAccess getCarsAllowed() {
return carsAllowed;
}

@Nonnull
public Accessibility getWheelchairBoarding() {
return wheelchairBoarding;
Expand Down Expand Up @@ -213,6 +221,7 @@ public boolean sameAs(@Nonnull 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
Loading