
org.opentripplanner.updater.vehicle_rental.VehicleRentalUpdater Maven / Gradle / Ivy
The newest version!
package org.opentripplanner.updater.vehicle_rental;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.opentripplanner.routing.linking.DisposableEdgeCollection;
import org.opentripplanner.routing.linking.VertexLinker;
import org.opentripplanner.service.vehiclerental.VehicleRentalRepository;
import org.opentripplanner.service.vehiclerental.model.GeofencingZone;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace;
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.RentalRestrictionExtension;
import org.opentripplanner.street.model.edge.LinkingDirection;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.vertex.VertexFactory;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.updater.GraphWriterRunnable;
import org.opentripplanner.updater.RealTimeUpdateContext;
import org.opentripplanner.updater.spi.PollingGraphUpdater;
import org.opentripplanner.updater.spi.UpdaterConstructionException;
import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDatasource;
import org.opentripplanner.utils.lang.ObjectUtils;
import org.opentripplanner.utils.logging.Throttle;
import org.opentripplanner.utils.time.DurationUtils;
import org.opentripplanner.utils.time.TimeUtils;
import org.opentripplanner.utils.tostring.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Dynamic vehicle-rental station updater which updates the Graph with vehicle rental stations from
* one VehicleRentalDataSource.
*/
public class VehicleRentalUpdater extends PollingGraphUpdater {
private static final Logger LOG = LoggerFactory.getLogger(VehicleRentalUpdater.class);
private final Throttle unlinkedPlaceThrottle;
private final VehicleRentalDatasource source;
private final String nameForLogging;
private Map latestModifiedEdges = Map.of();
private Set latestAppliedGeofencingZones = Set.of();
private final Map verticesByStation = new HashMap<>();
private final Map tempEdgesByStation = new HashMap<>();
private final VertexLinker linker;
private final VehicleRentalRepository service;
public VehicleRentalUpdater(
VehicleRentalUpdaterParameters parameters,
VehicleRentalDatasource source,
VertexLinker vertexLinker,
VehicleRentalRepository repository
) throws IllegalArgumentException {
super(parameters);
// Configure updater
LOG.info("Setting up vehicle rental updater for {}.", source);
this.source = source;
this.nameForLogging = ObjectUtils.ifNotNull(
parameters.sourceParameters().network(),
parameters.sourceParameters().url()
);
this.unlinkedPlaceThrottle = Throttle.ofOneSecond();
// Creation of network linker library will not modify the graph
this.linker = vertexLinker;
// Adding a vehicle rental station service needs a graph writer runnable
this.service = repository;
try {
// Do any setup if needed
source.setup();
} catch (UpdaterConstructionException e) {
LOG.warn("Unable to setup updater: {}", nameForLogging, e);
}
if (runOnlyOnce()) {
LOG.info(
"Creating vehicle-rental updater running once only (non-polling): {}",
nameForLogging
);
} else {
LOG.info(
"Creating vehicle-rental updater running every {}: {}",
DurationUtils.durationToStr(pollingPeriod()),
nameForLogging
);
}
}
@Override
public String toString() {
return ToStringBuilder.of(VehicleRentalUpdater.class).addObj("source", source).toString();
}
@Override
public String getConfigRef() {
return toString();
}
@Override
protected void runPolling() throws InterruptedException, ExecutionException {
LOG.debug("Updating vehicle rental stations from {}", nameForLogging);
if (!source.update()) {
LOG.debug("No updates from {}", nameForLogging);
return;
}
List stations = source.getUpdates();
var geofencingZones = source.getGeofencingZones();
// Create graph writer runnable to apply these stations to the graph
VehicleRentalGraphWriterRunnable graphWriterRunnable = new VehicleRentalGraphWriterRunnable(
stations,
geofencingZones
);
updateGraph(graphWriterRunnable);
}
private class VehicleRentalGraphWriterRunnable implements GraphWriterRunnable {
private final List stations;
private final Set geofencingZones;
public VehicleRentalGraphWriterRunnable(
List stations,
List geofencingZones
) {
this.stations = stations;
this.geofencingZones = Set.copyOf(geofencingZones);
}
@Override
public void run(RealTimeUpdateContext context) {
// Apply stations to graph
Set stationSet = new HashSet<>();
var vertexFactory = new VertexFactory(context.graph());
/* add any new stations and update vehicle counts for existing stations */
for (VehicleRentalPlace station : stations) {
service.addVehicleRentalStation(station);
stationSet.add(station.getId());
VehicleRentalPlaceVertex vehicleRentalVertex = verticesByStation.get(station.getId());
if (vehicleRentalVertex == null) {
vehicleRentalVertex = vertexFactory.vehicleRentalPlace(station);
DisposableEdgeCollection tempEdges = linker.linkVertexForRealTime(
vehicleRentalVertex,
new TraverseModeSet(TraverseMode.WALK),
LinkingDirection.BIDIRECTIONAL,
(vertex, streetVertex) ->
List.of(
StreetVehicleRentalLink.createStreetVehicleRentalLink(
(VehicleRentalPlaceVertex) vertex,
streetVertex
),
StreetVehicleRentalLink.createStreetVehicleRentalLink(
streetVertex,
(VehicleRentalPlaceVertex) vertex
)
)
);
if (vehicleRentalVertex.getOutgoing().isEmpty()) {
// Copy reference to pass into lambda
var vrv = vehicleRentalVertex;
unlinkedPlaceThrottle.throttle(() ->
// the toString includes the text "Bike rental station"
LOG.warn(
"VehicleRentalPlace is unlinked for {}: {} {}",
nameForLogging,
vrv,
unlinkedPlaceThrottle.setupInfo()
)
);
}
Set formFactors = Stream.concat(
station.getAvailablePickupFormFactors(false).stream(),
station.getAvailableDropoffFormFactors(false).stream()
).collect(Collectors.toSet());
for (RentalFormFactor formFactor : formFactors) {
tempEdges.addEdge(
VehicleRentalEdge.createVehicleRentalEdge(vehicleRentalVertex, formFactor)
);
}
verticesByStation.put(station.getId(), vehicleRentalVertex);
tempEdgesByStation.put(station.getId(), tempEdges);
} else {
vehicleRentalVertex.setStation(station);
}
}
/* remove existing stations that were not present in the update */
List toRemove = new ArrayList<>();
for (Entry entry : verticesByStation.entrySet()) {
FeedScopedId station = entry.getKey();
if (stationSet.contains(station)) continue;
toRemove.add(station);
service.removeVehicleRentalStation(station);
}
for (FeedScopedId station : toRemove) {
// post-iteration removal to avoid concurrent modification
verticesByStation.remove(station);
tempEdgesByStation.get(station).disposeEdges();
tempEdgesByStation.remove(station);
}
// this check relies on the generated equals for the record which also recursively checks that
// the JTS geometries are equal
if (!geofencingZones.isEmpty() && !geofencingZones.equals(latestAppliedGeofencingZones)) {
LOG.info("Computing geofencing zones for {}", nameForLogging);
var start = System.currentTimeMillis();
latestModifiedEdges.forEach(StreetEdge::removeRentalExtension);
var updater = new GeofencingVertexUpdater(
context.graph().getStreetIndex()::getEdgesForEnvelope
);
latestModifiedEdges = updater.applyGeofencingZones(geofencingZones);
latestAppliedGeofencingZones = geofencingZones;
var end = System.currentTimeMillis();
var millis = Duration.ofMillis(end - start);
LOG.info(
"Geofencing zones computation took {}. Added extension to {} edges. For {}",
TimeUtils.durationToStrCompact(millis),
latestModifiedEdges.size(),
nameForLogging
);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy