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

org.opentripplanner.graph_builder.module.nearbystops.StreetNearbyStopFinder Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.graph_builder.module.nearbystops;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.opentripplanner.astar.model.ShortestPathTree;
import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy;
import org.opentripplanner.astar.strategy.MaxCountTerminationStrategy;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.request.StreetRequest;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.vertex.StreetVertex;
import org.opentripplanner.street.model.vertex.TemporaryStreetLocation;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.StreetSearchBuilder;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.search.strategy.DominanceFunctions;
import org.opentripplanner.transit.model.site.AreaStop;

public class StreetNearbyStopFinder implements NearbyStopFinder {

  private final Duration durationLimit;
  private final int maxStopCount;
  private final DataOverlayContext dataOverlayContext;
  private final Set ignoreVertices;

  /**
   * Construct a NearbyStopFinder for the given graph and search radius.
   *
   * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the
   *                     maxStopCount we will always return all the directly connected stops.
   */
  public StreetNearbyStopFinder(
    Duration durationLimit,
    int maxStopCount,
    DataOverlayContext dataOverlayContext
  ) {
    this(durationLimit, maxStopCount, dataOverlayContext, Set.of());
  }

  /**
   * Construct a NearbyStopFinder for the given graph and search radius.
   *
   * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount
   *                     we will always return all the directly connected stops.
   * @param ignoreVertices   A set of stop vertices to ignore and not return NearbyStops for.
   */
  public StreetNearbyStopFinder(
    Duration durationLimit,
    int maxStopCount,
    DataOverlayContext dataOverlayContext,
    Set ignoreVertices
  ) {
    this.dataOverlayContext = dataOverlayContext;
    this.durationLimit = durationLimit;
    this.maxStopCount = maxStopCount;
    this.ignoreVertices = ignoreVertices;
  }

  /**
   * Return all stops within a certain radius of the given vertex, using network distance along
   * streets. If the origin vertex is a StopVertex, the result will include it; this characteristic
   * is essential for associating the correct stop with each trip pattern in the vicinity.
   */
  @Override
  public Collection findNearbyStops(
    Vertex vertex,
    RouteRequest routingRequest,
    StreetRequest streetRequest,
    boolean reverseDirection
  ) {
    return findNearbyStops(Set.of(vertex), routingRequest, streetRequest, reverseDirection);
  }

  /**
   * Return all stops within a certain radius of the given vertex, using network distance along
   * streets. If the origin vertex is a StopVertex, the result will include it.
   *
   * @param originVertices   the origin point of the street search.
   * @param reverseDirection if true the paths returned instead originate at the nearby stops and
   *                         have the originVertex as the destination.
   */
  public Collection findNearbyStops(
    Set originVertices,
    RouteRequest request,
    StreetRequest streetRequest,
    boolean reverseDirection
  ) {
    OTPRequestTimeoutException.checkForTimeout();

    List stopsFound = NearbyStop.nearbyStopsForTransitStopVerticesFiltered(
      Sets.difference(originVertices, ignoreVertices),
      reverseDirection,
      request,
      streetRequest
    );

    // Return only the origin vertices if there are no valid street modes
    if (
      streetRequest.mode() == StreetMode.NOT_SET ||
      (maxStopCount > 0 && stopsFound.size() >= maxStopCount)
    ) {
      return stopsFound;
    }
    stopsFound = new ArrayList<>(stopsFound);

    var streetSearch = StreetSearchBuilder.of()
      .setSkipEdgeStrategy(new DurationSkipEdgeStrategy<>(durationLimit))
      .setDominanceFunction(new DominanceFunctions.MinimumWeight())
      .setRequest(request)
      .setArriveBy(reverseDirection)
      .setStreetRequest(streetRequest)
      .setFrom(reverseDirection ? null : originVertices)
      .setTo(reverseDirection ? originVertices : null)
      .setDataOverlayContext(dataOverlayContext);

    if (maxStopCount > 0) {
      streetSearch.setTerminationStrategy(
        new MaxCountTerminationStrategy<>(maxStopCount, this::hasReachedStop)
      );
    }

    ShortestPathTree spt = streetSearch.getShortestPathTree();

    // Only used if OTPFeature.FlexRouting.isOn()
    Multimap locationsMap = ArrayListMultimap.create();

    if (spt != null) {
      // TODO use GenericAStar and a traverseVisitor? Add an earliestArrival switch to genericAStar?
      for (State state : spt.getAllStates()) {
        Vertex targetVertex = state.getVertex();
        if (originVertices.contains(targetVertex) || ignoreVertices.contains(targetVertex)) {
          continue;
        }
        if (targetVertex instanceof TransitStopVertex tsv && state.isFinal()) {
          stopsFound.add(NearbyStop.nearbyStopForState(state, tsv.getStop()));
        }
        if (
          OTPFeature.FlexRouting.isOn() &&
          targetVertex instanceof StreetVertex streetVertex &&
          !streetVertex.areaStops().isEmpty()
        ) {
          for (AreaStop areaStop : ((StreetVertex) targetVertex).areaStops()) {
            // This is for a simplification, so that we only return one vertex from each
            // stop location. All vertices are added to the multimap, which is filtered
            // below, so that only the closest vertex is added to stopsFound
            if (canBoardFlex(state, reverseDirection)) {
              locationsMap.put(areaStop, state);
            }
          }
        }
      }
    }

    if (OTPFeature.FlexRouting.isOn()) {
      for (var locationStates : locationsMap.asMap().entrySet()) {
        AreaStop areaStop = locationStates.getKey();
        Collection states = locationStates.getValue();
        // Select the vertex from all vertices that are reachable per AreaStop by taking
        // the minimum walking distance
        State min = Collections.min(states, Comparator.comparing(State::getWeight));

        // If the best state for this AreaStop is a SplitterVertex, we want to get the
        // TemporaryStreetLocation instead. This allows us to reach SplitterVertices in both
        // directions when routing later.
        if (min.getBackState().getVertex() instanceof TemporaryStreetLocation) {
          min = min.getBackState();
        }

        stopsFound.add(NearbyStop.nearbyStopForState(min, areaStop));
      }
    }

    return stopsFound;
  }

  private boolean canBoardFlex(State state, boolean reverse) {
    Collection edges = reverse
      ? state.getVertex().getIncoming()
      : state.getVertex().getOutgoing();

    return edges
      .stream()
      .anyMatch(e -> e instanceof StreetEdge se && se.getPermission().allows(TraverseMode.CAR));
  }

  /**
   * Checks if the {@code state} is at a transit vertex and if it's final, which means that the state
   * can actually board a vehicle.
   * 

* This is important because there can be cases where states that cannot actually board the vehicle * can dominate those that can thereby leading to zero found stops when this predicate is used with * the {@link MaxCountTerminationStrategy}. *

* An example of this would be an egress/reverse search with a very high walk reluctance where the * states that speculatively rent a vehicle move the walk states down the A* priority queue until * the required number of stops are reached to abort the search, leading to zero egress results. */ private boolean hasReachedStop(State state) { var vertex = state.getVertex(); return ( vertex instanceof TransitStopVertex && state.isFinal() && !ignoreVertices.contains(vertex) ); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy