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 7 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 @@ -20,7 +20,9 @@
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLCarsAllowed;
import org.opentripplanner.apis.gtfs.mapping.BikesAllowedMapper;
import org.opentripplanner.apis.gtfs.mapping.CarsAllowedMapper;
import org.opentripplanner.apis.gtfs.model.TripOccupancy;
import org.opentripplanner.apis.support.SemanticHash;
import org.opentripplanner.framework.time.ServiceDateUtils;
Expand Down Expand Up @@ -172,6 +174,11 @@ public DataFetcher<GraphQLBikesAllowed> bikesAllowed() {
return environment -> BikesAllowedMapper.map(getSource(environment).getBikesAllowed());
}

@Override
public DataFetcher<GraphQLCarsAllowed> carsAllowed() {
return environment -> CarsAllowedMapper.map(getSource(environment).getCarsAllowed());
}

@Override
public DataFetcher<String> blockId() {
return environment -> getSource(environment).getGtfsBlockId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAlertEffectType;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLAlertSeverityLevelType;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLCarsAllowed;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLInputField;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLOccupancyStatus;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLRelativeDirection;
Expand Down Expand Up @@ -1148,6 +1149,8 @@ public interface GraphQLTrip {

public DataFetcher<String> blockId();

public DataFetcher<GraphQLCarsAllowed> carsAllowed();

public DataFetcher<TripTimeOnDate> departureStoptime();

public DataFetcher<String> directionId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,12 @@ public void setGraphQLBannedNetworks(List<String> bannedNetworks) {
}
}

public enum GraphQLCarsAllowed {
ALLOWED,
NOT_ALLOWED,
NO_INFORMATION,
}

public static class GraphQLCyclingOptimizationInput {

private GraphQLTriangleCyclingFactorsInput triangle;
Expand Down Expand Up @@ -1492,6 +1498,7 @@ public enum GraphQLPlanAccessMode {
BICYCLE,
BICYCLE_PARKING,
BICYCLE_RENTAL,
CAR,
CAR_DROP_OFF,
CAR_PARKING,
CAR_RENTAL,
Expand Down Expand Up @@ -1575,6 +1582,7 @@ public enum GraphQLPlanDirectMode {
public enum GraphQLPlanEgressMode {
BICYCLE,
BICYCLE_RENTAL,
CAR,
CAR_PICKUP,
CAR_RENTAL,
FLEX,
Expand Down Expand Up @@ -1877,6 +1885,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 @@ -53,6 +53,7 @@ config:
RentalVehicle: org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle#VehicleRentalVehicle
VehicleRentalUris: org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris#VehicleRentalStationUris
BikesAllowed: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed#GraphQLBikesAllowed
CarsAllowed: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLCarsAllowed#GraphQLCarsAllowed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now removed

BookingInfo: org.opentripplanner.transit.model.timetable.booking.BookingInfo#BookingInfo
BookingTime: org.opentripplanner.transit.model.timetable.booking.BookingTime#BookingTime
CarPark: org.opentripplanner.routing.vehicle_parking.VehicleParking#VehicleParking
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.opentripplanner.apis.gtfs.mapping;

import javax.annotation.Nonnull;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLCarsAllowed;
import org.opentripplanner.transit.model.network.CarAccess;

public class CarsAllowedMapper {

@Nonnull
public static GraphQLCarsAllowed map(@Nonnull CarAccess carsAllowed) {
habrahamsson-skanetrafiken marked this conversation as resolved.
Show resolved Hide resolved
return switch (carsAllowed) {
case UNKNOWN -> GraphQLCarsAllowed.NO_INFORMATION;
case ALLOWED -> GraphQLCarsAllowed.ALLOWED;
case NOT_ALLOWED -> GraphQLCarsAllowed.NOT_ALLOWED;
};
}
}
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,7 @@ 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);
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 @@ -102,6 +103,31 @@ public void linkTransitStops(Graph graph, TransitModel transitModel) {
);
}

// The stops that are used by transit capable of transporting cars need to be connected to the road network (e.g. car ferries).
Set<StopLocation> stopLocationsUsedForCarsAllowedTrips = Set.of();
stopLocationsUsedForCarsAllowedTrips =
transitModel
.getAllTripPatterns()
.stream()
.filter(t ->
t
.getScheduledTimetable()
.getTripTimes()
.stream()
.anyMatch(tt -> tt.getTrip().getCarsAllowed() == CarAccess.ALLOWED)
)
.flatMap(t -> t.getStops().stream())
.collect(Collectors.toSet());

stopLocationsUsedForCarsAllowedTrips.addAll(
stopLocationsUsedForCarsAllowedTrips
.stream()
.filter(GroupStop.class::isInstance)
.map(GroupStop.class::cast)
.flatMap(g -> g.getChildLocations().stream().filter(RegularStop.class::isInstance))
.toList()
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you extract that into a method and convert the line comments to Javadoc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, can also extract a method for the flex stops?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created methods for both in the transitModel. This will also be useful later when working on the transferRequest stuff related to cars on transit


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 +142,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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();
switch (carsAllowed) {
case 1:
return CarAccess.ALLOWED;
case 2:
return CarAccess.NOT_ALLOWED;
default:
return CarAccess.UNKNOWN;
}
Copy link
Member

@leonardehrenfried leonardehrenfried Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
switch (carsAllowed) {
case 1:
return CarAccess.ALLOWED;
case 2:
return CarAccess.NOT_ALLOWED;
default:
return CarAccess.UNKNOWN;
}
return switch (carsAllowed) {
case 1 -> CarAccess.ALLOWED;
case 2 -> CarAccess.NOT_ALLOWED;
default -> CarAccess.UNKNOWN;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to this

}
}
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 All @@ -83,6 +89,10 @@ public static BikeAccess bikeAccessForTrip(Trip trip) {
return trip.getRoute().getBikesAllowed();
}

public static CarAccess carAccessForTrip(Trip trip) {
return trip.getCarsAllowed();
}
habrahamsson-skanetrafiken marked this conversation as resolved.
Show resolved Hide resolved

@Override
public boolean tripPatternPredicate(TripPatternForDate tripPatternForDate) {
for (TransitFilter filter : filters) {
Expand All @@ -103,6 +113,12 @@ public boolean tripTimesPredicate(TripTimes tripTimes, boolean withFilters) {
}
}

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

if (wheelchairEnabled) {
if (
wheelchairPreferences.trip().onlyConsiderAccessible() &&
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
@@ -0,0 +1,11 @@
package org.opentripplanner.transit.model.network;

/**
* 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 All @@ -33,6 +34,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 @@ -52,6 +54,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 @@ -137,6 +140,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 @@ -200,6 +208,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
Loading
Loading