getStopsInRadius(
new Coordinate(maxLon, maxLat)
);
- var stops = transitService().findRegularStop(envelope);
+ var stops = transitService().findRegularStops(envelope);
return stops
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java
index bfac498ff8c..6248f71e214 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RequestToPreferencesMapper.java
@@ -2,7 +2,7 @@
import jakarta.validation.constraints.NotNull;
import java.util.function.Consumer;
-import org.opentripplanner.ext.restapi.mapping.LegacyBicycleOptimizeType;
+import org.opentripplanner.ext.restapi.mapping.LegacyVehicleRoutingOptimizeType;
import org.opentripplanner.framework.lang.ObjectUtils;
import org.opentripplanner.routing.algorithm.filterchain.api.TransitGeneralizedCostFilterParams;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;
@@ -32,6 +32,7 @@ void map() {
mapCar();
mapWalk();
mapBike();
+ mapScooter();
var boardAndAlightSlack = mapTransit();
@@ -69,10 +70,10 @@ private void mapBike() {
setIfNotNull(req.bikeBoardCost, bike::withBoardCost);
setIfNotNull(
req.bikeOptimizeType,
- optimizeType -> bike.withOptimizeType(LegacyBicycleOptimizeType.map(optimizeType))
+ optimizeType -> bike.withOptimizeType(LegacyVehicleRoutingOptimizeType.map(optimizeType))
);
- if (req.bikeOptimizeType == LegacyBicycleOptimizeType.TRIANGLE) {
+ if (req.bikeOptimizeType == LegacyVehicleRoutingOptimizeType.TRIANGLE) {
bike.withOptimizeTriangle(triangle -> {
setIfNotNull(req.triangleTimeFactor, triangle::withTime);
setIfNotNull(req.triangleSlopeFactor, triangle::withSlope);
@@ -89,12 +90,33 @@ private void mapBike() {
bike.withWalking(walk -> {
setIfNotNull(req.bikeWalkingSpeed, walk::withSpeed);
setIfNotNull(req.bikeWalkingReluctance, walk::withReluctance);
- setIfNotNull(req.bikeSwitchTime, walk::withHopTime);
- setIfNotNull(req.bikeSwitchCost, walk::withHopCost);
+ setIfNotNull(req.bikeSwitchTime, walk::withMountDismountTime);
+ setIfNotNull(req.bikeSwitchCost, walk::withMountDismountCost);
});
});
}
+ private void mapScooter() {
+ preferences.withScooter(scooter -> {
+ setIfNotNull(req.bikeSpeed, scooter::withSpeed);
+ setIfNotNull(req.bikeReluctance, scooter::withReluctance);
+ setIfNotNull(
+ req.bikeOptimizeType,
+ optimizeType -> scooter.withOptimizeType(LegacyVehicleRoutingOptimizeType.map(optimizeType))
+ );
+
+ if (req.bikeOptimizeType == LegacyVehicleRoutingOptimizeType.TRIANGLE) {
+ scooter.withOptimizeTriangle(triangle -> {
+ setIfNotNull(req.triangleTimeFactor, triangle::withTime);
+ setIfNotNull(req.triangleSlopeFactor, triangle::withSlope);
+ setIfNotNull(req.triangleSafetyFactor, triangle::withSafety);
+ });
+ }
+
+ scooter.withRental(this::mapRental);
+ });
+ }
+
private BoardAndAlightSlack mapTransit() {
preferences.withTransit(tr -> {
setIfNotNull(req.boardSlack, tr::withDefaultBoardSlackSec);
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
index 1c234e3ff9e..1d985f0e555 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
@@ -20,7 +20,7 @@
import javax.xml.datatype.XMLGregorianCalendar;
import org.opentripplanner.api.parameter.QualifiedModeSet;
import org.opentripplanner.ext.dataoverlay.api.DataOverlayParameters;
-import org.opentripplanner.ext.restapi.mapping.LegacyBicycleOptimizeType;
+import org.opentripplanner.ext.restapi.mapping.LegacyVehicleRoutingOptimizeType;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.lang.StringUtils;
import org.opentripplanner.framework.time.DurationUtils;
@@ -239,17 +239,15 @@ public abstract class RoutingResource {
protected Double triangleTimeFactor;
/**
- * The set of characteristics that the user wants to optimize for. @See OptimizeType.
+ * The set of characteristics that the user wants to optimize for in bicycle and scooter routing.
+ * @See OptimizeType.
*
* @deprecated TODO OTP2 this should be completely removed and done only with individual cost
* parameters
- * Also: apparently OptimizeType only affects BICYCLE mode traversal of
- * street segments. If this is the case it should be very well
- * documented and carried over into the Enum name.
*/
@Deprecated
@QueryParam("optimize")
- protected LegacyBicycleOptimizeType bikeOptimizeType;
+ protected LegacyVehicleRoutingOptimizeType bikeOptimizeType;
/**
* The set of modes that a user is willing to use, with qualifiers stating whether vehicles should
diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java
index 8fcba82a357..a287e6a7d66 100644
--- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java
+++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/DecorateConsolidatedStopNames.java
@@ -7,9 +7,9 @@
import org.opentripplanner.routing.algorithm.filterchain.framework.spi.ItineraryDecorator;
/**
- * A decorating filter that checks if a transit leg contains any primary stops and if it does,
- * then replaces it with the secondary, agency-specific stop name. This is so that the in-vehicle
- * display matches what OTP returns as a board/alight stop name.
+ * A decorating filter that checks if a transit leg contains any consolidated stops and if it does,
+ * then replaces it with the appropriate, agency-specific stop name. This is so that the physical
+ * signage and in-vehicle display matches what OTP returns as a board/alight stop name.
*/
public class DecorateConsolidatedStopNames implements ItineraryDecorator {
@@ -21,31 +21,45 @@ public DecorateConsolidatedStopNames(StopConsolidationService service) {
@Override
public void decorate(Itinerary itinerary) {
- replacePrimaryNamesWithSecondary(itinerary);
+ replaceConsolidatedStops(itinerary);
}
/**
- * If the itinerary has a from/to that is the primary stop of a {@link org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup}
- * then we replace its name with the secondary name of the agency that is
- * operating the route, so that the name in the result matches the name in the in-vehicle
- * display.
+ * If the itinerary has a "from" stop that is the secondary stop of a
+ * {@link org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup}
+ * then we replace its name with the primary name of the agency that is
+ * operating the route, so that the name in the result matches the physical signage on the stop.
+ *
+ * If the leg has a "to" stop that is a primary stop, then we don't want to show the stop that's on
+ * the signage but what is shown _inside_ the vehicle. That's why we use the agency-specific (aka
+ * secondary) stop.
+ *
+ * This follows the somewhat idiosyncratic logic of the consolidated stops feature.
*/
- private void replacePrimaryNamesWithSecondary(Itinerary i) {
+ private void replaceConsolidatedStops(Itinerary i) {
i.transformTransitLegs(leg -> {
if (leg instanceof ScheduledTransitLeg stl && needsToRenameStops(stl)) {
var agency = leg.getAgency();
- return new ConsolidatedStopLeg(
- stl,
- service.agencySpecificName(stl.getFrom().stop, agency),
- service.agencySpecificName(stl.getTo().stop, agency)
- );
+ // to show the name on the stop signage we use the primary stop's name
+ var from = service.primaryStop(stl.getFrom().stop.getId());
+ // to show the name that's on the display inside the vehicle we use the agency-specific name
+ var to = service.agencySpecificStop(stl.getTo().stop, agency);
+ return new ConsolidatedStopLeg(stl, from, to);
} else {
return leg;
}
});
}
+ /**
+ * Figures out if the from/to stops are part of a consolidated stop group and therefore
+ * some stops need to be replaced.
+ *
+ * Please consult the Javadoc of {@link DecorateConsolidatedStopNames#replaceConsolidatedStops(Itinerary)}
+ * for details of this idiosyncratic business logic and in particular why the logic is not the same
+ * for the from/to stops.
+ */
private boolean needsToRenameStops(ScheduledTransitLeg stl) {
- return (service.isPrimaryStop(stl.getFrom().stop) || service.isPrimaryStop(stl.getTo().stop));
+ return (service.isSecondaryStop(stl.getFrom().stop) || service.isPrimaryStop(stl.getTo().stop));
}
}
diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java
index 2418d8f5625..a829a905e27 100644
--- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java
+++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/StopConsolidationService.java
@@ -2,7 +2,6 @@
import java.util.List;
import org.opentripplanner.ext.stopconsolidation.model.StopReplacement;
-import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.site.StopLocation;
@@ -24,13 +23,23 @@ public interface StopConsolidationService {
*/
boolean isPrimaryStop(StopLocation stop);
+ /**
+ * Is the given stop a secondary stop as defined by the stop consolidation configuration?
+ */
+ boolean isSecondaryStop(StopLocation stop);
+
/**
* Are any stop consolidations defined?
*/
boolean isActive();
/**
- * For a given primary stop look up the name as it was originally defined in the agency's feed.
+ * For a given primary stop look up secondary feed as it was originally defined in the agency's feed.
+ */
+ StopLocation agencySpecificStop(StopLocation stop, Agency agency);
+
+ /**
+ * For a given stop id return the primary stop if it is part of a consolidated stop group.
*/
- I18NString agencySpecificName(StopLocation stop, Agency agency);
+ StopLocation primaryStop(FeedScopedId id);
}
diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java
index 54bd960d078..51a57028121 100644
--- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java
+++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/internal/DefaultStopConsolidationService.java
@@ -2,14 +2,15 @@
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.stream.Stream;
+import javax.annotation.Nonnull;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationRepository;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
+import org.opentripplanner.ext.stopconsolidation.model.ConsolidatedStopGroup;
import org.opentripplanner.ext.stopconsolidation.model.StopReplacement;
-import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.organization.Agency;
-import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.TransitModel;
import org.slf4j.Logger;
@@ -61,26 +62,46 @@ public boolean isPrimaryStop(StopLocation stop) {
return repo.groups().stream().anyMatch(r -> r.primary().equals(stop.getId()));
}
+ @Override
+ public boolean isSecondaryStop(StopLocation stop) {
+ return repo.groups().stream().anyMatch(r -> r.secondaries().contains(stop.getId()));
+ }
+
@Override
public boolean isActive() {
return !repo.groups().isEmpty();
}
@Override
- public I18NString agencySpecificName(StopLocation stop, Agency agency) {
+ public StopLocation agencySpecificStop(StopLocation stop, Agency agency) {
if (agency.getId().getFeedId().equals(stop.getId().getFeedId())) {
- return stop.getName();
+ return stop;
} else {
- return repo
- .groups()
- .stream()
- .filter(r -> r.primary().equals(stop.getId()))
- .flatMap(g -> g.secondaries().stream())
- .filter(secondary -> secondary.getFeedId().equals(agency.getId().getFeedId()))
- .findAny()
- .map(id -> transitModel.getStopModel().getRegularStop(id))
- .map(RegularStop::getName)
- .orElseGet(stop::getName);
+ return findAgencySpecificStop(stop, agency).orElse(stop);
}
}
+
+ @Nonnull
+ private Optional findAgencySpecificStop(StopLocation stop, Agency agency) {
+ return repo
+ .groups()
+ .stream()
+ .filter(r -> r.primary().equals(stop.getId()))
+ .flatMap(g -> g.secondaries().stream())
+ .filter(secondary -> secondary.getFeedId().equals(agency.getId().getFeedId()))
+ .findAny()
+ .map(id -> transitModel.getStopModel().getRegularStop(id));
+ }
+
+ @Override
+ public StopLocation primaryStop(FeedScopedId id) {
+ var primaryId = repo
+ .groups()
+ .stream()
+ .filter(g -> g.secondaries().contains(id))
+ .map(ConsolidatedStopGroup::primary)
+ .findAny()
+ .orElse(id);
+ return transitModel.getStopModel().getRegularStop(primaryId);
+ }
}
diff --git a/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java b/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java
index e201c7ed805..39f06bd6347 100644
--- a/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java
+++ b/src/ext/java/org/opentripplanner/ext/stopconsolidation/model/ConsolidatedStopLeg.java
@@ -1,28 +1,28 @@
package org.opentripplanner.ext.stopconsolidation.model;
-import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.model.plan.Place;
import org.opentripplanner.model.plan.ScheduledTransitLeg;
import org.opentripplanner.model.plan.ScheduledTransitLegBuilder;
+import org.opentripplanner.transit.model.site.StopLocation;
public class ConsolidatedStopLeg extends ScheduledTransitLeg {
- private final I18NString fromName;
- private final I18NString toName;
+ private final StopLocation from;
+ private final StopLocation to;
- public ConsolidatedStopLeg(ScheduledTransitLeg original, I18NString fromName, I18NString toName) {
+ public ConsolidatedStopLeg(ScheduledTransitLeg original, StopLocation from, StopLocation to) {
super(new ScheduledTransitLegBuilder<>(original));
- this.fromName = fromName;
- this.toName = toName;
+ this.from = from;
+ this.to = to;
}
@Override
public Place getFrom() {
- return Place.forStop(super.getFrom().stop, fromName);
+ return Place.forStop(from);
}
@Override
public Place getTo() {
- return Place.forStop(super.getTo().stop, toName);
+ return Place.forStop(to);
}
}
diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java
index 772db7394f3..a1e9a83c85a 100644
--- a/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java
+++ b/src/ext/java/org/opentripplanner/ext/vectortiles/VectorTilesResource.java
@@ -28,6 +28,7 @@
import org.opentripplanner.inspector.vector.LayerBuilder;
import org.opentripplanner.inspector.vector.LayerParameters;
import org.opentripplanner.inspector.vector.VectorTileResponseFactory;
+import org.opentripplanner.model.FeedInfo;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
@Path("/routers/{ignoreRouterId}/vectorTiles")
@@ -81,13 +82,6 @@ public TileJson getTileJson(
@PathParam("layers") String requestedLayers
) {
var envelope = serverContext.worldEnvelopeService().envelope().orElseThrow();
- var feedInfos = serverContext
- .transitService()
- .getFeedIds()
- .stream()
- .map(serverContext.transitService()::getFeedInfo)
- .filter(Predicate.not(Objects::isNull))
- .toList();
List rLayers = Arrays.asList(requestedLayers.split(","));
@@ -101,7 +95,24 @@ public TileJson getTileJson(
TileJson.urlWithDefaultPath(uri, headers, rLayers, ignoreRouterId, "vectorTiles")
);
- return new TileJson(url, envelope, feedInfos);
+ return serverContext
+ .vectorTileConfig()
+ .attribution()
+ .map(attr -> new TileJson(url, envelope, attr))
+ .orElseGet(() -> {
+ var feedInfos = getFeedInfos();
+ return new TileJson(url, envelope, feedInfos);
+ });
+ }
+
+ private List getFeedInfos() {
+ return serverContext
+ .transitService()
+ .getFeedIds()
+ .stream()
+ .map(serverContext.transitService()::getFeedInfo)
+ .filter(Predicate.not(Objects::isNull))
+ .toList();
}
private static LayerBuilder> crateLayerBuilder(
diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
index 6d15816669e..2ad85587fe5 100644
--- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
+++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
@@ -37,7 +37,7 @@ public StopsLayerBuilder(
protected List getGeometries(Envelope query) {
return transitService
- .findRegularStop(query)
+ .findRegularStops(query)
.stream()
.map(stop -> {
Geometry point = stop.getGeometry();
diff --git a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java b/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java
deleted file mode 100644
index ad503b1212d..00000000000
--- a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/BikeToStopSkipEdgeStrategy.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.opentripplanner.ext.vehicletostopheuristics;
-
-import java.util.Collection;
-import java.util.function.Function;
-import org.opentripplanner.astar.spi.SkipEdgeStrategy;
-import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.search.state.State;
-import org.opentripplanner.transit.model.network.BikeAccess;
-import org.opentripplanner.transit.model.site.RegularStop;
-import org.opentripplanner.transit.model.timetable.Trip;
-
-/**
- * When wanting to take a bike onto transit we want to improve the performance by limiting the
- * number of accesses to those stops which actually have trips where you can take the bike on. Once
- * we have reached enough of trips we skip all further edges.
- */
-public class BikeToStopSkipEdgeStrategy implements SkipEdgeStrategy {
-
- private static final int LIMIT = 100;
- private static final double MAX_FACTOR = 1.2;
-
- private final Function> getTripsForStop;
-
- int numberOfBikeableTripsReached = 0;
- double distanceLimit = Double.MAX_VALUE;
-
- public BikeToStopSkipEdgeStrategy(Function> getTripsForStop) {
- this.getTripsForStop = getTripsForStop;
- }
-
- public static boolean bikeAccessForTrip(Trip trip) {
- if (trip.getBikesAllowed() != BikeAccess.UNKNOWN) {
- return trip.getBikesAllowed() == BikeAccess.ALLOWED;
- }
-
- return trip.getRoute().getBikesAllowed() == BikeAccess.ALLOWED;
- }
-
- @Override
- public boolean shouldSkipEdge(State current, Edge edge) {
- if (
- current.getVertex() instanceof TransitStopVertex stopVertex &&
- distanceLimit == Double.MAX_VALUE
- ) {
- numberOfBikeableTripsReached +=
- getTripsForStop
- .apply(stopVertex.getStop())
- .stream()
- .filter(BikeToStopSkipEdgeStrategy::bikeAccessForTrip)
- .count();
- if (numberOfBikeableTripsReached >= LIMIT) {
- distanceLimit = current.getWalkDistance() * MAX_FACTOR;
- }
- }
- return current.getWalkDistance() > distanceLimit;
- }
-}
diff --git a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java b/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java
deleted file mode 100644
index 3eb466f97b9..00000000000
--- a/src/ext/java/org/opentripplanner/ext/vehicletostopheuristics/VehicleToStopSkipEdgeStrategy.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package org.opentripplanner.ext.vehicletostopheuristics;
-
-import static org.opentripplanner.routing.api.request.StreetMode.BIKE_RENTAL;
-import static org.opentripplanner.routing.api.request.StreetMode.BIKE_TO_PARK;
-import static org.opentripplanner.routing.api.request.StreetMode.CAR_HAILING;
-import static org.opentripplanner.routing.api.request.StreetMode.CAR_PICKUP;
-import static org.opentripplanner.routing.api.request.StreetMode.CAR_RENTAL;
-import static org.opentripplanner.routing.api.request.StreetMode.CAR_TO_PARK;
-import static org.opentripplanner.routing.api.request.StreetMode.SCOOTER_RENTAL;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import org.opentripplanner.astar.spi.SkipEdgeStrategy;
-import org.opentripplanner.routing.api.request.StreetMode;
-import org.opentripplanner.routing.api.request.request.filter.TransitFilter;
-import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.search.state.State;
-import org.opentripplanner.transit.model.basic.TransitMode;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.TripPattern;
-import org.opentripplanner.transit.model.site.RegularStop;
-
-/**
- * This strategy terminates when enough "important" stops are found.
- *
- * The definition of important is a stop where many routes of mode RAIL, SUBWAY or FERRY depart.
- *
- * This means that the search radius scales with density of "important" stops:
- *
- *
in a city the radius is quite small
- * in more rural regions the radius is bigger and stops further away are considered
- *
- * The strategy is useful when you want to limit the number of accesses of Park+Ride, Bike+Ride and
- * Bike+Transit: it improves both performance the quality of results.
- *
- * {@see https://github.com/opentripplanner/OpenTripPlanner/pull/3906}
- */
-public class VehicleToStopSkipEdgeStrategy implements SkipEdgeStrategy {
-
- public static final Set applicableModes = Set.of(
- BIKE_TO_PARK,
- BIKE_RENTAL,
- CAR_TO_PARK,
- CAR_PICKUP,
- CAR_HAILING,
- CAR_RENTAL,
- SCOOTER_RENTAL
- );
- private final Function> getPatternsForStop;
- private final int maxScore;
- private final List filters;
- private double sumOfScores;
-
- private final Set stopsCounted = new HashSet<>();
-
- public VehicleToStopSkipEdgeStrategy(
- Function> getPatternsForStop,
- Collection filters
- ) {
- this.filters = new ArrayList<>(filters);
- this.maxScore = 300;
- this.getPatternsForStop = getPatternsForStop;
- }
-
- @Override
- public boolean shouldSkipEdge(State current, Edge edge) {
- if (current.currentMode().isWalking()) {
- if (
- current.getVertex() instanceof TransitStopVertex stopVertex &&
- !stopsCounted.contains(stopVertex.getStop().getId())
- ) {
- // TODO: 2022-12-05 filters: check performance on that and verify that this is right. Previously we were filtering just on modes
- var stop = stopVertex.getStop();
-
- // Not using streams. Performance is important here
- var patterns = getPatternsForStop.apply(stop);
- var score = 0;
- for (var pattern : patterns) {
- for (var filter : filters) {
- if (filter.matchTripPattern(pattern)) {
- score += VehicleToStopSkipEdgeStrategy.score(pattern.getMode());
- break;
- }
- }
- }
-
- stopsCounted.add(stop.getId());
-
- sumOfScores = sumOfScores + score;
- }
- return false;
- } else {
- return sumOfScores >= maxScore;
- }
- }
-
- private static int score(TransitMode mode) {
- return switch (mode) {
- case RAIL, FERRY, SUBWAY -> 20;
- case BUS -> 1;
- default -> 2;
- };
- }
-}
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
index d749987384c..a4f5cb00e2f 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
@@ -21,7 +21,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.locationtech.jts.geom.Coordinate;
@@ -710,7 +709,7 @@ public DataFetcher> stopsByBbox() {
);
Stream stopStream = getTransitService(environment)
- .findRegularStop(envelope)
+ .findRegularStops(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()));
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json
index 6a2eb3343b9..db865eaa003 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/package.json
@@ -10,8 +10,8 @@
},
"license": "LGPL-3.0",
"dependencies": {
- "@graphql-codegen/add": "5.0.0",
- "@graphql-codegen/cli": "5.0.0",
+ "@graphql-codegen/add": "5.0.2",
+ "@graphql-codegen/cli": "5.0.2",
"@graphql-codegen/java": "4.0.0",
"@graphql-codegen/java-resolvers": "3.0.0",
"graphql": "16.8.1"
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock
index 7e3c815cfb1..fffe602db18 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/yarn.lock
@@ -264,7 +264,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
-"@babel/helper-plugin-utils@^7.22.5":
+"@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
@@ -699,24 +699,25 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
-"@graphql-codegen/add@5.0.0":
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.0.tgz#578ebaf4fa87c1e934c381cd679bcedcf79feaba"
- integrity sha512-ynWDOsK2yxtFHwcJTB9shoSkUd7YXd6ZE57f0nk7W5cu/nAgxZZpEsnTPEpZB/Mjf14YRGe2uJHQ7AfElHjqUQ==
+"@graphql-codegen/add@5.0.2", "@graphql-codegen/add@^5.0.2":
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-5.0.2.tgz#71b3ae0465a4537172dddb84531b6967ca5545f2"
+ integrity sha512-ouBkSvMFUhda5VoKumo/ZvsZM9P5ZTyDsI8LW18VxSNWOjrTeLXBWHG8Gfaai0HwhflPtCYVABbriEcOmrRShQ==
dependencies:
- "@graphql-codegen/plugin-helpers" "^5.0.0"
- tslib "~2.5.0"
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ tslib "~2.6.0"
-"@graphql-codegen/cli@5.0.0":
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-5.0.0.tgz#761dcf08cfee88bbdd9cdf8097b2343445ec6f0a"
- integrity sha512-A7J7+be/a6e+/ul2KI5sfJlpoqeqwX8EzktaKCeduyVKgOLA6W5t+NUGf6QumBDXU8PEOqXk3o3F+RAwCWOiqA==
+"@graphql-codegen/cli@5.0.2":
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-5.0.2.tgz#07ff691c16da4c3dcc0e1995d3231530379ab317"
+ integrity sha512-MBIaFqDiLKuO4ojN6xxG9/xL9wmfD3ZjZ7RsPjwQnSHBCUXnEkdKvX+JVpx87Pq29Ycn8wTJUguXnTZ7Di0Mlw==
dependencies:
"@babel/generator" "^7.18.13"
"@babel/template" "^7.18.10"
"@babel/types" "^7.18.13"
- "@graphql-codegen/core" "^4.0.0"
- "@graphql-codegen/plugin-helpers" "^5.0.1"
+ "@graphql-codegen/client-preset" "^4.2.2"
+ "@graphql-codegen/core" "^4.0.2"
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
"@graphql-tools/apollo-engine-loader" "^8.0.0"
"@graphql-tools/code-file-loader" "^8.0.0"
"@graphql-tools/git-loader" "^8.0.0"
@@ -747,15 +748,45 @@
yaml "^2.3.1"
yargs "^17.0.0"
-"@graphql-codegen/core@^4.0.0":
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-4.0.0.tgz#b29c911746a532a675e33720acb4eb2119823e01"
- integrity sha512-JAGRn49lEtSsZVxeIlFVIRxts2lWObR+OQo7V2LHDJ7ohYYw3ilv7nJ8pf8P4GTg/w6ptcYdSdVVdkI8kUHB/Q==
+"@graphql-codegen/client-preset@^4.2.2":
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/client-preset/-/client-preset-4.2.2.tgz#545c62789a5687bee5df8b4738b4911e72ea8051"
+ integrity sha512-DF9pNWj3TEdA90E9FH5SsUIqiZfr872vqaQOspLVuVXGsaDx8F/JLLzaN+7ucmoo0ff/bLW8munVXYXTmgwwEA==
dependencies:
- "@graphql-codegen/plugin-helpers" "^5.0.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/template" "^7.20.7"
+ "@graphql-codegen/add" "^5.0.2"
+ "@graphql-codegen/gql-tag-operations" "4.0.4"
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-codegen/typed-document-node" "^5.0.4"
+ "@graphql-codegen/typescript" "^4.0.4"
+ "@graphql-codegen/typescript-operations" "^4.1.2"
+ "@graphql-codegen/visitor-plugin-common" "^4.1.2"
+ "@graphql-tools/documents" "^1.0.0"
+ "@graphql-tools/utils" "^10.0.0"
+ "@graphql-typed-document-node/core" "3.2.0"
+ tslib "~2.6.0"
+
+"@graphql-codegen/core@^4.0.2":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-4.0.2.tgz#7e6ec266276f54bbf02f60599d9e518f4a59d85e"
+ integrity sha512-IZbpkhwVqgizcjNiaVzNAzm/xbWT6YnGgeOLwVjm4KbJn3V2jchVtuzHH09G5/WkkLSk2wgbXNdwjM41JxO6Eg==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
"@graphql-tools/schema" "^10.0.0"
"@graphql-tools/utils" "^10.0.0"
- tslib "~2.5.0"
+ tslib "~2.6.0"
+
+"@graphql-codegen/gql-tag-operations@4.0.4":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.4.tgz#572be5db804af5efdc3ca24e4bcac815448730c5"
+ integrity sha512-dypul0iDLjb07yv+/cRb6qPbn42cFPcwlsJertVl9G6qkS4+3V4806WwSfUht4QVMWnvGfgDkJJqG0yUVKOHwA==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-codegen/visitor-plugin-common" "4.1.2"
+ "@graphql-tools/utils" "^10.0.0"
+ auto-bind "~4.0.0"
+ tslib "~2.6.0"
"@graphql-codegen/java-common@^3.0.0":
version "3.0.0"
@@ -813,29 +844,59 @@
lodash "~4.17.0"
tslib "~2.4.0"
-"@graphql-codegen/plugin-helpers@^5.0.0":
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.0.tgz#40c18217454af5cf8317e5f46cf4d38e8cc78ae4"
- integrity sha512-suL2ZMkBAU2a4YbBHaZvUPsV1z0q3cW6S96Z/eYYfkRIsJoe2vN+wNZ9Xdzmqx0JLmeeFCBSoBGC0imFyXlkDQ==
+"@graphql-codegen/plugin-helpers@^5.0.3":
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.3.tgz#7027b9d911d7cb594663590fcf5d63e9cf7ec2ff"
+ integrity sha512-yZ1rpULIWKBZqCDlvGIJRSyj1B2utkEdGmXZTBT/GVayP4hyRYlkd36AJV/LfEsVD8dnsKL5rLz2VTYmRNlJ5Q==
dependencies:
"@graphql-tools/utils" "^10.0.0"
change-case-all "1.0.15"
common-tags "1.8.2"
import-from "4.0.0"
lodash "~4.17.0"
- tslib "~2.5.0"
+ tslib "~2.6.0"
-"@graphql-codegen/plugin-helpers@^5.0.1":
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-5.0.1.tgz#e2429fcfba3f078d5aa18aa062d46c922bbb0d55"
- integrity sha512-6L5sb9D8wptZhnhLLBcheSPU7Tg//DGWgc5tQBWX46KYTOTQHGqDpv50FxAJJOyFVJrveN9otWk9UT9/yfY4ww==
+"@graphql-codegen/schema-ast@^4.0.2":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz#aeaa104e4555cca73a058f0a9350b4b0e290b377"
+ integrity sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==
dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
"@graphql-tools/utils" "^10.0.0"
+ tslib "~2.6.0"
+
+"@graphql-codegen/typed-document-node@^5.0.4":
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.4.tgz#06e286caacdd66c3566f98433dcb8f1a9c9a9f1d"
+ integrity sha512-t66Z6erQ4Dh1j6f9pRZmc8uYtHoUI3A49tLmJAlg9/3IV0kCmwrWKJut/G8SeOefDLG8cXBTVtI/YuZOe1Te+w==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-codegen/visitor-plugin-common" "4.1.2"
+ auto-bind "~4.0.0"
change-case-all "1.0.15"
- common-tags "1.8.2"
- import-from "4.0.0"
- lodash "~4.17.0"
- tslib "~2.5.0"
+ tslib "~2.6.0"
+
+"@graphql-codegen/typescript-operations@^4.1.2":
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-4.1.2.tgz#a0f455ae19e16961e5870420ca7515bbe51b5568"
+ integrity sha512-CtCWK+gW7hS+Ely3lohr8CL1HVLswQzMcaUk3k1sxdWCWKTNq7abMsWa31rTVwRCJ+WNEkM/7S8sIBTpEG683A==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-codegen/typescript" "^4.0.4"
+ "@graphql-codegen/visitor-plugin-common" "4.1.2"
+ auto-bind "~4.0.0"
+ tslib "~2.6.0"
+
+"@graphql-codegen/typescript@^4.0.4":
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-4.0.4.tgz#e791c61f675ae454951ea077b0ae519ae352cc3e"
+ integrity sha512-x79CKLfP9UQCX+/I78qxQlMs2Mmq3pF1lKafZo7lAno0f/fvJ+qWUduzdgjRNz+YL+5blGeWcC0pWEDxniO7hw==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-codegen/schema-ast" "^4.0.2"
+ "@graphql-codegen/visitor-plugin-common" "4.1.2"
+ auto-bind "~4.0.0"
+ tslib "~2.6.0"
"@graphql-codegen/visitor-plugin-common@2.13.1":
version "2.13.1"
@@ -853,6 +914,22 @@
parse-filepath "^1.0.2"
tslib "~2.4.0"
+"@graphql-codegen/visitor-plugin-common@4.1.2", "@graphql-codegen/visitor-plugin-common@^4.1.2":
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-4.1.2.tgz#674c5d5813f6c00dd65e1ee148a62536879e65e2"
+ integrity sha512-yk7iEAL1kYZ2Gi/pvVjdsZhul5WsYEM4Zcgh2Ev15VicMdJmPHsMhNUsZWyVJV0CaQCYpNOFlGD/11Ea3pn4GA==
+ dependencies:
+ "@graphql-codegen/plugin-helpers" "^5.0.3"
+ "@graphql-tools/optimize" "^2.0.0"
+ "@graphql-tools/relay-operation-optimizer" "^7.0.0"
+ "@graphql-tools/utils" "^10.0.0"
+ auto-bind "~4.0.0"
+ change-case-all "1.0.15"
+ dependency-graph "^0.11.0"
+ graphql-tag "^2.11.0"
+ parse-filepath "^1.0.2"
+ tslib "~2.6.0"
+
"@graphql-tools/apollo-engine-loader@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-8.0.0.tgz#ac1f351cbe41508411784f25757f5557b0f27489"
@@ -897,6 +974,14 @@
tslib "^2.5.0"
value-or-promise "^1.0.12"
+"@graphql-tools/documents@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@graphql-tools/documents/-/documents-1.0.0.tgz#e3ed97197cc22ec830ca227fd7d17e86d8424bdf"
+ integrity sha512-rHGjX1vg/nZ2DKqRGfDPNC55CWZBMldEVcH+91BThRa6JeT80NqXknffLLEZLRUxyikCfkwMsk6xR3UNMqG0Rg==
+ dependencies:
+ lodash.sortby "^4.7.0"
+ tslib "^2.4.0"
+
"@graphql-tools/executor-graphql-ws@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.0.2.tgz#29890f9370c5bebd4a2380e29904f8eaf9f013ca"
@@ -1036,6 +1121,13 @@
dependencies:
tslib "^2.4.0"
+"@graphql-tools/optimize@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@graphql-tools/optimize/-/optimize-2.0.0.tgz#7a9779d180824511248a50c5a241eff6e7a2d906"
+ integrity sha512-nhdT+CRGDZ+bk68ic+Jw1OZ99YCDIKYA5AlVAnBHJvMawSx9YQqQAIj4refNc1/LRieGiuWvhbG3jvPVYho0Dg==
+ dependencies:
+ tslib "^2.4.0"
+
"@graphql-tools/prisma-loader@^8.0.0":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-8.0.1.tgz#0a013c69b04e0779b5be15757173d458cdf94e35"
@@ -1069,6 +1161,15 @@
"@graphql-tools/utils" "9.1.1"
tslib "^2.4.0"
+"@graphql-tools/relay-operation-optimizer@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-7.0.0.tgz#24367666af87bc5a81748de5e8e9b3c523fd4207"
+ integrity sha512-UNlJi5y3JylhVWU4MBpL0Hun4Q7IoJwv9xYtmAz+CgRa066szzY7dcuPfxrA7cIGgG/Q6TVsKsYaiF4OHPs1Fw==
+ dependencies:
+ "@ardatan/relay-compiler" "12.0.0"
+ "@graphql-tools/utils" "^10.0.0"
+ tslib "^2.4.0"
+
"@graphql-tools/schema@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-10.0.0.tgz#7b5f6b6a59f51c927de8c9069bde4ebbfefc64b3"
@@ -2374,6 +2475,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
+lodash.sortby@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+ integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
+
lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@@ -3049,7 +3155,7 @@ ts-log@^2.2.3:
resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb"
integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w==
-tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.1, tslib@~2.5.0:
+tslib@^2.0.0, tslib@^2.3.1, tslib@^2.4.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java
index 1f53652f7b7..7f4f5200256 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/OptimizationTypeMapper.java
@@ -2,18 +2,18 @@
import javax.annotation.Nonnull;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
public final class OptimizationTypeMapper {
@Nonnull
- public static BicycleOptimizeType map(GraphQLTypes.GraphQLOptimizeType optimizeType) {
+ public static VehicleRoutingOptimizeType map(GraphQLTypes.GraphQLOptimizeType optimizeType) {
return switch (optimizeType) {
- case QUICK -> BicycleOptimizeType.SHORTEST_DURATION;
- case FLAT -> BicycleOptimizeType.FLAT_STREETS;
- case SAFE -> BicycleOptimizeType.SAFE_STREETS;
- case GREENWAYS -> BicycleOptimizeType.SAFEST_STREETS;
- case TRIANGLE -> BicycleOptimizeType.TRIANGLE;
+ case QUICK -> VehicleRoutingOptimizeType.SHORTEST_DURATION;
+ case FLAT -> VehicleRoutingOptimizeType.FLAT_STREETS;
+ case SAFE -> VehicleRoutingOptimizeType.SAFE_STREETS;
+ case GREENWAYS -> VehicleRoutingOptimizeType.SAFEST_STREETS;
+ case TRIANGLE -> VehicleRoutingOptimizeType.TRIANGLE;
};
}
}
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
index 212bfbcf380..4cd6c04b044 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapper.java
@@ -28,7 +28,7 @@
import org.opentripplanner.routing.api.request.preference.VehicleWalkingPreferences;
import org.opentripplanner.routing.api.request.request.filter.SelectRequest;
import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.transit.model.basic.MainAndSubMode;
import org.opentripplanner.transit.model.basic.TransitMode;
@@ -79,7 +79,7 @@ public static RouteRequest toRouteRequest(
)
);
}
- if (bike.optimizeType() == BicycleOptimizeType.TRIANGLE) {
+ if (bike.optimizeType() == VehicleRoutingOptimizeType.TRIANGLE) {
bike.withOptimizeTriangle(triangle -> {
callWith.argument("triangle.timeFactor", triangle::withTime);
callWith.argument("triangle.slopeFactor", triangle::withSlope);
@@ -98,6 +98,28 @@ public static RouteRequest toRouteRequest(
car.withRental(rental -> setRentalPreferences(callWith, request, rental));
});
+ preferences.withScooter(scooter -> {
+ callWith.argument("bikeReluctance", scooter::withReluctance);
+ callWith.argument("bikeSpeed", scooter::withSpeed);
+
+ if (environment.getArgument("optimize") != null) {
+ scooter.withOptimizeType(
+ OptimizationTypeMapper.map(
+ GraphQLTypes.GraphQLOptimizeType.valueOf(environment.getArgument("optimize"))
+ )
+ );
+ }
+ if (scooter.optimizeType() == VehicleRoutingOptimizeType.TRIANGLE) {
+ scooter.withOptimizeTriangle(triangle -> {
+ callWith.argument("triangle.timeFactor", triangle::withTime);
+ callWith.argument("triangle.slopeFactor", triangle::withSlope);
+ callWith.argument("triangle.safetyFactor", triangle::withSafety);
+ });
+ }
+
+ scooter.withRental(rental -> setRentalPreferences(callWith, request, rental));
+ });
+
preferences.withWalk(b -> {
callWith.argument("walkReluctance", b::withReluctance);
callWith.argument("walkSpeed", b::withSpeed);
@@ -340,8 +362,8 @@ private static void setVehicleWalkingPreferences(
) {
callWith.argument("bikeWalkingReluctance", walking::withReluctance);
callWith.argument("bikeWalkingSpeed", walking::withSpeed);
- callWith.argument("bikeSwitchTime", time -> walking.withHopTime((int) time));
- callWith.argument("bikeSwitchCost", cost -> walking.withHopCost((int) cost));
+ callWith.argument("bikeSwitchTime", time -> walking.withMountDismountTime((int) time));
+ callWith.argument("bikeSwitchCost", cost -> walking.withMountDismountCost((int) cost));
}
private static class CallerWithEnvironment {
diff --git a/src/main/java/org/opentripplanner/apis/support/TileJson.java b/src/main/java/org/opentripplanner/apis/support/TileJson.java
index 75aabb2b6c6..1f5c090c16d 100644
--- a/src/main/java/org/opentripplanner/apis/support/TileJson.java
+++ b/src/main/java/org/opentripplanner/apis/support/TileJson.java
@@ -36,15 +36,8 @@ public class TileJson implements Serializable {
public final double[] bounds;
public final double[] center;
- public TileJson(String tileUrl, WorldEnvelope envelope, Collection feedInfos) {
- attribution =
- feedInfos
- .stream()
- .map(feedInfo ->
- "" + feedInfo.getPublisherName() + ""
- )
- .collect(Collectors.joining(", "));
-
+ public TileJson(String tileUrl, WorldEnvelope envelope, String attribution) {
+ this.attribution = attribution;
tiles = new String[] { tileUrl };
bounds =
@@ -59,6 +52,10 @@ public TileJson(String tileUrl, WorldEnvelope envelope, Collection fee
center = new double[] { c.longitude(), c.latitude(), 9 };
}
+ public TileJson(String tileUrl, WorldEnvelope envelope, Collection feedInfos) {
+ this(tileUrl, envelope, attributionFromFeedInfo(feedInfos));
+ }
+
/**
* Creates a vector source layer URL from a hard-coded path plus information from the incoming
* HTTP request.
@@ -96,4 +93,14 @@ public static String urlFromOverriddenBasePath(
String.join(",", layers)
);
}
+
+ private static String attributionFromFeedInfo(Collection feedInfos) {
+ return feedInfos
+ .stream()
+ .map(feedInfo ->
+ "%s".formatted(feedInfo.getPublisherUrl(), feedInfo.getPublisherName())
+ )
+ .distinct()
+ .collect(Collectors.joining(", "));
+ }
}
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java
index 00bf98c703f..638d7783e9a 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java
@@ -687,7 +687,7 @@ private GraphQLSchema create() {
);
return GqlUtil
.getTransitService(environment)
- .findRegularStop(envelope)
+ .findRegularStops(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
.filter(stop ->
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java
index 846437952e4..a86193553f2 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/PreferencesMapper.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.apis.transmodel.mapping.preferences.BikePreferencesMapper.mapBikePreferences;
import static org.opentripplanner.apis.transmodel.mapping.preferences.CarPreferencesMapper.mapCarPreferences;
import static org.opentripplanner.apis.transmodel.mapping.preferences.ItineraryFilterPreferencesMapper.mapItineraryFilterPreferences;
+import static org.opentripplanner.apis.transmodel.mapping.preferences.ScooterPreferencesMapper.mapScooterPreferences;
import static org.opentripplanner.apis.transmodel.mapping.preferences.StreetPreferencesMapper.mapStreetPreferences;
import static org.opentripplanner.apis.transmodel.mapping.preferences.TransferPreferencesMapper.mapTransferPreferences;
import static org.opentripplanner.apis.transmodel.mapping.preferences.TransitPreferencesMapper.mapTransitPreferences;
@@ -24,6 +25,7 @@ static void mapPreferences(
preferences.withWalk(walk -> mapWalkPreferences(walk, callWith));
preferences.withBike(bike -> mapBikePreferences(bike, callWith));
preferences.withCar(car -> mapCarPreferences(car, callWith));
+ preferences.withScooter(scooter -> mapScooterPreferences(scooter, callWith));
preferences.withTransfer(transfer -> mapTransferPreferences(transfer, environment, callWith));
preferences.withTransit(transit -> mapTransitPreferences(transit, environment, callWith));
preferences.withItineraryFilter(itineraryFilter ->
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java
index d02e251f4e2..59259905944 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapper.java
@@ -7,23 +7,30 @@
class RequestModesMapper {
+ private static final String accessModeKey = "accessMode";
+ private static final String egressModeKey = "egressMode";
+ private static final String directModeKey = "directMode";
+
/**
- * Maps a GraphQL Modes input type to a RequestModes.
- *
- * This only maps access, egress, direct & transfer.
- * Transport modes are now part of filters.
- * Only in case filters are not present we will use this mapping
+ * Maps GraphQL Modes input type to RequestModes.
+ *
+ * This only maps access, egress, direct & transfer modes. Transport modes are set using filters.
+ * Default modes are WALK for access, egress, direct & transfer.
*/
- @SuppressWarnings("unchecked")
static RequestModes mapRequestModes(Map modesInput) {
- StreetMode accessMode = (StreetMode) modesInput.get("accessMode");
- RequestModesBuilder mBuilder = RequestModes
- .of()
- .withAccessMode(accessMode)
- .withEgressMode((StreetMode) modesInput.get("egressMode"))
- .withDirectMode((StreetMode) modesInput.get("directMode"));
+ RequestModesBuilder mBuilder = RequestModes.of();
- mBuilder.withTransferMode(accessMode == StreetMode.BIKE ? StreetMode.BIKE : StreetMode.WALK);
+ if (modesInput.containsKey(accessModeKey)) {
+ StreetMode accessMode = (StreetMode) modesInput.get(accessModeKey);
+ mBuilder.withAccessMode(accessMode);
+ mBuilder.withTransferMode(accessMode == StreetMode.BIKE ? StreetMode.BIKE : StreetMode.WALK);
+ }
+ if (modesInput.containsKey(egressModeKey)) {
+ mBuilder.withEgressMode((StreetMode) modesInput.get(egressModeKey));
+ }
+ if (modesInput.containsKey(directModeKey)) {
+ mBuilder.withDirectMode((StreetMode) modesInput.get(directModeKey));
+ }
return mBuilder.build();
}
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java
index 1179c034e93..76826d31e2d 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapper.java
@@ -4,7 +4,7 @@
import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator;
import org.opentripplanner.routing.api.request.preference.BikePreferences;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
public class BikePreferencesMapper {
@@ -37,7 +37,7 @@ public static void mapBikePreferences(
// bike.withWalkingReluctance(WALK_BIKE_RELATIVE_RELUCTANCE * (double)r );
//});
- if (bike.optimizeType() == BicycleOptimizeType.TRIANGLE) {
+ if (bike.optimizeType() == VehicleRoutingOptimizeType.TRIANGLE) {
bike.withOptimizeTriangle(triangle -> {
callWith.argument("triangleFactors.time", triangle::withTime);
callWith.argument("triangleFactors.slope", triangle::withSlope);
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapper.java
new file mode 100644
index 00000000000..d7d5c019f93
--- /dev/null
+++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapper.java
@@ -0,0 +1,36 @@
+package org.opentripplanner.apis.transmodel.mapping.preferences;
+
+import static org.opentripplanner.apis.transmodel.mapping.preferences.RentalPreferencesMapper.mapRentalPreferences;
+
+import org.opentripplanner.apis.transmodel.support.DataFetcherDecorator;
+import org.opentripplanner.routing.api.request.preference.ScooterPreferences;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
+
+public class ScooterPreferencesMapper {
+
+ public static void mapScooterPreferences(
+ ScooterPreferences.Builder scooter,
+ DataFetcherDecorator callWith
+ ) {
+ callWith.argument("bikeSpeed", scooter::withSpeed);
+ callWith.argument("bicycleOptimisationMethod", scooter::withOptimizeType);
+
+ // WALK reluctance is used for backwards compatibility, then overridden
+ callWith.argument(
+ "walkReluctance",
+ r -> {
+ scooter.withReluctance((double) r);
+ }
+ );
+
+ if (scooter.optimizeType() == VehicleRoutingOptimizeType.TRIANGLE) {
+ scooter.withOptimizeTriangle(triangle -> {
+ callWith.argument("triangleFactors.time", triangle::withTime);
+ callWith.argument("triangleFactors.slope", triangle::withSlope);
+ callWith.argument("triangleFactors.safety", triangle::withSafety);
+ });
+ }
+
+ scooter.withRental(rental -> mapRentalPreferences(rental, callWith));
+ }
+}
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
index e2897d868c9..b85d4ce19b9 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
@@ -17,7 +17,7 @@
import org.opentripplanner.routing.api.request.preference.ItineraryFilterDebugProfile;
import org.opentripplanner.routing.api.response.InputField;
import org.opentripplanner.routing.api.response.RoutingErrorCode;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.stoptimes.ArrivalDeparture;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.transit.model.basic.Accessibility;
@@ -64,11 +64,11 @@ public class EnumTypes {
public static final GraphQLEnumType BICYCLE_OPTIMISATION_METHOD = GraphQLEnumType
.newEnum()
.name("BicycleOptimisationMethod")
- .value("quick", BicycleOptimizeType.SHORTEST_DURATION)
- .value("safe", BicycleOptimizeType.SAFE_STREETS)
- .value("flat", BicycleOptimizeType.FLAT_STREETS)
- .value("greenways", BicycleOptimizeType.SAFEST_STREETS)
- .value("triangle", BicycleOptimizeType.TRIANGLE)
+ .value("quick", VehicleRoutingOptimizeType.SHORTEST_DURATION)
+ .value("safe", VehicleRoutingOptimizeType.SAFE_STREETS)
+ .value("flat", VehicleRoutingOptimizeType.FLAT_STREETS)
+ .value("greenways", VehicleRoutingOptimizeType.SAFEST_STREETS)
+ .value("triangle", VehicleRoutingOptimizeType.TRIANGLE)
.build();
public static final GraphQLEnumType BIKES_ALLOWED = GraphQLEnumType
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
index abeeb187342..bf34d9928e4 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
@@ -20,7 +20,7 @@
import org.opentripplanner.apis.transmodel.model.framework.PenaltyForStreetModeType;
import org.opentripplanner.apis.transmodel.support.GqlUtil;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
public class TripQuery {
@@ -371,7 +371,10 @@ public static GraphQLFieldDefinition create(
"When setting the " +
EnumTypes.BICYCLE_OPTIMISATION_METHOD.getName() +
" to '" +
- enumValAsString(EnumTypes.BICYCLE_OPTIMISATION_METHOD, BicycleOptimizeType.TRIANGLE) +
+ enumValAsString(
+ EnumTypes.BICYCLE_OPTIMISATION_METHOD,
+ VehicleRoutingOptimizeType.TRIANGLE
+ ) +
"', use these values to tell the routing engine how important each of the factors is compared to the others. All values should add up to 1."
)
.type(TriangleFactorsInputType.INPUT_TYPE)
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
index accaf1e35fb..d178e5125b5 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
@@ -531,7 +531,7 @@ public static Collection fetchStopPlaces(
);
Stream stations = transitService
- .findRegularStop(envelope)
+ .findRegularStops(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
.map(StopLocation::getParentStation)
diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
index f45d7d36413..8350a10d670 100644
--- a/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
+++ b/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
@@ -53,6 +53,7 @@ public class DebugStyleSpec {
static StyleSpec build(
VectorSourceLayer regularStops,
VectorSourceLayer areaStops,
+ VectorSourceLayer groupStops,
VectorSourceLayer edges,
VectorSourceLayer vertices
) {
@@ -122,6 +123,15 @@ static StyleSpec build(
.fillOutlineColor(BLACK)
.minZoom(6)
.maxZoom(MAX_ZOOM),
+ StyleBuilder
+ .ofId("group-stop")
+ .typeFill()
+ .vectorSourceLayer(groupStops)
+ .fillColor(GREEN)
+ .fillOpacity(0.5f)
+ .fillOutlineColor(BLACK)
+ .minZoom(6)
+ .maxZoom(MAX_ZOOM),
StyleBuilder
.ofId("regular-stop")
.typeCircle()
diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java
index 03f4357e540..f97830232bd 100644
--- a/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java
+++ b/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java
@@ -3,6 +3,7 @@
import static org.opentripplanner.apis.vectortiles.model.LayerType.AreaStop;
import static org.opentripplanner.apis.vectortiles.model.LayerType.Edge;
import static org.opentripplanner.apis.vectortiles.model.LayerType.GeofencingZones;
+import static org.opentripplanner.apis.vectortiles.model.LayerType.GroupStop;
import static org.opentripplanner.apis.vectortiles.model.LayerType.RegularStop;
import static org.opentripplanner.apis.vectortiles.model.LayerType.Vertex;
import static org.opentripplanner.framework.io.HttpUtils.APPLICATION_X_PROTOBUF;
@@ -35,6 +36,7 @@
import org.opentripplanner.inspector.vector.VectorTileResponseFactory;
import org.opentripplanner.inspector.vector.edge.EdgeLayerBuilder;
import org.opentripplanner.inspector.vector.geofencing.GeofencingZonesLayerBuilder;
+import org.opentripplanner.inspector.vector.stop.GroupStopLayerBuilder;
import org.opentripplanner.inspector.vector.stop.StopLayerBuilder;
import org.opentripplanner.inspector.vector.vertex.VertexLayerBuilder;
import org.opentripplanner.model.FeedInfo;
@@ -49,6 +51,7 @@ public class GraphInspectorVectorTileResource {
private static final LayerParams REGULAR_STOPS = new LayerParams("regularStops", RegularStop);
private static final LayerParams AREA_STOPS = new LayerParams("areaStops", AreaStop);
+ private static final LayerParams GROUP_STOPS = new LayerParams("groupStops", GroupStop);
private static final LayerParams GEOFENCING_ZONES = new LayerParams(
"geofencingZones",
GeofencingZones
@@ -58,6 +61,7 @@ public class GraphInspectorVectorTileResource {
private static final List> DEBUG_LAYERS = List.of(
REGULAR_STOPS,
AREA_STOPS,
+ GROUP_STOPS,
GEOFENCING_ZONES,
EDGES,
VERTICES
@@ -128,11 +132,11 @@ public TileJson getTileJson(
public StyleSpec getTileJson(@Context UriInfo uri, @Context HttpHeaders headers) {
var base = HttpUtils.getBaseAddress(uri, headers);
- // these two could also be loaded together but are put into separate sources because
+ // these could also be loaded together but are put into separate sources because
// the stops are fast and the edges are relatively slow
var stopsSource = new VectorSource(
"stops",
- tileJsonUrl(base, List.of(REGULAR_STOPS, AREA_STOPS))
+ tileJsonUrl(base, List.of(REGULAR_STOPS, AREA_STOPS, GROUP_STOPS))
);
var streetSource = new VectorSource(
"street",
@@ -142,6 +146,7 @@ public StyleSpec getTileJson(@Context UriInfo uri, @Context HttpHeaders headers)
return DebugStyleSpec.build(
REGULAR_STOPS.toVectorSourceLayer(stopsSource),
AREA_STOPS.toVectorSourceLayer(stopsSource),
+ GROUP_STOPS.toVectorSourceLayer(stopsSource),
EDGES.toVectorSourceLayer(streetSource),
VERTICES.toVectorSourceLayer(streetSource)
);
@@ -179,13 +184,19 @@ private static LayerBuilder> createLayerBuilder(
case RegularStop -> new StopLayerBuilder<>(
layerParameters,
locale,
- e -> context.transitService().findRegularStop(e)
+ e -> context.transitService().findRegularStops(e)
);
case AreaStop -> new StopLayerBuilder<>(
layerParameters,
locale,
e -> context.transitService().findAreaStops(e)
);
+ case GroupStop -> new GroupStopLayerBuilder(
+ layerParameters,
+ locale,
+ // There are not many GroupStops, so we can just list them all.
+ context.transitService().listGroupStops()
+ );
case GeofencingZones -> new GeofencingZonesLayerBuilder(context.graph(), layerParameters);
case Edge -> new EdgeLayerBuilder(context.graph(), layerParameters);
case Vertex -> new VertexLayerBuilder(context.graph(), layerParameters);
diff --git a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java
index ece75f29b60..585725d0707 100644
--- a/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java
+++ b/src/main/java/org/opentripplanner/apis/vectortiles/model/LayerType.java
@@ -3,6 +3,7 @@
public enum LayerType {
RegularStop,
AreaStop,
+ GroupStop,
GeofencingZones,
Edge,
Vertex,
diff --git a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
index 4847b204077..4ae5004cf6b 100644
--- a/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
+++ b/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
@@ -105,8 +105,7 @@ public enum OTPFeature {
SandboxAPIMapboxVectorTilesApi(false, true, "Enable Mapbox vector tiles API."),
SandboxAPIParkAndRideApi(false, true, "Enable park-and-ride endpoint."),
SandboxAPITravelTime(false, true, "Enable the isochrone/travel time surface API."),
- TransferAnalyzer(false, true, "Analyze transfers during graph build."),
- VehicleToStopHeuristics(false, true, "Enable improved heuristic for park-and-ride queries.");
+ TransferAnalyzer(false, true, "Analyze transfers during graph build.");
private static final Object TEST_LOCK = new Object();
diff --git a/src/main/java/org/opentripplanner/framework/io/HttpUtils.java b/src/main/java/org/opentripplanner/framework/io/HttpUtils.java
index 4981a8ab91b..672c9b9c481 100644
--- a/src/main/java/org/opentripplanner/framework/io/HttpUtils.java
+++ b/src/main/java/org/opentripplanner/framework/io/HttpUtils.java
@@ -3,6 +3,7 @@
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.UriInfo;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import org.apache.hc.core5.http.ContentType;
public final class HttpUtils {
@@ -31,7 +32,7 @@ public static String getBaseAddress(UriInfo uri, HttpHeaders headers) {
String host;
if (headers.getRequestHeader(HEADER_X_FORWARDED_HOST) != null) {
- host = headers.getRequestHeader(HEADER_X_FORWARDED_HOST).getFirst();
+ host = extractHost(headers.getRequestHeader(HEADER_X_FORWARDED_HOST).getFirst());
} else if (headers.getRequestHeader(HEADER_HOST) != null) {
host = headers.getRequestHeader(HEADER_HOST).getFirst();
} else {
@@ -40,4 +41,12 @@ public static String getBaseAddress(UriInfo uri, HttpHeaders headers) {
return protocol + "://" + host;
}
+
+ /**
+ * The X-Forwarded-Host header can contain a comma-separated list so we account for that.
+ * https://stackoverflow.com/questions/66042952/http-proxy-behavior-for-x-forwarded-host-header
+ */
+ private static String extractHost(String xForwardedFor) {
+ return Arrays.stream(xForwardedFor.split(",")).map(String::strip).findFirst().get();
+ }
}
diff --git a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java
index c726d03c66c..cbb32b9eaca 100644
--- a/src/main/java/org/opentripplanner/framework/lang/StringUtils.java
+++ b/src/main/java/org/opentripplanner/framework/lang/StringUtils.java
@@ -110,4 +110,13 @@ public static String padRight(String value, char ch, int width) {
public static String quoteReplace(@Nonnull String text) {
return text.replace('\'', '\"');
}
+
+ /**
+ * Convert "HELLO_WORLD" or "HellO_WorlD" to "hello-world".
+ *
+ * https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case
+ */
+ public static String kebabCase(String input) {
+ return input.toLowerCase().replace('_', '-');
+ }
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
index 360cdaee363..7386b60b452 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
@@ -18,8 +18,6 @@
import org.opentripplanner.astar.strategy.MaxCountSkipEdgeStrategy;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.flex.trip.FlexTrip;
-import org.opentripplanner.ext.vehicletostopheuristics.BikeToStopSkipEdgeStrategy;
-import org.opentripplanner.ext.vehicletostopheuristics.VehicleToStopSkipEdgeStrategy;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -85,7 +83,7 @@ public NearbyStopFinder(
// We need to accommodate straight line distance (in meters) but when streets are present we
// use an earliest arrival search, which optimizes on time. Ideally we'd specify in meters,
// but we don't have much of a choice here. Use the default walking speed to convert.
- this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStop);
+ this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStops);
}
}
@@ -205,7 +203,7 @@ public List findNearbyStopsViaStreets(
ShortestPathTree spt = StreetSearchBuilder
.of()
- .setSkipEdgeStrategy(getSkipEdgeStrategy(reverseDirection, request))
+ .setSkipEdgeStrategy(getSkipEdgeStrategy())
.setDominanceFunction(new DominanceFunctions.MinimumWeight())
.setRequest(request)
.setArriveBy(reverseDirection)
@@ -271,48 +269,14 @@ private List findNearbyStopsViaDirectTransfers(Vertex vertex) {
return directGraphFinder.findClosestStops(c0, limitMeters);
}
- private SkipEdgeStrategy getSkipEdgeStrategy(
- boolean reverseDirection,
- RouteRequest routingRequest
- ) {
+ private SkipEdgeStrategy getSkipEdgeStrategy() {
var durationSkipEdgeStrategy = new DurationSkipEdgeStrategy(durationLimit);
- // if we compute the accesses for Park+Ride, Bike+Ride and Bike+Transit we don't want to
- // search the full durationLimit as this returns way too many stops.
- // this is both slow and returns suboptimal results as it favours long drives with short
- // transit legs.
- // therefore, we use a heuristic based on the number of routes and their mode to determine
- // what are "good" stops for those accesses. if we have reached a threshold of "good" stops
- // we stop the access search.
- if (
- !reverseDirection &&
- OTPFeature.VehicleToStopHeuristics.isOn() &&
- VehicleToStopSkipEdgeStrategy.applicableModes.contains(
- routingRequest.journey().access().mode()
- )
- ) {
- var strategy = new VehicleToStopSkipEdgeStrategy(
- transitService::getPatternsForStop,
- routingRequest.journey().transit().filters()
- );
-
+ if (maxStopCount > 0) {
+ var strategy = new MaxCountSkipEdgeStrategy<>(maxStopCount, NearbyStopFinder::hasReachedStop);
return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy);
- } else if (
- OTPFeature.VehicleToStopHeuristics.isOn() &&
- routingRequest.journey().access().mode() == StreetMode.BIKE
- ) {
- var strategy = new BikeToStopSkipEdgeStrategy(transitService::getTripsForStop);
- return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy);
- } else {
- if (maxStopCount > 0) {
- var strategy = new MaxCountSkipEdgeStrategy<>(
- maxStopCount,
- NearbyStopFinder::hasReachedStop
- );
- return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy);
- }
- return durationSkipEdgeStrategy;
}
+ return durationSkipEdgeStrategy;
}
private static List createDirectlyConnectedStops(
diff --git a/src/main/java/org/opentripplanner/inspector/vector/stop/GroupStopLayerBuilder.java b/src/main/java/org/opentripplanner/inspector/vector/stop/GroupStopLayerBuilder.java
new file mode 100644
index 00000000000..1e66397bc05
--- /dev/null
+++ b/src/main/java/org/opentripplanner/inspector/vector/stop/GroupStopLayerBuilder.java
@@ -0,0 +1,50 @@
+package org.opentripplanner.inspector.vector.stop;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.opentripplanner.inspector.vector.LayerBuilder;
+import org.opentripplanner.inspector.vector.LayerParameters;
+import org.opentripplanner.transit.model.site.GroupStop;
+import org.opentripplanner.transit.model.site.StopLocation;
+
+/**
+ * A vector tile layer for {@link GroupStop}s inside the vector tile bounds. The builder does not
+ * query for the GroupStops to draw, but instead uses the geometries of the GroupStops that are
+ * passed to the constructor.
+ */
+public class GroupStopLayerBuilder extends LayerBuilder {
+
+ private final List geometries;
+
+ public GroupStopLayerBuilder(
+ LayerParameters layerParameters,
+ Locale locale,
+ Collection groupStops
+ ) {
+ super(
+ new StopLocationPropertyMapper(locale),
+ layerParameters.name(),
+ layerParameters.expansionFactor()
+ );
+ // Because there are very few GroupStops with relevant geometries, we can precompute the
+ // geometries and store them in a list at the time of construction.
+ this.geometries =
+ groupStops
+ .stream()
+ .filter(groupStop -> groupStop.getEncompassingAreaGeometry().isPresent())
+ .map(stop -> {
+ Geometry geometry = stop.getEncompassingAreaGeometry().get().copy();
+ geometry.setUserData(stop);
+ return geometry;
+ })
+ .toList();
+ }
+
+ @Override
+ protected List getGeometries(Envelope query) {
+ return geometries;
+ }
+}
diff --git a/src/main/java/org/opentripplanner/model/plan/Place.java b/src/main/java/org/opentripplanner/model/plan/Place.java
index 71c6d9bc188..fe3a9dee420 100644
--- a/src/main/java/org/opentripplanner/model/plan/Place.java
+++ b/src/main/java/org/opentripplanner/model/plan/Place.java
@@ -90,11 +90,7 @@ public static Place normal(Vertex vertex, I18NString name) {
}
public static Place forStop(StopLocation stop) {
- return forStop(stop, stop.getName());
- }
-
- public static Place forStop(StopLocation stop, I18NString nameOverride) {
- return new Place(nameOverride, stop.getCoordinate(), VertexType.TRANSIT, stop, null, null);
+ return new Place(stop.getName(), stop.getCoordinate(), VertexType.TRANSIT, stop, null, null);
}
public static Place forFlexStop(StopLocation stop, Vertex vertex) {
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
index c9e7f92263f..ab11d654ef7 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
@@ -232,6 +232,8 @@ private Collection fetchAccessEgresses(AccessEgressType typ
accessRequest.withPreferences(p -> {
p.withBike(b -> b.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)));
p.withCar(c -> c.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false)));
+ p.withScooter(s -> s.withRental(r -> r.withAllowArrivingInRentedVehicleAtDestination(false))
+ );
});
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java
index 2c6b09c2fb2..6229cbb022c 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectStreetRouter.java
@@ -96,6 +96,8 @@ private static double calculateDistanceMaxLimit(RouteRequest request) {
distanceLimit = durationLimit * preferences.car().speed();
} else if (mode.includesBiking()) {
distanceLimit = durationLimit * preferences.bike().speed();
+ } else if (mode.includesScooter()) {
+ distanceLimit = durationLimit * preferences.scooter().speed();
} else if (mode.includesWalking()) {
distanceLimit = durationLimit * preferences.walk().speed();
} else {
diff --git a/src/main/java/org/opentripplanner/routing/api/request/RequestModes.java b/src/main/java/org/opentripplanner/routing/api/request/RequestModes.java
index 5253bb2018d..72b06f62979 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/RequestModes.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/RequestModes.java
@@ -2,12 +2,9 @@
import static org.opentripplanner.routing.api.request.StreetMode.NOT_SET;
-import java.util.Collection;
-import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.transit.model.basic.MainAndSubMode;
public class RequestModes {
@@ -39,10 +36,11 @@ private RequestModes(
StreetMode directMode,
StreetMode transferMode
) {
- this.accessMode = (accessMode != null && accessMode.access) ? accessMode : NOT_SET;
- this.egressMode = (egressMode != null && egressMode.egress) ? egressMode : NOT_SET;
+ this.accessMode = (accessMode != null && accessMode.accessAllowed()) ? accessMode : NOT_SET;
+ this.egressMode = (egressMode != null && egressMode.egressAllowed()) ? egressMode : NOT_SET;
this.directMode = directMode != null ? directMode : NOT_SET;
- this.transferMode = (transferMode != null && transferMode.transfer) ? transferMode : NOT_SET;
+ this.transferMode =
+ (transferMode != null && transferMode.transferAllowed()) ? transferMode : NOT_SET;
}
public RequestModes(RequestModesBuilder builder) {
diff --git a/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java b/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java
index 0f5d65305b3..47073aa7eeb 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/StreetMode.java
@@ -1,5 +1,7 @@
package org.opentripplanner.routing.api.request;
+import java.util.EnumSet;
+import java.util.Set;
import org.opentripplanner.framework.doc.DocumentedEnum;
public enum StreetMode implements DocumentedEnum {
@@ -7,126 +9,121 @@ public enum StreetMode implements DocumentedEnum {
* No street mode is set. This option is used if we do not want street routing at all in this part
* of the search.
*/
- NOT_SET(true, true, true, false, false, false, false, false, false),
+ NOT_SET(Feature.ACCESS, Feature.TRANSFER, Feature.EGRESS),
/**
* Walk only
*/
- WALK(true, true, true, true, false, false, false, false, false),
+ WALK(Feature.ACCESS, Feature.TRANSFER, Feature.EGRESS, Feature.WALKING),
/**
* Bike only
*/
- BIKE(true, true, true, false, true, false, false, false, false),
+ BIKE(Feature.ACCESS, Feature.TRANSFER, Feature.EGRESS, Feature.CYCLING),
/**
* Bike to a bike parking area, then walk the rest of the way.
*
* Direct mode and access mode only.
*/
- BIKE_TO_PARK(true, false, false, true, true, false, false, true, false),
+ BIKE_TO_PARK(Feature.ACCESS, Feature.WALKING, Feature.CYCLING, Feature.PARKING),
/**
* Walk to a bike rental point, bike to a bike rental drop-off point, and walk the rest of the
* way. This can include bike rental at fixed locations or free-floating services.
*/
- BIKE_RENTAL(true, true, true, true, true, false, true, false, false),
+ BIKE_RENTAL(Feature.ACCESS, Feature.EGRESS, Feature.WALKING, Feature.CYCLING, Feature.RENTING),
/**
* Walk to a scooter rental point, ride a scooter to a scooter rental drop-off point, and walk the
* rest of the way. This can include scooter rental at fixed locations or free-floating services.
*/
- SCOOTER_RENTAL(true, true, true, true, true, false, true, false, false),
+ SCOOTER_RENTAL(Feature.ACCESS, Feature.EGRESS, Feature.WALKING, Feature.SCOOTER, Feature.RENTING),
/**
* Car only
*
* Direct mode only.
*/
- CAR(true, false, false, false, false, true, false, false, false),
+ CAR(Feature.ACCESS, Feature.DRIVING),
/**
* Start in the car, drive to a parking area, and walk the rest of the way.
*
* Direct mode and access mode only.
*/
- CAR_TO_PARK(true, false, false, true, false, true, false, true, false),
+ CAR_TO_PARK(Feature.ACCESS, Feature.WALKING, Feature.DRIVING, Feature.PARKING),
/**
* Walk to a pickup point along the road, drive to a drop-off point along the road, and walk the
* rest of the way. This can include various taxi-services or kiss & ride.
*/
- CAR_PICKUP(true, false, true, true, false, true, false, false, true),
+ CAR_PICKUP(Feature.ACCESS, Feature.EGRESS, Feature.WALKING, Feature.DRIVING, Feature.PICKUP),
/**
* Walk to a car rental point, drive to a car rental drop-off point and walk the rest of the way.
* This can include car rental at fixed locations or free-floating services.
*/
- CAR_RENTAL(true, true, true, true, false, true, true, false, false),
+ CAR_RENTAL(Feature.ACCESS, Feature.EGRESS, Feature.WALKING, Feature.DRIVING, Feature.RENTING),
/**
* Using a car hailing app like Uber or Lyft to get to a train station or all the way to the destination.
*/
- CAR_HAILING(true, false, true, false, false, true, false, false, true),
+ CAR_HAILING(Feature.ACCESS, Feature.EGRESS, Feature.DRIVING, Feature.PICKUP),
/**
* Encompasses all types of on-demand and flexible transportation.
*/
- FLEXIBLE(true, false, true, true, false, false, false, false, false);
-
- final boolean access;
-
- final boolean transfer;
-
- final boolean egress;
-
- final boolean includesWalking;
-
- final boolean includesBiking;
+ FLEXIBLE(Feature.ACCESS, Feature.EGRESS, Feature.WALKING);
+
+ private enum Feature {
+ ACCESS,
+ EGRESS,
+ TRANSFER,
+ WALKING,
+ CYCLING,
+ DRIVING,
+ SCOOTER,
+ RENTING,
+ PARKING,
+ PICKUP,
+ }
- final boolean includesDriving;
+ private final Set features;
- final boolean includesRenting;
+ StreetMode(Feature first, Feature... rest) {
+ this.features = EnumSet.of(first, rest);
+ }
- final boolean includesParking;
+ public boolean accessAllowed() {
+ return features.contains(Feature.ACCESS);
+ }
- final boolean includesPickup;
+ public boolean transferAllowed() {
+ return features.contains(Feature.TRANSFER);
+ }
- StreetMode(
- boolean access,
- boolean transfer,
- boolean egress,
- boolean includesWalking,
- boolean includesBiking,
- boolean includesDriving,
- boolean includesRenting,
- boolean includesParking,
- boolean includesPickup
- ) {
- this.access = access;
- this.transfer = transfer;
- this.egress = egress;
- this.includesWalking = includesWalking;
- this.includesBiking = includesBiking;
- this.includesDriving = includesDriving;
- this.includesRenting = includesRenting;
- this.includesParking = includesParking;
- this.includesPickup = includesPickup;
+ public boolean egressAllowed() {
+ return features.contains(Feature.EGRESS);
}
public boolean includesWalking() {
- return includesWalking;
+ return features.contains(Feature.WALKING);
}
public boolean includesBiking() {
- return includesBiking;
+ return features.contains(Feature.CYCLING);
}
public boolean includesDriving() {
- return includesDriving;
+ return features.contains(Feature.DRIVING);
+ }
+
+ public boolean includesScooter() {
+ return features.contains(Feature.SCOOTER);
}
public boolean includesRenting() {
- return includesRenting;
+ return features.contains(Feature.RENTING);
}
public boolean includesParking() {
- return includesParking;
+ return features.contains(Feature.PARKING);
}
public boolean includesPickup() {
- return includesPickup;
+ return features.contains(Feature.PICKUP);
}
@Override
diff --git a/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java b/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java
index 60bd4d4c769..c45fc3e33a8 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/framework/TimeAndCostPenaltyForEnum.java
@@ -81,6 +81,13 @@ private EnumMap copyValues() {
return values.isEmpty() ? new EnumMap<>(type) : new EnumMap<>(values);
}
+ /**
+ * Convert the values to an {@link EnumMap}.
+ */
+ public EnumMap asEnumMap() {
+ return copyValues();
+ }
+
private static > String toString(
Class> clazz,
Map values
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java
index db8845e7c41..9998f0a3b1c 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/AccessEgressPreferences.java
@@ -7,12 +7,12 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
-import org.opentripplanner.framework.model.Units;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.framework.DurationForEnum;
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty;
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenaltyForEnum;
+import org.opentripplanner.routing.api.request.framework.TimePenalty;
/**
* Preferences for access/egress routing on street network
@@ -21,14 +21,31 @@
*/
public final class AccessEgressPreferences implements Serializable {
+ private static final TimeAndCostPenalty DEFAULT_PENALTY = TimeAndCostPenalty.of(
+ TimePenalty.of(ofMinutes(20), 2f),
+ 1.5
+ );
+ private static final TimeAndCostPenalty FLEX_DEFAULT_PENALTY = TimeAndCostPenalty.of(
+ TimePenalty.of(ofMinutes(10), 1.3f),
+ 1.3
+ );
+ private static final TimeAndCostPenaltyForEnum DEFAULT_TIME_AND_COST = TimeAndCostPenaltyForEnum
+ .of(StreetMode.class)
+ .with(StreetMode.CAR_TO_PARK, DEFAULT_PENALTY)
+ .with(StreetMode.CAR_HAILING, DEFAULT_PENALTY)
+ .with(StreetMode.CAR_RENTAL, DEFAULT_PENALTY)
+ .with(StreetMode.FLEXIBLE, FLEX_DEFAULT_PENALTY)
+ .build();
+
public static final AccessEgressPreferences DEFAULT = new AccessEgressPreferences();
+
private final TimeAndCostPenaltyForEnum penalty;
private final DurationForEnum maxDuration;
private final int maxStopCount;
private AccessEgressPreferences() {
this.maxDuration = durationForStreetModeOf(ofMinutes(45));
- this.penalty = TimeAndCostPenaltyForEnum.ofDefault(StreetMode.class);
+ this.penalty = DEFAULT_TIME_AND_COST;
this.maxStopCount = 500;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
index 26da1476b38..5767b74eaa8 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/BikePreferences.java
@@ -2,8 +2,8 @@
import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals;
import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull;
-import static org.opentripplanner.routing.core.BicycleOptimizeType.SAFE_STREETS;
-import static org.opentripplanner.routing.core.BicycleOptimizeType.TRIANGLE;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.SAFE_STREETS;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.TRIANGLE;
import java.io.Serializable;
import java.util.Objects;
@@ -11,7 +11,7 @@
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.framework.model.Units;
import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
/**
* The bike preferences contain all speed, reluctance, cost and factor preferences for biking
@@ -29,7 +29,7 @@ public final class BikePreferences implements Serializable {
private final Cost boardCost;
private final VehicleParkingPreferences parking;
private final VehicleRentalPreferences rental;
- private final BicycleOptimizeType optimizeType;
+ private final VehicleRoutingOptimizeType optimizeType;
private final TimeSlopeSafetyTriangle optimizeTriangle;
private final VehicleWalkingPreferences walking;
@@ -96,7 +96,7 @@ public VehicleRentalPreferences rental() {
/**
* The set of characteristics that the user wants to optimize for -- defaults to SAFE_STREETS.
*/
- public BicycleOptimizeType optimizeType() {
+ public VehicleRoutingOptimizeType optimizeType() {
return optimizeType;
}
@@ -164,7 +164,7 @@ public static class Builder {
private Cost boardCost;
private VehicleParkingPreferences parking;
private VehicleRentalPreferences rental;
- private BicycleOptimizeType optimizeType;
+ private VehicleRoutingOptimizeType optimizeType;
private TimeSlopeSafetyTriangle optimizeTriangle;
private VehicleWalkingPreferences walking;
@@ -221,11 +221,11 @@ public Builder withRental(Consumer body) {
return this;
}
- public BicycleOptimizeType optimizeType() {
+ public VehicleRoutingOptimizeType optimizeType() {
return optimizeType;
}
- public Builder withOptimizeType(BicycleOptimizeType optimizeType) {
+ public Builder withOptimizeType(VehicleRoutingOptimizeType optimizeType) {
this.optimizeType = optimizeType;
return this;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
index d3d22160935..3fec7a9e46e 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/RoutingPreferences.java
@@ -7,6 +7,7 @@
import java.util.Objects;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.street.search.TraverseMode;
@@ -23,6 +24,7 @@ public final class RoutingPreferences implements Serializable {
private final WheelchairPreferences wheelchair;
private final BikePreferences bike;
private final CarPreferences car;
+ private final ScooterPreferences scooter;
private final SystemPreferences system;
private final ItineraryFilterPreferences itineraryFilter;
@@ -34,6 +36,7 @@ public RoutingPreferences() {
this.wheelchair = WheelchairPreferences.DEFAULT;
this.bike = BikePreferences.DEFAULT;
this.car = CarPreferences.DEFAULT;
+ this.scooter = ScooterPreferences.DEFAULT;
this.system = SystemPreferences.DEFAULT;
this.itineraryFilter = ItineraryFilterPreferences.DEFAULT;
}
@@ -46,6 +49,7 @@ private RoutingPreferences(Builder builder) {
this.street = requireNonNull(builder.street());
this.bike = requireNonNull(builder.bike());
this.car = requireNonNull(builder.car());
+ this.scooter = requireNonNull(builder.scooter());
this.system = requireNonNull(builder.system());
this.itineraryFilter = requireNonNull(builder.itineraryFilter());
}
@@ -90,6 +94,10 @@ public CarPreferences car() {
return car;
}
+ public ScooterPreferences scooter() {
+ return scooter;
+ }
+
/**
* Get parking preferences for the traverse mode. Note, only car and bike are supported.
*/
@@ -99,20 +107,28 @@ public VehicleParkingPreferences parking(TraverseMode mode) {
/**
* Get rental preferences for the traverse mode. Note, only car, scooter and bike are supported.
- *
- * TODO make scooter preferences independent of bike
*/
+ @Nonnull
public VehicleRentalPreferences rental(TraverseMode mode) {
- return mode == TraverseMode.CAR ? car.rental() : bike.rental();
+ return switch (mode) {
+ case BICYCLE -> bike.rental();
+ case CAR -> car.rental();
+ case SCOOTER -> scooter.rental();
+ default -> throw new IllegalArgumentException("rental(): Invalid mode " + mode);
+ };
}
/**
* Get rental preferences for the traverse mode. Note, only car, scooter and bike are supported.
- *
- * TODO make scooter preferences independent of bike
*/
+ @Nullable
public VehicleRentalPreferences rental(StreetMode mode) {
- return mode == StreetMode.CAR_RENTAL ? car.rental() : bike.rental();
+ return switch (mode) {
+ case BIKE_RENTAL -> bike.rental();
+ case CAR_RENTAL -> car.rental();
+ case SCOOTER_RENTAL -> scooter.rental();
+ default -> null;
+ };
}
@Nonnull
@@ -126,12 +142,15 @@ public SystemPreferences system() {
/**
* The road speed for a specific traverse mode.
+ *
+ * NOTE, this is only used for tests and doesn't support scooter walking
*/
public double getSpeed(TraverseMode mode, boolean walkingBike) {
return switch (mode) {
case WALK -> walkingBike ? bike.walking().speed() : walk.speed();
case BICYCLE -> bike.speed();
case CAR -> car.speed();
+ case SCOOTER -> scooter.speed();
default -> throw new IllegalArgumentException("getSpeed(): Invalid mode " + mode);
};
}
@@ -149,6 +168,7 @@ public boolean equals(Object o) {
Objects.equals(wheelchair, that.wheelchair) &&
Objects.equals(bike, that.bike) &&
Objects.equals(car, that.car) &&
+ Objects.equals(scooter, that.scooter) &&
Objects.equals(system, that.system) &&
Objects.equals(itineraryFilter, that.itineraryFilter)
);
@@ -164,6 +184,7 @@ public int hashCode() {
wheelchair,
bike,
car,
+ scooter,
system,
itineraryFilter
);
@@ -179,6 +200,7 @@ public static class Builder {
private WheelchairPreferences wheelchair = null;
private BikePreferences bike = null;
private CarPreferences car = null;
+ private ScooterPreferences scooter = null;
private SystemPreferences system = null;
private ItineraryFilterPreferences itineraryFilter = null;
@@ -259,6 +281,15 @@ public Builder withCar(Consumer body) {
return this;
}
+ public ScooterPreferences scooter() {
+ return scooter == null ? original.scooter : scooter;
+ }
+
+ public Builder withScooter(Consumer body) {
+ this.scooter = ifNotNull(this.scooter, original.scooter).copyOf().apply(body).build();
+ return this;
+ }
+
public SystemPreferences system() {
return system == null ? original.system : system;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/ScooterPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/ScooterPreferences.java
new file mode 100644
index 00000000000..1d5cf83a54b
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/ScooterPreferences.java
@@ -0,0 +1,203 @@
+package org.opentripplanner.routing.api.request.preference;
+
+import static org.opentripplanner.framework.lang.DoubleUtils.doubleEquals;
+import static org.opentripplanner.framework.lang.ObjectUtils.ifNotNull;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.SAFE_STREETS;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.TRIANGLE;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.function.Consumer;
+import org.opentripplanner.framework.model.Units;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
+
+/**
+ * The scooter preferences contain all speed, reluctance, cost and factor preferences for scooter
+ * related to street and transit routing. The values are normalized(rounded) so the class can used
+ * as a cache key.
+ *
+ * Only Scooter rental is supported currently.
+ *
+ * THIS CLASS IS IMMUTABLE AND THREAD-SAFE.
+ */
+public final class ScooterPreferences implements Serializable {
+
+ public static final ScooterPreferences DEFAULT = new ScooterPreferences();
+
+ private final double speed;
+ private final double reluctance;
+ private final VehicleRentalPreferences rental;
+ private final VehicleRoutingOptimizeType optimizeType;
+ private final TimeSlopeSafetyTriangle optimizeTriangle;
+
+ private ScooterPreferences() {
+ this.speed = 5;
+ this.reluctance = 2.0;
+ this.rental = VehicleRentalPreferences.DEFAULT;
+ this.optimizeType = SAFE_STREETS;
+ this.optimizeTriangle = TimeSlopeSafetyTriangle.DEFAULT;
+ }
+
+ private ScooterPreferences(Builder builder) {
+ this.speed = Units.speed(builder.speed);
+ this.reluctance = Units.reluctance(builder.reluctance);
+ this.rental = builder.rental;
+ this.optimizeType = Objects.requireNonNull(builder.optimizeType);
+ this.optimizeTriangle = Objects.requireNonNull(builder.optimizeTriangle);
+ }
+
+ public static ScooterPreferences.Builder of() {
+ return new Builder(DEFAULT);
+ }
+
+ public ScooterPreferences.Builder copyOf() {
+ return new Builder(this);
+ }
+
+ /**
+ * Default: 5 m/s, ~11 mph, a random scooter speed
+ */
+ public double speed() {
+ return speed;
+ }
+
+ public double reluctance() {
+ return reluctance;
+ }
+
+ /** Rental preferences that can be different per request */
+ public VehicleRentalPreferences rental() {
+ return rental;
+ }
+
+ /**
+ * The set of characteristics that the user wants to optimize for -- defaults to SAFE_STREETS.
+ */
+ public VehicleRoutingOptimizeType optimizeType() {
+ return optimizeType;
+ }
+
+ public TimeSlopeSafetyTriangle optimizeTriangle() {
+ return optimizeTriangle;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ScooterPreferences that = (ScooterPreferences) o;
+ return (
+ doubleEquals(that.speed, speed) &&
+ doubleEquals(that.reluctance, reluctance) &&
+ Objects.equals(rental, that.rental) &&
+ optimizeType == that.optimizeType &&
+ optimizeTriangle.equals(that.optimizeTriangle)
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(speed, reluctance, rental, optimizeType, optimizeTriangle);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(ScooterPreferences.class)
+ .addNum("speed", speed, DEFAULT.speed)
+ .addNum("reluctance", reluctance, DEFAULT.reluctance)
+ .addObj("rental", rental, DEFAULT.rental)
+ .addEnum("optimizeType", optimizeType, DEFAULT.optimizeType)
+ .addObj("optimizeTriangle", optimizeTriangle, DEFAULT.optimizeTriangle)
+ .toString();
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ public static class Builder {
+
+ private final ScooterPreferences original;
+ private double speed;
+ private double reluctance;
+ private VehicleRentalPreferences rental;
+ private VehicleRoutingOptimizeType optimizeType;
+ private TimeSlopeSafetyTriangle optimizeTriangle;
+
+ public Builder(ScooterPreferences original) {
+ this.original = original;
+ this.speed = original.speed;
+ this.reluctance = original.reluctance;
+ this.rental = original.rental;
+ this.optimizeType = original.optimizeType;
+ this.optimizeTriangle = original.optimizeTriangle;
+ }
+
+ public ScooterPreferences original() {
+ return original;
+ }
+
+ public double speed() {
+ return speed;
+ }
+
+ public Builder withSpeed(double speed) {
+ this.speed = speed;
+ return this;
+ }
+
+ public double reluctance() {
+ return reluctance;
+ }
+
+ public Builder withReluctance(double reluctance) {
+ this.reluctance = reluctance;
+ return this;
+ }
+
+ public Builder withRental(Consumer body) {
+ this.rental = ifNotNull(this.rental, original.rental).copyOf().apply(body).build();
+ return this;
+ }
+
+ public VehicleRoutingOptimizeType optimizeType() {
+ return optimizeType;
+ }
+
+ public Builder withOptimizeType(VehicleRoutingOptimizeType optimizeType) {
+ this.optimizeType = optimizeType;
+ return this;
+ }
+
+ public TimeSlopeSafetyTriangle optimizeTriangle() {
+ return optimizeTriangle;
+ }
+
+ /** This also sets the optimization type as TRIANGLE if triangle parameters are defined */
+ public Builder withForcedOptimizeTriangle(Consumer body) {
+ var builder = TimeSlopeSafetyTriangle.of();
+ body.accept(builder);
+ this.optimizeTriangle = builder.buildOrDefault(this.optimizeTriangle);
+ if (!builder.isEmpty()) {
+ this.optimizeType = TRIANGLE;
+ }
+ return this;
+ }
+
+ public Builder withOptimizeTriangle(Consumer body) {
+ var builder = TimeSlopeSafetyTriangle.of();
+ body.accept(builder);
+ this.optimizeTriangle = builder.buildOrDefault(this.optimizeTriangle);
+ return this;
+ }
+
+ public Builder apply(Consumer body) {
+ body.accept(this);
+ return this;
+ }
+
+ public ScooterPreferences build() {
+ var value = new ScooterPreferences(this);
+ return original.equals(value) ? original : value;
+ }
+ }
+}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java
index 99925a441fd..ae4f1039f32 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/TimeSlopeSafetyTriangle.java
@@ -4,13 +4,14 @@
import static org.opentripplanner.framework.lang.DoubleUtils.roundTo2Decimals;
/**
- * Sets the (bicycle) triangle routing parameters -- the relative importance of safety, flatness,
- * and speed. These three fields should have values between 0 and 1, and should add up to 1.
+ * Sets the (bicycle or scooter) triangle routing parameters -- the relative importance of safety,
+ * flatness, and speed. These three fields should have values between 0 and 1, and should add up to
+ * 1.
*
* The constructor accepts any three numbers and will normalize them to add up to 1. {@code time}
* and {@code slope} are rounded to the closest two decimal number, then
- * {@code safety := 1.0 - (time + slope)}. This is done to make the rounding predictable and
- * to allways add up to one. This allows this class to be used in an index of a cache. For example:
+ * {@code safety := 1.0 - (time + slope)}. This is done to make the rounding predictable and to
+ * allways add up to one. This allows this class to be used in an index of a cache. For example:
*
* ( 1.0, 1.0, 1.0 ) => ( time: 0.33, slope: 0.33, safety: 0.34 )
*
@@ -25,10 +26,10 @@ public record TimeSlopeSafetyTriangle(double time, double slope, double safety)
public static final TimeSlopeSafetyTriangle DEFAULT = new TimeSlopeSafetyTriangle(1, 1, 1);
/**
- * Sets the bicycle triangle routing parameters -- the relative importance of safety, flatness,
- * and speed. These three fields of the RouteRequest should have values between 0 and 1, and
- * should add up to 1. This setter function accepts any three numbers and will normalize them to
- * add up to 1.
+ * Sets the bicycle or scooter triangle routing parameters -- the relative importance of safety,
+ * flatness, and speed. These three fields of the RouteRequest should have values between 0 and 1,
+ * and should add up to 1. This setter function accepts any three numbers and will normalize them
+ * to add up to 1.
*/
public TimeSlopeSafetyTriangle(double time, double slope, double safety) {
safety = positiveValueOrZero(safety);
diff --git a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java
index aa3631e1c6b..b7adc04df1c 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferences.java
@@ -19,15 +19,15 @@ public class VehicleWalkingPreferences implements Serializable {
private final double speed;
private final double reluctance;
- private final Duration hopTime;
- private final Cost hopCost;
+ private final Duration mountDismountTime;
+ private final Cost mountDismountCost;
private final double stairsReluctance;
private VehicleWalkingPreferences() {
this.speed = 1.33;
this.reluctance = 5.0;
- this.hopTime = Duration.ZERO;
- this.hopCost = Cost.ZERO;
+ this.mountDismountTime = Duration.ZERO;
+ this.mountDismountCost = Cost.ZERO;
// very high reluctance to carry the bike up/down a flight of stairs
this.stairsReluctance = 10;
}
@@ -39,8 +39,8 @@ private VehicleWalkingPreferences() {
private VehicleWalkingPreferences(Builder builder) {
this.speed = Units.speed(builder.speed);
this.reluctance = Units.reluctance(builder.reluctance);
- this.hopTime = Duration.ofSeconds(Units.duration(builder.hopTime));
- this.hopCost = Cost.costOfSeconds(builder.hopCost);
+ this.mountDismountTime = Duration.ofSeconds(Units.duration(builder.mountDismountTime));
+ this.mountDismountCost = Cost.costOfSeconds(builder.mountDismountCost);
this.stairsReluctance = Units.reluctance(builder.stairsReluctance);
}
@@ -73,13 +73,13 @@ public double reluctance() {
}
/** Time to get on and off your own vehicle. */
- public Duration hopTime() {
- return hopTime;
+ public Duration mountDismountTime() {
+ return mountDismountTime;
}
/** Cost of getting on and off your own vehicle. */
- public Cost hopCost() {
- return hopCost;
+ public Cost mountDismountCost() {
+ return mountDismountCost;
}
/** Reluctance of walking carrying a vehicle up a flight of stairs. */
@@ -95,15 +95,15 @@ public boolean equals(Object o) {
return (
speed == that.speed &&
reluctance == that.reluctance &&
- Objects.equals(hopTime, that.hopTime) &&
- Objects.equals(hopCost, that.hopCost) &&
+ Objects.equals(mountDismountTime, that.mountDismountTime) &&
+ Objects.equals(mountDismountCost, that.mountDismountCost) &&
stairsReluctance == that.stairsReluctance
);
}
@Override
public int hashCode() {
- return Objects.hash(speed, reluctance, hopTime, hopCost, stairsReluctance);
+ return Objects.hash(speed, reluctance, mountDismountTime, mountDismountCost, stairsReluctance);
}
@Override
@@ -112,8 +112,8 @@ public String toString() {
.of(VehicleWalkingPreferences.class)
.addNum("speed", speed, DEFAULT.speed)
.addNum("reluctance", reluctance, DEFAULT.reluctance)
- .addObj("hopTime", hopTime, DEFAULT.hopTime)
- .addObj("hopCost", hopCost, DEFAULT.hopCost)
+ .addObj("mountDismountTime", mountDismountTime, DEFAULT.mountDismountTime)
+ .addObj("mountDismountCost", mountDismountCost, DEFAULT.mountDismountCost)
.addNum("stairsReluctance", stairsReluctance, DEFAULT.stairsReluctance)
.toString();
}
@@ -123,16 +123,16 @@ public static class Builder {
private final VehicleWalkingPreferences original;
private double speed;
private double reluctance;
- private int hopTime;
- private int hopCost;
+ private int mountDismountTime;
+ private int mountDismountCost;
private double stairsReluctance;
private Builder(VehicleWalkingPreferences original) {
this.original = original;
this.speed = original.speed;
this.reluctance = original.reluctance;
- this.hopTime = (int) original.hopTime.toSeconds();
- this.hopCost = original.hopCost.toSeconds();
+ this.mountDismountTime = (int) original.mountDismountTime.toSeconds();
+ this.mountDismountCost = original.mountDismountCost.toSeconds();
this.stairsReluctance = original.stairsReluctance;
}
@@ -146,18 +146,18 @@ public VehicleWalkingPreferences.Builder withReluctance(double reluctance) {
return this;
}
- public VehicleWalkingPreferences.Builder withHopTime(Duration hopTime) {
- this.hopTime = (int) hopTime.toSeconds();
+ public VehicleWalkingPreferences.Builder withMountDismountTime(Duration mountDismountTime) {
+ this.mountDismountTime = (int) mountDismountTime.toSeconds();
return this;
}
- public VehicleWalkingPreferences.Builder withHopTime(int hopTime) {
- this.hopTime = hopTime;
+ public VehicleWalkingPreferences.Builder withMountDismountTime(int mountDismountTime) {
+ this.mountDismountTime = mountDismountTime;
return this;
}
- public VehicleWalkingPreferences.Builder withHopCost(int hopCost) {
- this.hopCost = hopCost;
+ public VehicleWalkingPreferences.Builder withMountDismountCost(int mountDismountCost) {
+ this.mountDismountCost = mountDismountCost;
return this;
}
diff --git a/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java b/src/main/java/org/opentripplanner/routing/core/VehicleRoutingOptimizeType.java
similarity index 52%
rename from src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java
rename to src/main/java/org/opentripplanner/routing/core/VehicleRoutingOptimizeType.java
index 1d639e0af8b..cb8436b83b3 100644
--- a/src/main/java/org/opentripplanner/routing/core/BicycleOptimizeType.java
+++ b/src/main/java/org/opentripplanner/routing/core/VehicleRoutingOptimizeType.java
@@ -5,10 +5,10 @@
import java.util.Set;
/**
- * When planning a bicycle route what should be optimized for. Optimize types are basically
- * combined presets of routing parameters, except for triangle.
+ * When planning a bicycle or scooter route what should be optimized for. Optimize types are
+ * basically combined presets of routing parameters, except for triangle.
*/
-public enum BicycleOptimizeType {
+public enum VehicleRoutingOptimizeType {
/** This was previously called QUICK */
SHORTEST_DURATION,
/** This was previously called SAFE */
@@ -19,14 +19,14 @@ public enum BicycleOptimizeType {
SAFEST_STREETS,
TRIANGLE;
- private static final Set NON_TRIANGLE_VALUES = Collections.unmodifiableSet(
+ private static final Set NON_TRIANGLE_VALUES = Collections.unmodifiableSet(
EnumSet.complementOf(EnumSet.of(TRIANGLE))
);
/**
- * Return all values that are not {@link BicycleOptimizeType#TRIANGLE}.
+ * Return all values that are not {@link VehicleRoutingOptimizeType#TRIANGLE}.
*/
- public static Set nonTriangleValues() {
+ public static Set nonTriangleValues() {
return NON_TRIANGLE_VALUES;
}
}
diff --git a/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java b/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java
index 830a80c39a5..047e44f229a 100644
--- a/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java
+++ b/src/main/java/org/opentripplanner/routing/linking/FlexLocationAdder.java
@@ -16,7 +16,7 @@ static void addFlexLocations(StreetEdge edge, IntersectionVertex v0, StopModel s
if (edge.getPermission().allows(StreetTraversalPermission.PEDESTRIAN_AND_CAR)) {
Point p = GeometryUtils.getGeometryFactory().createPoint(v0.getCoordinate());
Envelope env = p.getEnvelopeInternal();
- for (AreaStop areaStop : stopModel.queryLocationIndex(env)) {
+ for (AreaStop areaStop : stopModel.findAreaStops(env)) {
if (!areaStop.getGeometry().disjoint(p)) {
v0.addAreaStops(Set.of(areaStop));
}
diff --git a/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java b/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
index 4a18aae03c2..385b347d24a 100644
--- a/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
+++ b/src/main/java/org/opentripplanner/service/vehiclerental/street/StreetVehicleRentalLink.java
@@ -53,7 +53,10 @@ public State[] traverse(State s0) {
}
var preferences = s0.getPreferences().rental(s0.getRequest().mode());
- if (vehicleRentalPlaceVertex.getStation().networkIsNotAllowed(preferences)) {
+ // preferences will be null while finding nearest places with WALK mode
+ if (
+ preferences != null && vehicleRentalPlaceVertex.getStation().networkIsNotAllowed(preferences)
+ ) {
return State.empty();
}
diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
index fa3a7069e2d..bbc6733a40c 100644
--- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
+++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
@@ -114,7 +114,7 @@ public interface OtpServerRequestContext {
TraverseVisitor traverseVisitor();
default GraphFinder graphFinder() {
- return GraphFinder.getInstance(graph(), transitService()::findRegularStop);
+ return GraphFinder.getInstance(graph(), transitService()::findRegularStops);
}
FlexConfig flexConfig();
diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java
index ce880058005..0e048a2e3e7 100644
--- a/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java
+++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/EnumMapper.java
@@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.Optional;
import org.opentripplanner.framework.doc.DocumentedEnum;
+import org.opentripplanner.framework.lang.StringUtils;
public class EnumMapper {
@@ -23,11 +24,7 @@ public static Optional extends Enum>> mapToEnum2(String text, Class extend
}
public static String toString(Enum> en) {
- return kebabCase(en.name());
- }
-
- public static String kebabCase(String input) {
- return input.toLowerCase().replace('_', '-');
+ return StringUtils.kebabCase(en.name());
}
/**
diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java
index 296c07805f9..06675475fd1 100644
--- a/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java
+++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/NodeInfo.java
@@ -7,6 +7,7 @@
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opentripplanner.framework.lang.StringUtils;
import org.opentripplanner.framework.tostring.ValueObjectToStringBuilder;
/**
@@ -137,7 +138,7 @@ public List extends Enum>> enumTypeValues() {
*/
public String toMarkdownString(Object value) {
if (enumType != null) {
- value = EnumMapper.kebabCase(value.toString());
+ value = StringUtils.kebabCase(value.toString());
}
return type.quote(value);
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java
index f1d90d0ec40..088a7794585 100644
--- a/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java
+++ b/src/main/java/org/opentripplanner/standalone/config/framework/json/ParameterBuilder.java
@@ -303,14 +303,15 @@ public > Map asEnumMap(Class enumType, Class el
*/
public > Map asEnumMap(
Class enumType,
- Function typeMapper
+ Function typeMapper,
+ Map defaultValue
) {
info.withOptional().withEnumMap(enumType, OBJECT);
var mapNode = buildObject();
if (mapNode.isEmpty()) {
- return Map.of();
+ return defaultValue;
}
EnumMap result = new EnumMap<>(enumType);
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
index 6f7d6967ce8..d6beac6d256 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerconfig/VectorTileConfig.java
@@ -18,18 +18,23 @@
public class VectorTileConfig
implements VectorTilesResource.LayersParameters {
- public static final VectorTileConfig DEFAULT = new VectorTileConfig(List.of(), null);
+ public static final VectorTileConfig DEFAULT = new VectorTileConfig(List.of(), null, null);
private final List> layers;
@Nullable
private final String basePath;
+ @Nullable
+ private final String attribution;
+
VectorTileConfig(
Collection extends LayerParameters> layers,
- @Nullable String basePath
+ @Nullable String basePath,
+ @Nullable String attribution
) {
this.layers = List.copyOf(layers);
this.basePath = basePath;
+ this.attribution = attribution;
}
@Override
@@ -41,6 +46,10 @@ public Optional basePath() {
return Optional.ofNullable(basePath);
}
+ public Optional attribution() {
+ return Optional.ofNullable(attribution);
+ }
+
public static VectorTileConfig mapVectorTilesParameters(NodeAdapter node, String paramName) {
var root = node.of(paramName).summary("Vector tile configuration").asObject();
return new VectorTileConfig(
@@ -71,7 +80,23 @@ public static VectorTileConfig mapVectorTilesParameters(NodeAdapter node, String
is expected to be handled by a proxy.
"""
)
- .asString(DEFAULT.basePath)
+ .asString(DEFAULT.basePath),
+ root
+ .of("attribution")
+ .since(V2_5)
+ .summary("Custom attribution to be returned in `tilejson.json`")
+ .description(
+ """
+ By default the, `attribution` property in `tilejson.json` is computed from the names and
+ URLs of the feed publishers.
+ If the OTP deployment contains many fields, this can become very unwieldy.
+
+ This configuration parameter allows you to set the `attribution` to any string you wish
+ including HTML tags,
+ for example `Regional Partners`.
+ """
+ )
+ .asString(DEFAULT.attribution)
);
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
index 4538f6de84d..19a494a3024 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/RouteRequestConfig.java
@@ -15,15 +15,20 @@
import static org.opentripplanner.standalone.config.routerequest.WheelchairConfig.mapWheelchairPreferences;
import java.time.Duration;
+import java.util.List;
+import java.util.stream.Collectors;
import org.opentripplanner.api.parameter.QualifiedModeSet;
import org.opentripplanner.framework.application.OTPFeature;
+import org.opentripplanner.framework.lang.StringUtils;
import org.opentripplanner.routing.api.request.RequestModes;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;
+import org.opentripplanner.routing.api.request.preference.AccessEgressPreferences;
import org.opentripplanner.routing.api.request.preference.BikePreferences;
import org.opentripplanner.routing.api.request.preference.CarPreferences;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
+import org.opentripplanner.routing.api.request.preference.ScooterPreferences;
import org.opentripplanner.routing.api.request.preference.StreetPreferences;
import org.opentripplanner.routing.api.request.preference.SystemPreferences;
import org.opentripplanner.routing.api.request.preference.TransitPreferences;
@@ -181,6 +186,7 @@ private static void mapPreferences(NodeAdapter c, RoutingPreferences.Builder pre
preferences.withBike(it -> mapBikePreferences(c, it));
preferences.withStreet(it -> mapStreetPreferences(c, it));
preferences.withCar(it -> mapCarPreferences(c, it));
+ preferences.withScooter(it -> mapScooterPreferences(c, it));
preferences.withSystem(it -> mapSystemPreferences(c, it));
preferences.withTransfer(it -> mapTransferPreferences(c, it));
preferences.withWalk(it -> mapWalkPreferences(c, it));
@@ -455,7 +461,9 @@ private static void mapStreetPreferences(NodeAdapter c, StreetPreferences.Builde
the access legs used. In other cases where the access(CAR) is faster than transit the
performance will be better.
- The default is no penalty, if not configured.
+ The default values are
+
+ %s
Example: `"car-to-park" : { "timePenalty": "10m + 1.5t", "costFactor": 2.5 }`
@@ -470,9 +478,15 @@ the access legs used. In other cases where the access(CAR) is faster than transi
The `costFactor` is used to add an additional cost to the leg´s generalized-cost. The
time-penalty is multiplied with the cost-factor. A cost-factor of zero, gives no
extra cost, while 1.0 will add the same amount to both time and cost.
- """
+ """.formatted(
+ formatPenaltyDefaultValues(dftAccessEgress)
+ )
+ )
+ .asEnumMap(
+ StreetMode.class,
+ TimeAndCostPenaltyMapper::map,
+ dftAccessEgress.penalty().asEnumMap()
)
- .asEnumMap(StreetMode.class, TimeAndCostPenaltyMapper::map)
)
.withMaxDuration(
cae
@@ -569,6 +583,16 @@ The street search(AStar) aborts after this duration and any paths found are retu
);
}
+ private static String formatPenaltyDefaultValues(AccessEgressPreferences dftAccessEgress) {
+ return dftAccessEgress
+ .penalty()
+ .asEnumMap()
+ .entrySet()
+ .stream()
+ .map(s -> "- `%s` = %s".formatted(StringUtils.kebabCase(s.getKey().toString()), s.getValue()))
+ .collect(Collectors.joining("\n"));
+ }
+
private static void mapCarPreferences(NodeAdapter root, CarPreferences.Builder builder) {
var dft = builder.original();
NodeAdapter c = root.of("car").since(V2_5).summary("Car preferences.").asObject();
@@ -621,6 +645,41 @@ private static void mapCarPreferences(NodeAdapter root, CarPreferences.Builder b
.withRental(it -> mapRental(c, it));
}
+ private static void mapScooterPreferences(NodeAdapter root, ScooterPreferences.Builder builder) {
+ var dft = builder.original();
+ NodeAdapter c = root.of("scooter").since(V2_5).summary("Scooter preferences.").asObject();
+ builder
+ .withSpeed(
+ c
+ .of("speed")
+ .since(V2_0)
+ .summary("Max scooter speed along streets, in meters per second")
+ .asDouble(dft.speed())
+ )
+ .withReluctance(
+ c
+ .of("reluctance")
+ .since(V2_0)
+ .summary(
+ "A multiplier for how bad scooter travel is, compared to being in transit for equal lengths of time."
+ )
+ .asDouble(dft.reluctance())
+ )
+ .withOptimizeType(
+ c
+ .of("optimization")
+ .since(V2_0)
+ .summary("The set of characteristics that the user wants to optimize for.")
+ .description(
+ "If the triangle optimization is used, it's enough to just define the triangle parameters"
+ )
+ .asEnum(dft.optimizeType())
+ )
+ // triangle overrides the optimization type if defined
+ .withForcedOptimizeTriangle(it -> mapOptimizationTriangle(c, it))
+ .withRental(it -> mapRental(c, it));
+ }
+
private static void mapSystemPreferences(NodeAdapter c, SystemPreferences.Builder builder) {
var dft = builder.original();
builder
diff --git a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java
index f2ba922c8e3..2b20e12f755 100644
--- a/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/routerequest/VehicleWalkingConfig.java
@@ -43,9 +43,9 @@ private static void mapVehicleWalkingPreferences(
)
.asDouble(dft.reluctance())
)
- .withHopTime(
+ .withMountDismountTime(
c
- .of("hopTime")
+ .of("mountDismountTime")
.since(V2_0)
.summary("The time it takes the user to hop on or off a vehicle.")
.description(
@@ -54,11 +54,11 @@ private static void mapVehicleWalkingPreferences(
for controlling the duration of those events.
"""
)
- .asDuration(dft.hopTime())
+ .asDuration(dft.mountDismountTime())
)
- .withHopCost(
+ .withMountDismountCost(
c
- .of("hopCost")
+ .of("mountDismountCost")
.since(V2_0)
.summary("The cost of hopping on or off a vehicle.")
.description(
@@ -67,7 +67,7 @@ private static void mapVehicleWalkingPreferences(
not meant for controlling the cost of those events.
"""
)
- .asInt(dft.hopCost().toSeconds())
+ .asInt(dft.mountDismountCost().toSeconds())
)
.withStairsReluctance(
c
diff --git a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java
index 799a5b006e6..7f61982434d 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/BikeWalkableEdge.java
@@ -17,8 +17,10 @@ default void switchToWalkingBike(RoutingPreferences preferences, StateEditor edi
editor.setBackWalkingBike(true);
if (shouldIncludeCost) {
- editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds());
- editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds());
+ editor.incrementWeight(preferences.bike().walking().mountDismountCost().toSeconds());
+ editor.incrementTimeInSeconds(
+ (int) preferences.bike().walking().mountDismountTime().toSeconds()
+ );
}
}
@@ -28,8 +30,10 @@ default void switchToBiking(RoutingPreferences preferences, StateEditor editor)
editor.setBackWalkingBike(false);
if (shouldIncludeCost) {
- editor.incrementWeight(preferences.bike().walking().hopCost().toSeconds());
- editor.incrementTimeInSeconds((int) preferences.bike().walking().hopTime().toSeconds());
+ editor.incrementWeight(preferences.bike().walking().mountDismountCost().toSeconds());
+ editor.incrementTimeInSeconds(
+ (int) preferences.bike().walking().mountDismountTime().toSeconds()
+ );
}
}
diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java b/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
index 1e3594ee04b..ca33396b0a3 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
@@ -234,8 +234,9 @@ public double calculateSpeed(
case WALK -> walkingBike
? preferences.bike().walking().speed()
: preferences.walk().speed();
- case BICYCLE, SCOOTER -> preferences.bike().speed();
+ case BICYCLE -> preferences.bike().speed();
case CAR -> getCarSpeed();
+ case SCOOTER -> preferences.scooter().speed();
case FLEX -> throw new IllegalArgumentException("getSpeed(): Invalid mode " + traverseMode);
};
@@ -1098,7 +1099,7 @@ private StateEditor doTraverse(State s0, TraverseMode traverseMode, boolean walk
var traversalCosts =
switch (traverseMode) {
- case BICYCLE, SCOOTER -> bicycleTraversalCost(preferences, speed);
+ case BICYCLE, SCOOTER -> bicycleOrScooterTraversalCost(preferences, traverseMode, speed);
case WALK -> walkingTraversalCosts(
preferences,
traverseMode,
@@ -1213,10 +1214,17 @@ private TraversalCosts otherTraversalCosts(
}
@Nonnull
- private TraversalCosts bicycleTraversalCost(RoutingPreferences pref, double speed) {
+ private TraversalCosts bicycleOrScooterTraversalCost(
+ RoutingPreferences pref,
+ TraverseMode mode,
+ double speed
+ ) {
double time = getEffectiveBikeDistance() / speed;
double weight;
- switch (pref.bike().optimizeType()) {
+ var optimizeType = mode == TraverseMode.BICYCLE
+ ? pref.bike().optimizeType()
+ : pref.scooter().optimizeType();
+ switch (optimizeType) {
case SAFEST_STREETS -> {
weight = bicycleSafetyFactor * getDistanceMeters() / speed;
if (bicycleSafetyFactor <= SAFEST_STREETS_SAFETY_FACTOR) {
@@ -1232,20 +1240,17 @@ private TraversalCosts bicycleTraversalCost(RoutingPreferences pref, double spee
double quick = getEffectiveBikeDistance();
double safety = getEffectiveBicycleSafetyDistance();
double slope = getEffectiveBikeDistanceForWorkCost();
- weight =
- quick *
- pref.bike().optimizeTriangle().time() +
- slope *
- pref.bike().optimizeTriangle().slope() +
- safety *
- pref.bike().optimizeTriangle().safety();
+ var triangle = mode == TraverseMode.BICYCLE
+ ? pref.bike().optimizeTriangle()
+ : pref.scooter().optimizeTriangle();
+ weight = quick * triangle.time() + slope * triangle.slope() + safety * triangle.safety();
weight /= speed;
}
default -> weight = getDistanceMeters() / speed;
}
var reluctance = StreetEdgeReluctanceCalculator.computeReluctance(
pref,
- TraverseMode.BICYCLE,
+ mode,
false,
isStairs()
);
diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java b/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java
index ee2f3d833ee..a6fdf4ce6f1 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/StreetEdgeReluctanceCalculator.java
@@ -25,6 +25,7 @@ static double computeReluctance(
case WALK -> walkingBike ? pref.bike().walking().reluctance() : pref.walk().reluctance();
case BICYCLE -> pref.bike().reluctance();
case CAR -> pref.car().reluctance();
+ case SCOOTER -> pref.scooter().reluctance();
default -> throw new IllegalArgumentException(
"getReluctance(): Invalid mode " + traverseMode
);
diff --git a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
index df5b24a5928..dabd70bf28d 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/StreetTransitEntityLink.java
@@ -137,13 +137,13 @@ else if (
@Nonnull
private State[] buildState(State s0, StateEditor s1, RoutingPreferences pref) {
- var rentalPreferences = s0.getRequest().preferences().rental(s0.getRequest().mode());
- if (
- s0.isRentingVehicleFromStation() &&
- s0.mayKeepRentedVehicleAtDestination() &&
- rentalPreferences.allowArrivingInRentedVehicleAtDestination()
- ) {
- s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds());
+ if (s0.isRentingVehicleFromStation() && s0.mayKeepRentedVehicleAtDestination()) {
+ var rentalPreferences = s0.getRequest().preferences().rental(s0.getRequest().mode());
+ if (rentalPreferences.allowArrivingInRentedVehicleAtDestination()) {
+ s1.incrementWeight(
+ rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds()
+ );
+ }
}
s1.setBackMode(null);
diff --git a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
index d825a8fcbea..e93c6523d89 100644
--- a/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
+++ b/src/main/java/org/opentripplanner/street/model/edge/TemporaryFreeEdge.java
@@ -42,13 +42,13 @@ public State[] traverse(State s0) {
s1.incrementWeight(1);
s1.setBackMode(null);
- var rentalPreferences = s0.getPreferences().rental(s0.getRequest().mode());
- if (
- s0.isRentingVehicleFromStation() &&
- s0.mayKeepRentedVehicleAtDestination() &&
- rentalPreferences.allowArrivingInRentedVehicleAtDestination()
- ) {
- s1.incrementWeight(rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds());
+ if (s0.isRentingVehicleFromStation() && s0.mayKeepRentedVehicleAtDestination()) {
+ var rentalPreferences = s0.getPreferences().rental(s0.getRequest().mode());
+ if (rentalPreferences.allowArrivingInRentedVehicleAtDestination()) {
+ s1.incrementWeight(
+ rentalPreferences.arrivingInRentalVehicleAtDestinationCost().toSeconds()
+ );
+ }
}
return s1.makeStateArray();
diff --git a/src/main/java/org/opentripplanner/street/search/state/StateData.java b/src/main/java/org/opentripplanner/street/search/state/StateData.java
index cc2bfa5a57a..d1db14d91f4 100644
--- a/src/main/java/org/opentripplanner/street/search/state/StateData.java
+++ b/src/main/java/org/opentripplanner/street/search/state/StateData.java
@@ -87,7 +87,9 @@ public static List getInitialStateDatas(
return getInitialStateDatas(
request.mode(),
request.arriveBy(),
- rentalPreferences.allowArrivingInRentedVehicleAtDestination(),
+ rentalPreferences != null
+ ? rentalPreferences.allowArrivingInRentedVehicleAtDestination()
+ : false,
stateDataConstructor
);
}
@@ -102,7 +104,9 @@ public static StateData getBaseCaseStateData(StreetSearchRequest request) {
var stateDatas = getInitialStateDatas(
request.mode(),
request.arriveBy(),
- rentalPreferences.allowArrivingInRentedVehicleAtDestination(),
+ rentalPreferences != null
+ ? rentalPreferences.allowArrivingInRentedVehicleAtDestination()
+ : false,
StateData::new
);
diff --git a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
index e43278901d4..7bccdbb94df 100644
--- a/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
+++ b/src/main/java/org/opentripplanner/street/search/strategy/EuclideanRemainingWeightHeuristic.java
@@ -55,6 +55,9 @@ private double getStreetSpeedUpperBound(RoutingPreferences preferences, StreetMo
if (streetMode.includesBiking()) {
return preferences.bike().speed();
}
+ if (streetMode.includesScooter()) {
+ return preferences.scooter().speed();
+ }
return preferences.walk().speed();
}
diff --git a/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java b/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java
index edee00e27e1..c674d588238 100644
--- a/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java
+++ b/src/main/java/org/opentripplanner/transit/model/site/GroupStop.java
@@ -94,13 +94,12 @@ public Geometry getGeometry() {
/**
* Returns the geometry of the area that encompasses the bounds of this StopLocation group. If the
- * group is defined only as a list of stops, this will return the same as getGeometry. If on the
- * other hand the group is defined as an area and the stops are inferred from that area, then this
- * will return the geometry of the area.
+ * group is defined as all the stops within an area, then this will return the geometry of the
+ * area. If the group is defined simply as a list of stops, this will return an empty optional.
*/
@Override
public Optional extends Geometry> getEncompassingAreaGeometry() {
- return Optional.ofNullable(encompassingAreaGeometry).or(() -> Optional.of(geometry));
+ return Optional.ofNullable(encompassingAreaGeometry);
}
@Override
diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
index 0d08bcf34b2..ca88b7d3130 100644
--- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
+++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
@@ -41,6 +41,7 @@
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.organization.Operator;
import org.opentripplanner.transit.model.site.AreaStop;
+import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.MultiModalStation;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
@@ -226,6 +227,12 @@ public Collection listRegularStops() {
return transitModel.getStopModel().listRegularStops();
}
+ @Override
+ public Collection listGroupStops() {
+ OTPRequestTimeoutException.checkForTimeout();
+ return transitModel.getStopModel().listGroupStops();
+ }
+
@Override
public StopLocation getStopLocation(FeedScopedId id) {
return transitModel.getStopModel().getStopLocation(id);
@@ -543,7 +550,7 @@ public ZonedDateTime getTransitServiceStarts() {
}
@Override
- public Collection findRegularStop(Envelope envelope) {
+ public Collection findRegularStops(Envelope envelope) {
OTPRequestTimeoutException.checkForTimeout();
return transitModel.getStopModel().findRegularStops(envelope);
}
@@ -551,7 +558,7 @@ public Collection findRegularStop(Envelope envelope) {
@Override
public Collection findAreaStops(Envelope envelope) {
OTPRequestTimeoutException.checkForTimeout();
- return transitModel.getStopModel().queryLocationIndex(envelope);
+ return transitModel.getStopModel().findAreaStops(envelope);
}
@Override
diff --git a/src/main/java/org/opentripplanner/transit/service/StopModel.java b/src/main/java/org/opentripplanner/transit/service/StopModel.java
index dd6b65e5b33..763aa504c40 100644
--- a/src/main/java/org/opentripplanner/transit/service/StopModel.java
+++ b/src/main/java/org/opentripplanner/transit/service/StopModel.java
@@ -108,7 +108,7 @@ public StopModelBuilder withContext() {
}
/**
- * Return a regular transit stop if found(not flex stops).
+ * Return a regular transit stop if found (not flex stops).
*/
public RegularStop getRegularStop(FeedScopedId id) {
return regularStopById.get(id);
@@ -121,6 +121,9 @@ public Collection listRegularStops() {
return regularStopById.values();
}
+ /**
+ * Find regular stops within a geographical area.
+ */
public Collection findRegularStops(Envelope envelope) {
return index.findRegularStops(envelope);
}
@@ -131,21 +134,30 @@ public boolean hasAreaStops() {
/**
* Flex locations are generated by GTFS graph builder, but consumed only after the street graph is
- * built
+ * built.
*/
@Nullable
public AreaStop getAreaStop(FeedScopedId id) {
return areaStopById.get(id);
}
+ /**
+ * Return all flex stops, not regular transit stops and flex group of stops.
+ */
public Collection listAreaStops() {
return areaStopById.values();
}
- public Collection queryLocationIndex(Envelope envelope) {
+ /**
+ * Find flex stops within a geographical area.
+ */
+ public Collection findAreaStops(Envelope envelope) {
return index.findAreaStops(envelope);
}
+ /**
+ * Return all flex groups of stops.
+ */
public Collection listGroupStops() {
return groupStopById.values();
}
diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java
index d0664aa292d..17354ec4a20 100644
--- a/src/main/java/org/opentripplanner/transit/service/TransitService.java
+++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java
@@ -32,6 +32,7 @@
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.organization.Operator;
import org.opentripplanner.transit.model.site.AreaStop;
+import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.MultiModalStation;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
@@ -95,6 +96,8 @@ public interface TransitService {
Collection listRegularStops();
+ Collection listGroupStops();
+
StopLocation getStopLocation(FeedScopedId parseId);
Collection getStopOrChildStops(FeedScopedId id);
@@ -187,7 +190,7 @@ List stopTimesForPatternAtStop(
boolean transitFeedCovers(Instant dateTime);
- Collection findRegularStop(Envelope envelope);
+ Collection findRegularStops(Envelope envelope);
Collection findAreaStops(Envelope envelope);
diff --git a/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java b/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java
index f15aff9048b..6ab7d86917c 100644
--- a/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java
+++ b/src/main/java/org/opentripplanner/visualizer/GraphVisualizer.java
@@ -64,7 +64,7 @@
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.graph_builder.issue.api.DataImportIssue;
import org.opentripplanner.routing.api.request.RouteRequest;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.GraphPathFinder;
import org.opentripplanner.street.model.edge.Edge;
@@ -490,6 +490,12 @@ protected void route(String from, String to) {
// there should be a ui element for walk distance and optimize type
.withOptimizeType(getSelectedOptimizeType())
);
+ preferences.withScooter(scooter ->
+ scooter
+ .withSpeed(Float.parseFloat(bikeSpeed.getText()))
+ // there should be a ui element for walk distance and optimize type
+ .withOptimizeType(getSelectedOptimizeType())
+ );
});
System.out.println("--------");
@@ -547,20 +553,20 @@ protected void route(String from, String to) {
}
}
- BicycleOptimizeType getSelectedOptimizeType() {
+ VehicleRoutingOptimizeType getSelectedOptimizeType() {
if (opQuick.isSelected()) {
- return BicycleOptimizeType.SHORTEST_DURATION;
+ return VehicleRoutingOptimizeType.SHORTEST_DURATION;
}
if (opSafe.isSelected()) {
- return BicycleOptimizeType.SAFE_STREETS;
+ return VehicleRoutingOptimizeType.SAFE_STREETS;
}
if (opFlat.isSelected()) {
- return BicycleOptimizeType.FLAT_STREETS;
+ return VehicleRoutingOptimizeType.FLAT_STREETS;
}
if (opGreenways.isSelected()) {
- return BicycleOptimizeType.SAFEST_STREETS;
+ return VehicleRoutingOptimizeType.SAFEST_STREETS;
}
- return BicycleOptimizeType.SHORTEST_DURATION;
+ return VehicleRoutingOptimizeType.SHORTEST_DURATION;
}
private Container makeDiffTab() {
diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
index 25cf95ca2f9..9c0eae88fa1 100644
--- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
+++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
@@ -774,7 +774,7 @@ type QueryType {
"Input type for executing a travel search for a trip between two locations. Returns trip patterns describing suggested alternatives for the trip."
trip(
"Time and cost penalty on access/egress modes."
- accessEgressPenalty: [PenaltyForStreetMode!] = [],
+ accessEgressPenalty: [PenaltyForStreetMode!] = [{streetMode : car_park, timePenalty : "20m + 2.0 t", costFactor : 1.5}, {streetMode : flexible, timePenalty : "10m + 1.30 t", costFactor : 1.3}],
"The alightSlack is the minimum extra time after exiting a public transport vehicle. This is the default value used, if not overridden by the 'alightSlackList'."
alightSlackDefault: Int = 0,
"List of alightSlack for a given set of modes. Defaults: []"
diff --git a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
index b05dd77e2a9..ecba3320838 100644
--- a/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/gtfs/mapping/RouteRequestMapperTest.java
@@ -5,8 +5,8 @@
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.params.provider.Arguments.of;
-import static org.opentripplanner.routing.core.BicycleOptimizeType.SAFE_STREETS;
-import static org.opentripplanner.routing.core.BicycleOptimizeType.TRIANGLE;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.SAFE_STREETS;
+import static org.opentripplanner.routing.core.VehicleRoutingOptimizeType.TRIANGLE;
import graphql.ExecutionInput;
import graphql.execution.ExecutionId;
@@ -55,7 +55,7 @@ class RouteRequestMapperTest implements PlanTestConstants {
graph.getVehicleParkingService(),
new DefaultVehicleRentalService(),
new DefaultRealtimeVehicleService(transitService),
- GraphFinder.getInstance(graph, transitService::findRegularStop),
+ GraphFinder.getInstance(graph, transitService::findRegularStops),
new RouteRequest()
);
}
diff --git a/src/test/java/org/opentripplanner/apis/support/TileJsonTest.java b/src/test/java/org/opentripplanner/apis/support/TileJsonTest.java
index ac3b7bca522..73c8cd1369e 100644
--- a/src/test/java/org/opentripplanner/apis/support/TileJsonTest.java
+++ b/src/test/java/org/opentripplanner/apis/support/TileJsonTest.java
@@ -2,16 +2,33 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.time.LocalDate;
import java.util.List;
import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import org.opentripplanner.model.FeedInfo;
+import org.opentripplanner.service.worldenvelope.model.WorldEnvelope;
import org.opentripplanner.test.support.HttpForTest;
class TileJsonTest {
private static final List LAYERS = List.of("stops", "rentalVehicles");
+ private static final WorldEnvelope ENVELOPE = WorldEnvelope
+ .of()
+ .expandToIncludeStreetEntities(1, 1)
+ .expandToIncludeStreetEntities(2, 2)
+ .build();
+ private static final FeedInfo FEED_INFO = new FeedInfo(
+ "1",
+ "Trimet",
+ "https://trimet.org",
+ "en",
+ LocalDate.MIN,
+ LocalDate.MIN,
+ "1"
+ );
@ParameterizedTest
@ValueSource(
@@ -40,4 +57,23 @@ void defaultPath() {
TileJson.urlWithDefaultPath(uriInfo, req, LAYERS, "default", "vectorTiles")
);
}
+
+ @Test
+ void attributionFromFeedInfo() {
+ var tileJson = new TileJson("http://example.com", ENVELOPE, List.of(FEED_INFO));
+ assertEquals("Trimet", tileJson.attribution);
+ }
+
+ @Test
+ void duplicateAttribution() {
+ var tileJson = new TileJson("http://example.com", ENVELOPE, List.of(FEED_INFO, FEED_INFO));
+ assertEquals("Trimet", tileJson.attribution);
+ }
+
+ @Test
+ void attributionFromOverride() {
+ var override = "OVERRIDE";
+ var tileJson = new TileJson("http://example.com", ENVELOPE, override);
+ assertEquals(override, tileJson.attribution);
+ }
}
diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapperTest.java
new file mode 100644
index 00000000000..160014213d3
--- /dev/null
+++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/RequestModesMapperTest.java
@@ -0,0 +1,57 @@
+package org.opentripplanner.apis.transmodel.mapping;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.routing.api.request.RequestModes;
+import org.opentripplanner.routing.api.request.StreetMode;
+
+class RequestModesMapperTest {
+
+ @Test
+ void testMapRequestModesEmptyMapReturnsDefaults() {
+ Map inputModes = Map.of();
+
+ RequestModes mappedModes = RequestModesMapper.mapRequestModes(inputModes);
+
+ assertEquals(RequestModes.of().build(), mappedModes);
+ }
+
+ @Test
+ void testMapRequestModesAccessSetReturnsDefaultsForOthers() {
+ Map inputModes = Map.of("accessMode", StreetMode.BIKE);
+
+ RequestModes wantModes = RequestModes
+ .of()
+ .withAccessMode(StreetMode.BIKE)
+ .withTransferMode(StreetMode.BIKE)
+ .build();
+
+ RequestModes mappedModes = RequestModesMapper.mapRequestModes(inputModes);
+
+ assertEquals(wantModes, mappedModes);
+ }
+
+ @Test
+ void testMapRequestModesEgressSetReturnsDefaultsForOthers() {
+ Map inputModes = Map.of("egressMode", StreetMode.CAR);
+
+ RequestModes wantModes = RequestModes.of().withEgressMode(StreetMode.CAR).build();
+
+ RequestModes mappedModes = RequestModesMapper.mapRequestModes(inputModes);
+
+ assertEquals(wantModes, mappedModes);
+ }
+
+ @Test
+ void testMapRequestModesDirectSetReturnsDefaultsForOthers() {
+ Map inputModes = Map.of("directMode", StreetMode.CAR);
+
+ RequestModes wantModes = RequestModes.of().withDirectMode(StreetMode.CAR).build();
+
+ RequestModes mappedModes = RequestModesMapper.mapRequestModes(inputModes);
+
+ assertEquals(wantModes, mappedModes);
+ }
+}
diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
index 127fe66f0ea..87004fd9cfa 100644
--- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
@@ -40,7 +40,7 @@
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.preference.StreetPreferences;
import org.opentripplanner.routing.api.request.preference.TimeSlopeSafetyTriangle;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.service.realtimevehicles.internal.DefaultRealtimeVehicleService;
import org.opentripplanner.service.vehiclerental.internal.DefaultVehicleRentalService;
@@ -255,14 +255,14 @@ public void testMaxDirectDurationForFlexWithTooLongDuration() {
public void testBikeTriangleFactors() {
Map arguments = Map.of(
"bicycleOptimisationMethod",
- BicycleOptimizeType.TRIANGLE,
+ VehicleRoutingOptimizeType.TRIANGLE,
"triangleFactors",
Map.of("safety", 0.1, "slope", 0.1, "time", 0.8)
);
var req1 = TripRequestMapper.createRequest(executionContext(arguments));
- assertEquals(BicycleOptimizeType.TRIANGLE, req1.preferences().bike().optimizeType());
+ assertEquals(VehicleRoutingOptimizeType.TRIANGLE, req1.preferences().bike().optimizeType());
assertEquals(
new TimeSlopeSafetyTriangle(0.8, 0.1, 0.1),
req1.preferences().bike().optimizeTriangle()
@@ -272,18 +272,18 @@ public void testBikeTriangleFactors() {
@Test
void testDefaultTriangleFactors() {
var req2 = TripRequestMapper.createRequest(executionContext(Map.of()));
- assertEquals(BicycleOptimizeType.SAFE_STREETS, req2.preferences().bike().optimizeType());
+ assertEquals(VehicleRoutingOptimizeType.SAFE_STREETS, req2.preferences().bike().optimizeType());
assertEquals(TimeSlopeSafetyTriangle.DEFAULT, req2.preferences().bike().optimizeTriangle());
}
- static Stream noTriangleCases = BicycleOptimizeType
+ static Stream noTriangleCases = VehicleRoutingOptimizeType
.nonTriangleValues()
.stream()
.map(Arguments::of);
@ParameterizedTest
@VariableSource("noTriangleCases")
- public void testBikeTriangleFactorsHasNoEffect(BicycleOptimizeType bot) {
+ public void testBikeTriangleFactorsHasNoEffect(VehicleRoutingOptimizeType bot) {
Map arguments = Map.of(
"bicycleOptimisationMethod",
bot,
diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java
index a72fd25cbfa..786ddc5c198 100644
--- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/BikePreferencesMapperTest.java
@@ -9,7 +9,7 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.apis.transmodel._support.TestDataFetcherDecorator;
import org.opentripplanner.routing.api.request.preference.BikePreferences;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
class BikePreferencesMapperTest {
@@ -23,7 +23,7 @@ static List mapBikePreferencesTestCases() {
Arguments.of("bikeSpeed", 10.0, "BikePreferences{speed: 10.0}"),
Arguments.of(
"bicycleOptimisationMethod",
- BicycleOptimizeType.TRIANGLE,
+ VehicleRoutingOptimizeType.TRIANGLE,
"BikePreferences{optimizeType: TRIANGLE}"
),
// No effect unless BicycleOptimize is TRIANGLE
@@ -64,7 +64,7 @@ static List mapBikePreferencesOptimizeTriangleTestCases() {
@ParameterizedTest
@MethodSource("mapBikePreferencesOptimizeTriangleTestCases")
void testMapBikePreferencesOptimizeTriangle(String field, Object value, String expected) {
- var preferences = BikePreferences.of().withOptimizeType(BicycleOptimizeType.TRIANGLE);
+ var preferences = BikePreferences.of().withOptimizeType(VehicleRoutingOptimizeType.TRIANGLE);
mapBikePreferences(preferences, TestDataFetcherDecorator.of(field, value));
assertEquals(
"BikePreferences{optimizeType: TRIANGLE, optimizeTriangle: " + expected + "}",
diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapperTest.java
new file mode 100644
index 00000000000..2b5c7563ac2
--- /dev/null
+++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/preferences/ScooterPreferencesMapperTest.java
@@ -0,0 +1,70 @@
+package org.opentripplanner.apis.transmodel.mapping.preferences;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.opentripplanner.apis.transmodel.mapping.preferences.ScooterPreferencesMapper.mapScooterPreferences;
+
+import java.util.List;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.opentripplanner.apis.transmodel._support.TestDataFetcherDecorator;
+import org.opentripplanner.routing.api.request.preference.ScooterPreferences;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
+
+class ScooterPreferencesMapperTest {
+
+ static List mapScooterPreferencesTestCases() {
+ return List.of(
+ Arguments.of("walkReluctance", 10.0, "ScooterPreferences{reluctance: 10.0}"),
+ Arguments.of("bikeSpeed", 10.0, "ScooterPreferences{speed: 10.0}"),
+ Arguments.of(
+ "bicycleOptimisationMethod",
+ VehicleRoutingOptimizeType.TRIANGLE,
+ "ScooterPreferences{optimizeType: TRIANGLE}"
+ ),
+ // No effect unless BicycleOptimize is TRIANGLE
+ Arguments.of("triangleFactors.time", 0.17, "ScooterPreferences{}"),
+ Arguments.of("triangleFactors.slope", 0.12, "ScooterPreferences{}"),
+ Arguments.of("triangleFactors.safety", 0.13, "ScooterPreferences{}")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("mapScooterPreferencesTestCases")
+ void testMapScooterPreferences(String field, Object value, String expected) {
+ var preferences = ScooterPreferences.of();
+ mapScooterPreferences(preferences, TestDataFetcherDecorator.of(field, value));
+ assertEquals(expected, preferences.build().toString());
+ }
+
+ static List mapScooterPreferencesOptimizeTriangleTestCases() {
+ return List.of(
+ Arguments.of(
+ "triangleFactors.time",
+ 0.17,
+ "TimeSlopeSafetyTriangle[time=1.0, slope=0.0, safety=0.0]"
+ ),
+ Arguments.of(
+ "triangleFactors.slope",
+ 0.12,
+ "TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]"
+ ),
+ Arguments.of(
+ "triangleFactors.safety",
+ 0.13,
+ "TimeSlopeSafetyTriangle[time=0.0, slope=0.0, safety=1.0]"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("mapScooterPreferencesOptimizeTriangleTestCases")
+ void testMapScooterPreferencesOptimizeTriangle(String field, Object value, String expected) {
+ var preferences = ScooterPreferences.of().withOptimizeType(VehicleRoutingOptimizeType.TRIANGLE);
+ mapScooterPreferences(preferences, TestDataFetcherDecorator.of(field, value));
+ assertEquals(
+ "ScooterPreferences{optimizeType: TRIANGLE, optimizeTriangle: " + expected + "}",
+ preferences.build().toString()
+ );
+ }
+}
diff --git a/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java b/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java
index f2c3ebf49de..d971cefe2e2 100644
--- a/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java
+++ b/src/test/java/org/opentripplanner/apis/vectortiles/DebugStyleSpecTest.java
@@ -17,9 +17,10 @@ void spec() {
var vectorSource = new VectorSource("vectorSource", "https://example.com");
var regularStops = new VectorSourceLayer(vectorSource, "stops");
var areaStops = new VectorSourceLayer(vectorSource, "stops");
+ var groupStops = new VectorSourceLayer(vectorSource, "stops");
var edges = new VectorSourceLayer(vectorSource, "edges");
var vertices = new VectorSourceLayer(vectorSource, "vertices");
- var spec = DebugStyleSpec.build(regularStops, areaStops, edges, vertices);
+ var spec = DebugStyleSpec.build(regularStops, areaStops, groupStops, edges, vertices);
var json = ObjectMappers.ignoringExtraFields().valueToTree(spec);
var expectation = RESOURCES.fileToString("style.json");
diff --git a/src/test/java/org/opentripplanner/framework/io/HttpUtilsTest.java b/src/test/java/org/opentripplanner/framework/io/HttpUtilsTest.java
new file mode 100644
index 00000000000..ac8ae52848c
--- /dev/null
+++ b/src/test/java/org/opentripplanner/framework/io/HttpUtilsTest.java
@@ -0,0 +1,38 @@
+package org.opentripplanner.framework.io;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.Map;
+import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.opentripplanner.test.support.HttpForTest;
+
+class HttpUtilsTest {
+
+ private static final String HOSTNAME = "example.com";
+
+ static List testCases() {
+ return List.of(
+ HOSTNAME,
+ "example.com,",
+ " example.com ,",
+ "example.com,example.com",
+ "example.com, example.com",
+ "example.com, example.net",
+ "example.com, example.net, example.com",
+ " example.com, example.net, example.com"
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ void extractHost(String headerValue) {
+ var req = HttpForTest.containerRequest();
+ req.headers(Map.of("X-Forwarded-Host", List.of(headerValue)));
+ var uriInfo = new UriRoutingContext(req);
+ var baseAddress = HttpUtils.getBaseAddress(uriInfo, req);
+ assertEquals("https://" + HOSTNAME, baseAddress);
+ }
+}
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java b/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
index 4cdada95e4b..52069a7a72e 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/osm/TriangleInequalityTest.java
@@ -182,6 +182,7 @@ private void checkTriangleInequality(RequestModes modes, List fil
.withStreet(street -> street.withTurnReluctance(1.0))
.withCar(car -> car.withSpeed(1.0).withReluctance(1.0))
.withBike(bike -> bike.withSpeed(1.0).withReluctance(1.0))
+ .withScooter(scooter -> scooter.withSpeed(1.0).withReluctance(1.0))
);
if (modes != null) {
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java b/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java
index 53e340dcc63..dfb062226a4 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/TurnCostTest.java
@@ -94,6 +94,7 @@ public void before() {
preferences
.withCar(it -> it.withSpeed(1.0).withReluctance(1.0))
.withBike(bike -> bike.withSpeed(1.0).withReluctance(1.0))
+ .withScooter(scooter -> scooter.withSpeed(1.0).withReluctance(1.0))
.withWalk(walk -> walk.withSpeed(1.0).withStairsReluctance(1.0).withReluctance(1.0))
.withStreet(it -> it.withTurnReluctance(1.0))
);
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java
index 9027b0a0dd5..677edfcbabf 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/ElevationSnapshotTest.java
@@ -19,7 +19,7 @@
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.error.RoutingValidationException;
@ExtendWith(SnapshotExtension.class)
@@ -98,7 +98,7 @@ public void directBike() {
request.withPreferences(pref ->
pref.withBike(bike ->
bike
- .withOptimizeType(BicycleOptimizeType.TRIANGLE)
+ .withOptimizeType(VehicleRoutingOptimizeType.TRIANGLE)
.withOptimizeTriangle(b -> b.withTime(0.3).withSlope(0.4).withSafety(0.3))
)
);
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
index 8a6ac3f4f45..3bbaf0105e5 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/BikePreferencesTest.java
@@ -5,7 +5,7 @@
import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
class BikePreferencesTest {
@@ -16,7 +16,8 @@ class BikePreferencesTest {
.of()
.withSlope(1)
.build();
- public static final BicycleOptimizeType OPTIMIZE_TYPE = BicycleOptimizeType.TRIANGLE;
+ public static final VehicleRoutingOptimizeType OPTIMIZE_TYPE =
+ VehicleRoutingOptimizeType.TRIANGLE;
public static final int RENTAL_PICKUP_TIME = 30;
public static final int PARK_COST = 30;
@@ -106,20 +107,20 @@ void testForcedTriangleOptimization() {
.of()
.withForcedOptimizeTriangle(it -> it.withSlope(1).build())
.build();
- assertEquals(BicycleOptimizeType.TRIANGLE, trianglePreferences.optimizeType());
+ assertEquals(VehicleRoutingOptimizeType.TRIANGLE, trianglePreferences.optimizeType());
var conflictingPreferences = BikePreferences
.of()
- .withOptimizeType(BicycleOptimizeType.SAFE_STREETS)
+ .withOptimizeType(VehicleRoutingOptimizeType.SAFE_STREETS)
.withForcedOptimizeTriangle(it -> it.withSlope(1).build())
.build();
- assertEquals(BicycleOptimizeType.TRIANGLE, conflictingPreferences.optimizeType());
+ assertEquals(VehicleRoutingOptimizeType.TRIANGLE, conflictingPreferences.optimizeType());
var emptyTrianglePreferences = BikePreferences
.of()
- .withOptimizeType(BicycleOptimizeType.SAFE_STREETS)
+ .withOptimizeType(VehicleRoutingOptimizeType.SAFE_STREETS)
.withForcedOptimizeTriangle(it -> it.build())
.build();
- assertEquals(BicycleOptimizeType.SAFE_STREETS, emptyTrianglePreferences.optimizeType());
+ assertEquals(VehicleRoutingOptimizeType.SAFE_STREETS, emptyTrianglePreferences.optimizeType());
}
}
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/ScooterPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/ScooterPreferencesTest.java
new file mode 100644
index 00000000000..01a12ef5cbb
--- /dev/null
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/ScooterPreferencesTest.java
@@ -0,0 +1,109 @@
+package org.opentripplanner.routing.api.request.preference;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.opentripplanner.routing.api.request.preference.ImmutablePreferencesAsserts.assertEqualsAndHashCode;
+
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
+
+class ScooterPreferencesTest {
+
+ public static final double SPEED = 2.0;
+ public static final double RELUCTANCE = 1.2;
+ public static final TimeSlopeSafetyTriangle TRIANGLE = TimeSlopeSafetyTriangle
+ .of()
+ .withSlope(1)
+ .build();
+ public static final VehicleRoutingOptimizeType OPTIMIZE_TYPE =
+ VehicleRoutingOptimizeType.TRIANGLE;
+ public static final int RENTAL_PICKUP_TIME = 30;
+
+ private final ScooterPreferences subject = ScooterPreferences
+ .of()
+ .withSpeed(SPEED)
+ .withReluctance(RELUCTANCE)
+ .withOptimizeType(OPTIMIZE_TYPE)
+ .withRental(rental -> rental.withPickupTime(RENTAL_PICKUP_TIME).build())
+ .withOptimizeTriangle(it -> it.withSlope(1).build())
+ .build();
+
+ @Test
+ void speed() {
+ assertEquals(SPEED, subject.speed());
+ }
+
+ @Test
+ void reluctance() {
+ assertEquals(RELUCTANCE, subject.reluctance());
+ }
+
+ @Test
+ void optimizeType() {
+ assertEquals(OPTIMIZE_TYPE, subject.optimizeType());
+ }
+
+ @Test
+ void optimizeTriangle() {
+ assertEquals(TRIANGLE, subject.optimizeTriangle());
+ }
+
+ @Test
+ void rental() {
+ var vehicleRental = VehicleRentalPreferences.of().withPickupTime(RENTAL_PICKUP_TIME).build();
+ assertEquals(vehicleRental, subject.rental());
+ }
+
+ @Test
+ void testOfAndCopyOf() {
+ // Return same object if no value is set
+ assertSame(ScooterPreferences.DEFAULT, ScooterPreferences.of().build());
+ assertSame(subject, subject.copyOf().build());
+ }
+
+ @Test
+ void testCopyOfEqualsAndHashCode() {
+ // Create a copy, make a change and set it back again to force creating a new object
+ var other = subject.copyOf().withSpeed(0.7).build();
+ var same = other.copyOf().withSpeed(SPEED).build();
+ assertEqualsAndHashCode(subject, other, same);
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("ScooterPreferences{}", ScooterPreferences.DEFAULT.toString());
+ assertEquals(
+ "ScooterPreferences{" +
+ "speed: 2.0, " +
+ "reluctance: 1.2, " +
+ "rental: VehicleRentalPreferences{pickupTime: 30s}, " +
+ "optimizeType: TRIANGLE, " +
+ "optimizeTriangle: TimeSlopeSafetyTriangle[time=0.0, slope=1.0, safety=0.0]" +
+ "}",
+ subject.toString()
+ );
+ }
+
+ @Test
+ void testForcedTriangleOptimization() {
+ var trianglePreferences = ScooterPreferences
+ .of()
+ .withForcedOptimizeTriangle(it -> it.withSlope(1).build())
+ .build();
+ assertEquals(VehicleRoutingOptimizeType.TRIANGLE, trianglePreferences.optimizeType());
+
+ var conflictingPreferences = ScooterPreferences
+ .of()
+ .withOptimizeType(VehicleRoutingOptimizeType.SAFE_STREETS)
+ .withForcedOptimizeTriangle(it -> it.withSlope(1).build())
+ .build();
+ assertEquals(VehicleRoutingOptimizeType.TRIANGLE, conflictingPreferences.optimizeType());
+
+ var emptyTrianglePreferences = ScooterPreferences
+ .of()
+ .withOptimizeType(VehicleRoutingOptimizeType.SAFE_STREETS)
+ .withForcedOptimizeTriangle(it -> it.build())
+ .build();
+ assertEquals(VehicleRoutingOptimizeType.SAFE_STREETS, emptyTrianglePreferences.optimizeType());
+ }
+}
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java
index 1d47337a312..a318474eab7 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/StreetPreferencesTest.java
@@ -109,10 +109,10 @@ void testToString() {
"routingTimeout: 3s, " +
"elevator: ElevatorPreferences{boardTime: 2m}, " +
"intersectionTraversalModel: CONSTANT, " +
- "accessEgress: AccessEgressPreferences{" +
- "penalty: TimeAndCostPenaltyForEnum{CAR_TO_PARK: " +
+ "accessEgress: AccessEgressPreferences{penalty: TimeAndCostPenaltyForEnum{CAR_TO_PARK: " +
CAR_PENALTY +
- "}, " +
+ ", CAR_RENTAL: (timePenalty: 20m + 2.0 t, costFactor: 1.50), CAR_HAILING: (timePenalty: 20m + 2.0 t, costFactor: 1.50), " +
+ "FLEXIBLE: (timePenalty: 10m + 1.30 t, costFactor: 1.30)}, " +
"maxDuration: DurationForStreetMode{default:5m}" +
"}, " +
"maxDirectDuration: DurationForStreetMode{default:10m}" +
diff --git a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java
index 9571eee8cc5..fdc416d7c0f 100644
--- a/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/api/request/preference/VehicleWalkingPreferencesTest.java
@@ -11,8 +11,8 @@ class VehicleWalkingPreferencesTest {
private static final double SPEED = 1.45;
private static final double RELUCTANCE = 5.5;
- private static final Duration HOP_TIME = Duration.ofSeconds(15);
- private static final Cost HOP_COST = Cost.costOfSeconds(20);
+ private static final Duration MOUNT_DISMOUNT_TIME = Duration.ofSeconds(15);
+ private static final Cost MOUNT_DISMOUNT_COST = Cost.costOfSeconds(20);
private static final double STAIRS_RELUCTANCE = 11;
private final VehicleWalkingPreferences subject = createPreferences();
@@ -28,13 +28,13 @@ void reluctance() {
}
@Test
- void hopTime() {
- assertEquals(HOP_TIME, subject.hopTime());
+ void mountDismountTime() {
+ assertEquals(MOUNT_DISMOUNT_TIME, subject.mountDismountTime());
}
@Test
- void hopCost() {
- assertEquals(HOP_COST, subject.hopCost());
+ void mountDismountCost() {
+ assertEquals(MOUNT_DISMOUNT_COST, subject.mountDismountCost());
}
@Test
@@ -57,8 +57,8 @@ void testToString() {
"VehicleWalkingPreferences{" +
"speed: 1.45, " +
"reluctance: 5.5, " +
- "hopTime: PT15S, " +
- "hopCost: $20, " +
+ "mountDismountTime: PT15S, " +
+ "mountDismountCost: $20, " +
"stairsReluctance: 11.0}",
subject.toString()
);
@@ -69,8 +69,8 @@ private VehicleWalkingPreferences createPreferences() {
.of()
.withSpeed(SPEED)
.withReluctance(RELUCTANCE)
- .withHopTime(HOP_TIME)
- .withHopCost(HOP_COST.toSeconds())
+ .withMountDismountTime(MOUNT_DISMOUNT_TIME)
+ .withMountDismountCost(MOUNT_DISMOUNT_COST.toSeconds())
.withStairsReluctance(STAIRS_RELUCTANCE)
.build();
}
diff --git a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
index c67d539e902..c3cff43b1a9 100644
--- a/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
+++ b/src/test/java/org/opentripplanner/routing/core/RoutingPreferencesTest.java
@@ -48,6 +48,16 @@ public void copyOfWithBikeChanges() {
assertSame(pref.walk(), copy.walk());
}
+ @Test
+ public void copyOfWithScooterChanges() {
+ var pref = new RoutingPreferences();
+ var copy = pref.copyOf().withScooter(b -> b.withReluctance(2.5)).build();
+
+ assertNotSame(pref, copy);
+ assertNotSame(pref.scooter(), copy.scooter());
+ assertSame(pref.walk(), copy.walk());
+ }
+
@Test
public void copyOfWithWalkChanges() {
var pref = new RoutingPreferences();
diff --git a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java
index f975579cbf5..be44d1ea86f 100644
--- a/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java
+++ b/src/test/java/org/opentripplanner/standalone/config/framework/json/NodeAdapterTest.java
@@ -225,15 +225,23 @@ public void asEnumMapWithCustomType() {
NodeAdapter subject = newNodeAdapterForTest("{ key : { A: {a:'Foo'} } }");
assertEquals(
Map.of(AnEnum.A, new ARecord("Foo")),
- subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson)
+ subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson, Map.of())
);
assertEquals(
Collections.emptyMap(),
- subject.of("missing-key").asEnumMap(AnEnum.class, ARecord::fromJson)
+ subject.of("missing-key").asEnumMap(AnEnum.class, ARecord::fromJson, Map.of())
);
assertEquals(NON_UNUSED_PARAMETERS, unusedParams(subject));
}
+ @Test
+ public void asEnumMapWithDefaultValue() {
+ var subject = newNodeAdapterForTest("{}");
+ final Map dflt = Map.of(AnEnum.A, new ARecord("Foo"));
+ assertEquals(dflt, subject.of("key").asEnumMap(AnEnum.class, ARecord::fromJson, dflt));
+ assertEquals(NON_UNUSED_PARAMETERS, unusedParams(subject));
+ }
+
@Test
public void asEnumMapWithUnknownKey() {
NodeAdapter subject = newNodeAdapterForTest("{ enumMap : { unknown : 7 } }");
diff --git a/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java b/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java
index d5b7733f3e8..db937ff2a6e 100644
--- a/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java
+++ b/src/test/java/org/opentripplanner/street/integration/BicycleRoutingTest.java
@@ -16,7 +16,7 @@
import org.opentripplanner.routing.algorithm.mapping.GraphPathToItineraryMapper;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.impl.GraphPathFinder;
import org.opentripplanner.street.search.TemporaryVerticesContainer;
@@ -76,7 +76,7 @@ private static String computePolyline(Graph graph, GenericLocation from, Generic
request.setFrom(from);
request.setTo(to);
request.withPreferences(p ->
- p.withBike(it -> it.withOptimizeType(BicycleOptimizeType.SHORTEST_DURATION))
+ p.withBike(it -> it.withOptimizeType(VehicleRoutingOptimizeType.SHORTEST_DURATION))
);
request.journey().direct().setMode(StreetMode.BIKE);
diff --git a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java
index 417f1b87347..41ec677b3bb 100644
--- a/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java
+++ b/src/test/java/org/opentripplanner/street/integration/BikeWalkingTest.java
@@ -375,7 +375,10 @@ private List runStreetSearchAndCreateDescriptor(
preferences
.withWalk(w -> w.withSpeed(10))
.withBike(it ->
- it.withSpeed(20d).withWalking(w -> w.withSpeed(5d).withHopTime(100).withHopCost(1000))
+ it
+ .withSpeed(20d)
+ .withWalking(w -> w.withSpeed(5d).withMountDismountTime(100).withMountDismountCost(1000)
+ )
)
);
request.setArriveBy(arriveBy);
diff --git a/src/test/java/org/opentripplanner/street/model/_data/StreetModelForTest.java b/src/test/java/org/opentripplanner/street/model/_data/StreetModelForTest.java
index 1f3dbf8c4c9..d50c8a74dfc 100644
--- a/src/test/java/org/opentripplanner/street/model/_data/StreetModelForTest.java
+++ b/src/test/java/org/opentripplanner/street/model/_data/StreetModelForTest.java
@@ -9,6 +9,9 @@
import org.opentripplanner.framework.geometry.SphericalDistanceLibrary;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.I18NString;
+import org.opentripplanner.service.vehiclerental.model.TestFreeFloatingRentalVehicleBuilder;
+import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex;
+import org.opentripplanner.street.model.RentalFormFactor;
import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.edge.StreetEdgeBuilder;
@@ -93,4 +96,19 @@ public static StreetEdge streetEdge(
) {
return streetEdge(from, to, 1, permissions);
}
+
+ public static VehicleRentalPlaceVertex rentalVertex(RentalFormFactor formFactor) {
+ var rentalVehicleBuilder = TestFreeFloatingRentalVehicleBuilder
+ .of()
+ .withLatitude(-122.575133)
+ .withLongitude(45.456773);
+ if (formFactor == RentalFormFactor.SCOOTER) {
+ rentalVehicleBuilder.withVehicleScooter();
+ } else if (formFactor == RentalFormFactor.BICYCLE) {
+ rentalVehicleBuilder.withVehicleBicycle();
+ } else if (formFactor == RentalFormFactor.CAR) {
+ rentalVehicleBuilder.withVehicleCar();
+ }
+ return new VehicleRentalPlaceVertex(rentalVehicleBuilder.build());
+ }
}
diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeScooterTraversalTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeScooterTraversalTest.java
new file mode 100644
index 00000000000..734b1efa2b9
--- /dev/null
+++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeScooterTraversalTest.java
@@ -0,0 +1,255 @@
+package org.opentripplanner.street.model.edge;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
+import org.opentripplanner.routing.api.request.StreetMode;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
+import org.opentripplanner.routing.util.ElevationUtils;
+import org.opentripplanner.routing.util.SlopeCosts;
+import org.opentripplanner.service.vehiclerental.street.StreetVehicleRentalLink;
+import org.opentripplanner.service.vehiclerental.street.VehicleRentalEdge;
+import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex;
+import org.opentripplanner.street.model.RentalFormFactor;
+import org.opentripplanner.street.model.StreetTraversalPermission;
+import org.opentripplanner.street.model._data.StreetModelForTest;
+import org.opentripplanner.street.model.vertex.StreetVertex;
+import org.opentripplanner.street.search.TraverseMode;
+import org.opentripplanner.street.search.request.StreetSearchRequest;
+import org.opentripplanner.street.search.state.State;
+
+public class StreetEdgeScooterTraversalTest {
+
+ private static final double DELTA = 0.00001;
+ private static final double SPEED = 6.0;
+
+ @Test
+ public void testTraverseFloatingScooter() {
+ // This test does not depend on the setup method - and can probably be simplified
+ Coordinate c1 = new Coordinate(-122.575033, 45.456773);
+ Coordinate c2 = new Coordinate(-122.576668, 45.451426);
+
+ var formFactor = RentalFormFactor.SCOOTER;
+ var rentalVertex = StreetModelForTest.rentalVertex(formFactor);
+ var vehicleRentalEdge = VehicleRentalEdge.createVehicleRentalEdge(rentalVertex, formFactor);
+
+ StreetVertex v1 = StreetModelForTest.intersectionVertex("v1", c1.x, c1.y);
+ StreetVertex v2 = StreetModelForTest.intersectionVertex("v2", c2.x, c2.y);
+
+ var link = StreetVehicleRentalLink.createStreetVehicleRentalLink(rentalVertex, v1);
+
+ GeometryFactory factory = new GeometryFactory();
+ LineString geometry = factory.createLineString(new Coordinate[] { c1, c2 });
+
+ double length = 650.0;
+
+ StreetEdge testStreet = new StreetEdgeBuilder<>()
+ .withFromVertex(v1)
+ .withToVertex(v2)
+ .withGeometry(geometry)
+ .withName("Test Lane")
+ .withMeterLength(length)
+ .withPermission(StreetTraversalPermission.ALL)
+ .buildAndConnect();
+
+ var request = StreetSearchRequest.of().withMode(StreetMode.SCOOTER_RENTAL);
+
+ request.withPreferences(pref -> pref.withScooter(scooter -> scooter.withSpeed(5)));
+
+ State slowResult = traverseStreetFromRental(
+ testStreet,
+ vehicleRentalEdge,
+ link,
+ rentalVertex,
+ request.build()
+ );
+ request.withPreferences(pref -> pref.withScooter(scooter -> scooter.withSpeed(10)));
+
+ State fastResult = traverseStreetFromRental(
+ testStreet,
+ vehicleRentalEdge,
+ link,
+ rentalVertex,
+ request.build()
+ );
+
+ // Cost and time should be less when scooter speed is higher.
+ assertTrue(slowResult.getWeight() > fastResult.getWeight() + DELTA);
+ assertTrue(slowResult.getElapsedTimeSeconds() > fastResult.getElapsedTimeSeconds());
+
+ request.withPreferences(pref -> pref.withScooter(scooter -> scooter.withReluctance(1)));
+ State lowReluctanceResult = traverseStreetFromRental(
+ testStreet,
+ vehicleRentalEdge,
+ link,
+ rentalVertex,
+ request.build()
+ );
+
+ request.withPreferences(pref -> pref.withScooter(scooter -> scooter.withReluctance(5)));
+
+ State highReluctanceResult = traverseStreetFromRental(
+ testStreet,
+ vehicleRentalEdge,
+ link,
+ rentalVertex,
+ request.build()
+ );
+ // Cost should be more when reluctance is higher but the time should be the same.
+ assertTrue(highReluctanceResult.getWeight() > lowReluctanceResult.getWeight() + DELTA);
+
+ assertEquals(
+ highReluctanceResult.getElapsedTimeSeconds(),
+ lowReluctanceResult.getElapsedTimeSeconds()
+ );
+ }
+
+ @Test
+ public void testWalkingBeforeScooter() {
+ StreetEdge e1 = StreetModelForTest
+ .streetEdgeBuilder(
+ StreetModelForTest.V1,
+ StreetModelForTest.V2,
+ 100.0,
+ StreetTraversalPermission.ALL
+ )
+ .withCarSpeed(10.0f)
+ .buildAndConnect();
+
+ var request = StreetSearchRequest
+ .of()
+ .withPreferences(pref -> pref.withWalk(walk -> walk.withReluctance(1)))
+ .withMode(StreetMode.SCOOTER_RENTAL);
+
+ State s0 = new State(StreetModelForTest.V1, request.build());
+ State result = e1.traverse(s0)[0];
+
+ request.withPreferences(pref ->
+ pref.withScooter(scooter -> scooter.withReluctance(5).withSpeed(8.5))
+ );
+
+ s0 = new State(StreetModelForTest.V1, request.build());
+ var scooterReluctanceResult = e1.traverse(s0)[0];
+
+ // Scooter preferences shouldn't affect walking when SCOOTER_RENTAL is used as mode
+ assertEquals(TraverseMode.WALK, result.currentMode());
+ assertEquals(result.getWeight(), scooterReluctanceResult.getWeight(), DELTA);
+ assertEquals(result.getElapsedTimeSeconds(), scooterReluctanceResult.getElapsedTimeSeconds());
+ }
+
+ @Test
+ public void testScooterOptimizeTriangle() {
+ // This test does not depend on the setup method - and can probably be simplified
+ Coordinate c1 = new Coordinate(-122.575033, 45.456773);
+ Coordinate c2 = new Coordinate(-122.576668, 45.451426);
+
+ var formFactor = RentalFormFactor.SCOOTER;
+
+ var rentalVertex = StreetModelForTest.rentalVertex(formFactor);
+ var vehicleRentalEdge = VehicleRentalEdge.createVehicleRentalEdge(rentalVertex, formFactor);
+
+ StreetVertex v1 = StreetModelForTest.intersectionVertex("v1", c1.x, c1.y);
+ StreetVertex v2 = StreetModelForTest.intersectionVertex("v2", c2.x, c2.y);
+
+ var link = StreetVehicleRentalLink.createStreetVehicleRentalLink(rentalVertex, v1);
+
+ GeometryFactory factory = new GeometryFactory();
+ LineString geometry = factory.createLineString(new Coordinate[] { c1, c2 });
+
+ double length = 650.0;
+
+ StreetEdge testStreet = new StreetEdgeBuilder<>()
+ .withFromVertex(v1)
+ .withToVertex(v2)
+ .withGeometry(geometry)
+ .withName("Test Lane")
+ .withMeterLength(length)
+ .withPermission(StreetTraversalPermission.ALL)
+ // a safe street
+ .withBicycleSafetyFactor(0.74f)
+ .buildAndConnect();
+
+ Coordinate[] profile = new Coordinate[] {
+ new Coordinate(0, 0), // slope = 0.1
+ new Coordinate(length / 2, length / 20.0),
+ new Coordinate(length, 0), // slope = -0.1
+ };
+ PackedCoordinateSequence elev = new PackedCoordinateSequence.Double(profile);
+ StreetElevationExtensionBuilder
+ .of(testStreet)
+ .withElevationProfile(elev)
+ .withComputed(false)
+ .build()
+ .ifPresent(testStreet::setElevationExtension);
+
+ SlopeCosts costs = ElevationUtils.getSlopeCosts(elev, true);
+ double trueLength = costs.lengthMultiplier * length;
+ double slopeWorkLength = testStreet.getEffectiveBikeDistanceForWorkCost();
+ double slopeSpeedLength = testStreet.getEffectiveBikeDistance();
+
+ var request = StreetSearchRequest.of().withMode(StreetMode.SCOOTER_RENTAL);
+
+ request.withPreferences(pref ->
+ pref
+ .withScooter(scooter ->
+ scooter
+ .withSpeed(SPEED)
+ .withOptimizeType(VehicleRoutingOptimizeType.TRIANGLE)
+ .withOptimizeTriangle(it -> it.withTime(1))
+ .withReluctance(1)
+ )
+ .withWalk(walk -> walk.withReluctance(1))
+ .withCar(car -> car.withReluctance(1))
+ );
+
+ var rentedState = vehicleRentalEdge.traverse(new State(rentalVertex, request.build()));
+ var startState = link.traverse(rentedState[0])[0];
+
+ State result = testStreet.traverse(startState)[0];
+ double expectedTimeWeight = slopeSpeedLength / SPEED;
+ assertEquals(TraverseMode.SCOOTER, result.currentMode());
+ assertEquals(expectedTimeWeight, result.getWeight() - startState.getWeight(), DELTA);
+
+ request.withPreferences(p ->
+ p.withScooter(scooter -> scooter.withOptimizeTriangle(it -> it.withSlope(1)))
+ );
+ rentedState = vehicleRentalEdge.traverse(new State(rentalVertex, request.build()));
+ startState = link.traverse(rentedState[0])[0];
+
+ result = testStreet.traverse(startState)[0];
+ double slopeWeight = result.getWeight();
+ double expectedSlopeWeight = slopeWorkLength / SPEED;
+ assertEquals(expectedSlopeWeight, slopeWeight - startState.getWeight(), DELTA);
+ assertTrue(length * 1.5 / SPEED < slopeWeight);
+ assertTrue(length * 1.5 * 10 / SPEED > slopeWeight);
+
+ request.withPreferences(p ->
+ p.withScooter(scooter -> scooter.withOptimizeTriangle(it -> it.withSafety(1)))
+ );
+ rentedState = vehicleRentalEdge.traverse(new State(rentalVertex, request.build()));
+ startState = link.traverse(rentedState[0])[0];
+
+ result = testStreet.traverse(startState)[0];
+ double slopeSafety = costs.slopeSafetyCost;
+ double safetyWeight = result.getWeight();
+ double expectedSafetyWeight = (trueLength * 0.74 + slopeSafety) / SPEED;
+ assertEquals(expectedSafetyWeight, safetyWeight - startState.getWeight(), DELTA);
+ }
+
+ private State traverseStreetFromRental(
+ StreetEdge streetEdge,
+ VehicleRentalEdge rentalEdge,
+ StreetVehicleRentalLink link,
+ VehicleRentalPlaceVertex rentalVertex,
+ StreetSearchRequest request
+ ) {
+ var rentedState = rentalEdge.traverse(new State(rentalVertex, request));
+ var startState = link.traverse(rentedState[0])[0];
+ return streetEdge.traverse(startState)[0];
+ }
+}
diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java
index 42d841b9fa3..a2bf7475bdc 100644
--- a/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java
+++ b/src/test/java/org/opentripplanner/street/model/edge/StreetEdgeTest.java
@@ -18,7 +18,7 @@
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.opentripplanner.routing.api.request.StreetMode;
-import org.opentripplanner.routing.core.BicycleOptimizeType;
+import org.opentripplanner.routing.core.VehicleRoutingOptimizeType;
import org.opentripplanner.routing.util.ElevationUtils;
import org.opentripplanner.routing.util.SlopeCosts;
import org.opentripplanner.street.model.StreetTraversalPermission;
@@ -221,7 +221,7 @@ public void testBikeSwitch() {
StreetSearchRequestBuilder noPenalty = StreetSearchRequest.copyOf(proto);
noPenalty.withPreferences(p ->
- p.withBike(it -> it.withWalking(w -> w.withHopTime(0).withHopCost(0)))
+ p.withBike(it -> it.withWalking(w -> w.withMountDismountTime(0).withMountDismountCost(0)))
);
State s0 = new State(v0, noPenalty.withMode(StreetMode.BIKE).build());
@@ -231,7 +231,7 @@ public void testBikeSwitch() {
StreetSearchRequestBuilder withPenalty = StreetSearchRequest.copyOf(proto);
withPenalty.withPreferences(p ->
- p.withBike(it -> it.withWalking(w -> w.withHopTime(42).withHopCost(23)))
+ p.withBike(it -> it.withWalking(w -> w.withMountDismountTime(42).withMountDismountCost(23)))
);
State s4 = new State(v0, withPenalty.withMode(StreetMode.BIKE).build());
@@ -357,7 +357,7 @@ public void testBikeOptimizeTriangle() {
.withBike(bike ->
bike
.withSpeed(SPEED)
- .withOptimizeType(BicycleOptimizeType.TRIANGLE)
+ .withOptimizeType(VehicleRoutingOptimizeType.TRIANGLE)
.withOptimizeTriangle(it -> it.withTime(1))
.withReluctance(1)
)
diff --git a/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java b/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
index 84f5efcc1c5..a3fbfcb018e 100644
--- a/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
+++ b/src/test/java/org/opentripplanner/street/model/edge/VehicleRentalEdgeTest.java
@@ -18,6 +18,7 @@
import org.opentripplanner.service.vehiclerental.street.VehicleRentalEdge;
import org.opentripplanner.service.vehiclerental.street.VehicleRentalPlaceVertex;
import org.opentripplanner.street.model.RentalFormFactor;
+import org.opentripplanner.street.model._data.StreetModelForTest;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.search.state.VehicleRentalState;
@@ -30,7 +31,7 @@ class VehicleRentalEdgeTest {
VehicleRentalPlaceVertex vertex;
@Test
- void testRentingWithAvailableVehicles() {
+ void testRentingWithAvailableBikes() {
initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3);
var s1 = rent();
@@ -49,7 +50,7 @@ void testRentingWithNoAvailableVehicles() {
@Test
void testRentingWithNoAvailableVehiclesAndNoRealtimeUsage() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 0, 3, false, true, false);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 0, 3, false, true, false, false);
var s1 = rent();
@@ -76,7 +77,7 @@ void testReturningWithNoAvailableSpaces() {
@Test
void testReturningWithNoAvailableSpacesAndOverloading() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, true, true, true);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, true, true, true, false);
var s1 = rentAndDropOff();
@@ -85,7 +86,7 @@ void testReturningWithNoAvailableSpacesAndOverloading() {
@Test
void testReturningWithNoAvailableSpacesAndNoRealtimeUsage() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, false, true, false);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, false, true, false, false);
var s1 = rentAndDropOff();
@@ -94,7 +95,7 @@ void testReturningWithNoAvailableSpacesAndNoRealtimeUsage() {
@Test
void testRentingFromClosedStation() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, true, false, true);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 0, true, false, true, false);
var s1 = rent();
@@ -103,13 +104,13 @@ void testRentingFromClosedStation() {
@Test
void testReturningToClosedStation() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, true, true, true);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, true, true, true, false);
var s1 = rent();
assertFalse(State.isEmpty(s1));
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, true, false, true);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, true, false, true, false);
var s2 = dropOff(s1[0]);
@@ -118,15 +119,78 @@ void testReturningToClosedStation() {
@Test
void testReturningAndReturningToClosedStationWithNoRealtimeUsage() {
- initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, false, true, false);
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, false, true, false, false);
var s1 = rentAndDropOff();
assertFalse(State.isEmpty(s1));
}
+ @Test
+ void testRentingWithFreeFloatingBicycle() {
+ initFreeFloatingEdgeAndRequest(StreetMode.BIKE_RENTAL, RentalFormFactor.BICYCLE, false);
+
+ var s1 = rent();
+
+ assertFalse(State.isEmpty(s1));
+ }
+
+ @Test
+ void testRentingWithFreeFloatingScooter() {
+ initFreeFloatingEdgeAndRequest(StreetMode.SCOOTER_RENTAL, RentalFormFactor.SCOOTER, false);
+
+ var s1 = rent();
+
+ assertFalse(State.isEmpty(s1));
+ }
+
+ @Test
+ void testRentingWithFreeFloatingCar() {
+ initFreeFloatingEdgeAndRequest(StreetMode.CAR_RENTAL, RentalFormFactor.CAR, false);
+
+ var s1 = rent();
+
+ assertFalse(State.isEmpty(s1));
+ }
+
+ @Test
+ void testBannedBicycleNetworkStation() {
+ initEdgeAndRequest(StreetMode.BIKE_RENTAL, 3, 3, false, true, true, true);
+
+ var s1 = rent();
+
+ assertTrue(State.isEmpty(s1));
+ }
+
+ @Test
+ void testBannedBicycleNetworkFreeFloating() {
+ initFreeFloatingEdgeAndRequest(StreetMode.BIKE_RENTAL, RentalFormFactor.BICYCLE, true);
+
+ var s1 = rent();
+
+ assertTrue(State.isEmpty(s1));
+ }
+
+ @Test
+ void testBannedScooterNetworkFreeFloating() {
+ initFreeFloatingEdgeAndRequest(StreetMode.SCOOTER_RENTAL, RentalFormFactor.SCOOTER, true);
+
+ var s1 = rent();
+
+ assertTrue(State.isEmpty(s1));
+ }
+
+ @Test
+ void testBannedCarNetworkFreeFloating() {
+ initFreeFloatingEdgeAndRequest(StreetMode.CAR_RENTAL, RentalFormFactor.CAR, true);
+
+ var s1 = rent();
+
+ assertTrue(State.isEmpty(s1));
+ }
+
private void initEdgeAndRequest(StreetMode mode, int vehicles, int spaces) {
- initEdgeAndRequest(mode, vehicles, spaces, false, true, true);
+ initEdgeAndRequest(mode, vehicles, spaces, false, true, true, false);
}
@Nested
@@ -204,7 +268,8 @@ private void initEdgeAndRequest(
int spaces,
boolean overloadingAllowed,
boolean stationOn,
- boolean useRealtime
+ boolean useRealtime,
+ boolean banNetwork
) {
var station = TestVehicleRentalStationBuilder
.of()
@@ -218,17 +283,49 @@ private void initEdgeAndRequest(
vehicleRentalEdge = VehicleRentalEdge.createVehicleRentalEdge(vertex, RentalFormFactor.BICYCLE);
+ Set bannedNetworks = banNetwork ? Set.of(station.getNetwork()) : Set.of();
+
this.request =
StreetSearchRequest
.of()
.withMode(mode)
.withPreferences(preferences ->
preferences
- .withCar(car ->
- car.withRental(rental -> rental.withUseAvailabilityInformation(useRealtime))
- )
.withBike(bike ->
- bike.withRental(rental -> rental.withUseAvailabilityInformation(useRealtime))
+ bike.withRental(rental ->
+ rental
+ .withUseAvailabilityInformation(useRealtime)
+ .withBannedNetworks(bannedNetworks)
+ )
+ )
+ .build()
+ )
+ .build();
+ }
+
+ private void initFreeFloatingEdgeAndRequest(
+ StreetMode mode,
+ RentalFormFactor formFactor,
+ boolean banNetwork
+ ) {
+ this.vertex = StreetModelForTest.rentalVertex(formFactor);
+
+ vehicleRentalEdge = VehicleRentalEdge.createVehicleRentalEdge(vertex, formFactor);
+
+ Set bannedNetworks = banNetwork
+ ? Set.of(this.vertex.getStation().getNetwork())
+ : Set.of();
+
+ this.request =
+ StreetSearchRequest
+ .of()
+ .withMode(mode)
+ .withPreferences(preferences ->
+ preferences
+ .withCar(car -> car.withRental(rental -> rental.withBannedNetworks(bannedNetworks)))
+ .withBike(bike -> bike.withRental(rental -> rental.withBannedNetworks(bannedNetworks)))
+ .withScooter(scooter ->
+ scooter.withRental(rental -> rental.withBannedNetworks(bannedNetworks))
)
.build()
)
diff --git a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json
index e62c3968924..7e611171409 100644
--- a/src/test/resources/org/opentripplanner/apis/vectortiles/style.json
+++ b/src/test/resources/org/opentripplanner/apis/vectortiles/style.json
@@ -154,6 +154,19 @@
"fill-outline-color" : "#140d0e"
}
},
+ {
+ "id" : "group-stop",
+ "type" : "fill",
+ "source" : "vectorSource",
+ "source-layer" : "stops",
+ "minzoom" : 6,
+ "maxzoom" : 23,
+ "paint" : {
+ "fill-color" : "#22DD9E",
+ "fill-opacity" : 0.5,
+ "fill-outline-color" : "#140d0e"
+ }
+ },
{
"id" : "regular-stop",
"type" : "circle",
diff --git a/src/test/resources/standalone/config/router-config.json b/src/test/resources/standalone/config/router-config.json
index 9293cfad8f4..2b2449a7415 100644
--- a/src/test/resources/standalone/config/router-config.json
+++ b/src/test/resources/standalone/config/router-config.json
@@ -57,6 +57,20 @@
"cost": 600
}
},
+ "scooter": {
+ "speed": 5,
+ "reluctance": 5.0,
+ "rental": {
+ "pickupCost": 120,
+ "dropOffTime": "30s",
+ "dropOffCost": 30
+ },
+ "triangle": {
+ "safety": 0.4,
+ "flatness": 0.3,
+ "time": 0.3
+ }
+ },
"walk": {
"speed": 1.3,
"reluctance": 4.0,