All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.updater.vehicle_rental.GeofencingVertexUpdater Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.updater.vehicle_rental;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.service.vehiclerental.model.GeofencingZone;
import org.opentripplanner.service.vehiclerental.street.BusinessAreaBorder;
import org.opentripplanner.service.vehiclerental.street.GeofencingZoneExtension;
import org.opentripplanner.street.model.RentalRestrictionExtension;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetEdge;

/**
 * Even though the data is kept on the vertex this updater operates mostly on edges which then
 * delegate to the vertices.
 * 

* This is because we want to drop the vehicle outside the geofencing zone rather than on the first * vertex inside of it. To make this work we need to know which are the edges that cross the * border. *

* Perhaps this logic will be replaced with edge splitting where a new vertex is insert right on * the border of the zone. */ class GeofencingVertexUpdater { private final Function> getEdgesForEnvelope; public GeofencingVertexUpdater(Function> getEdgesForEnvelope) { this.getEdgesForEnvelope = getEdgesForEnvelope; } /** * Applies the restrictions described in the geofencing zones to eges by adding * {@link RentalRestrictionExtension} to them. */ Map applyGeofencingZones( Collection geofencingZones ) { var restrictedZones = geofencingZones.stream().filter(GeofencingZone::hasRestriction).toList(); // these are the edges inside business area where exceptions like "no pass through" // or "no drop-off" are added var restrictedEdges = addExtensionToIntersectingStreetEdges( restrictedZones, GeofencingZoneExtension::new ); var updates = new HashMap<>(restrictedEdges); var generalBusinessAreas = geofencingZones .stream() .filter(GeofencingZone::isBusinessArea) .toList(); if (!generalBusinessAreas.isEmpty()) { // if the geofencing zones don't have any restrictions then they describe a general business // area which you can traverse freely but are not allowed to leave // here we just take the boundary of the geometry since we want to add a "no pass through" // restriction to any edge intersecting it var network = generalBusinessAreas.get(0).id().getFeedId(); var polygons = generalBusinessAreas .stream() .map(GeofencingZone::geometry) .toArray(Geometry[]::new); var unionOfBusinessAreas = GeometryUtils.getGeometryFactory() .createGeometryCollection(polygons) .union(); var updated = applyExtension( unionOfBusinessAreas.getBoundary(), new BusinessAreaBorder(network) ); updates.putAll(updated); } return Map.copyOf(updates); } private Map addExtensionToIntersectingStreetEdges( List zones, Function createExtension ) { var edgesUpdated = new HashMap(); for (GeofencingZone zone : zones) { var geom = zone.geometry(); var ext = createExtension.apply(zone); edgesUpdated.putAll(applyExtension(geom, ext)); } return edgesUpdated; } private Map applyExtension( Geometry geom, RentalRestrictionExtension ext ) { var edgesUpdated = new HashMap(); Set candidates; // for business areas we only care about the borders so we compute the boundary of the // (multi) polygon. this can either be a MultiLineString or a LineString if (geom instanceof LineString ring) { candidates = getEdgesAlongLineStrings(List.of(ring)); } else if (geom instanceof MultiLineString mls) { var lineStrings = GeometryUtils.getLineStrings(mls); candidates = getEdgesAlongLineStrings(lineStrings); } else { candidates = Set.copyOf(getEdgesForEnvelope.apply(geom.getEnvelopeInternal())); } for (var e : candidates) { if (e instanceof StreetEdge streetEdge && streetEdge.getGeometry().intersects(geom)) { streetEdge.addRentalRestriction(ext); edgesUpdated.put(streetEdge, ext); } } return edgesUpdated; } /** * This method optimizes finding all the candidate edges which could cross the business zone * border. *

* If you put the entire zone into an envelope you get lots and lots of edges in the middle of it * that are nowhere near the border. Since checking if they intersect with the border is an * expensive operation we apply the following optimization: *

  • we split the line string into segments for each pair of coordinates *
  • for each segment we compute the envelope *
  • we only get the edges for that envelope and check if they intersect *

    * When finding the edges near the business area border in Oslo this speeds up the computation * from ~25 seconds to ~3 seconds (on 2021 hardware). */ private Set getEdgesAlongLineStrings(Collection lineStrings) { return lineStrings .stream() .flatMap(GeometryUtils::toEnvelopes) .map(getEdgesForEnvelope) .flatMap(Collection::stream) .collect(Collectors.toSet()); } }





  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy