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

org.opentripplanner.graph_builder.module.NearbyStopFinder Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.graph_builder.module;

import com.beust.jcommander.internal.Lists;
import com.beust.jcommander.internal.Sets;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;

import org.opentripplanner.api.resource.CoordinateArrayListSequence;
import org.opentripplanner.api.resource.SimpleIsochrone;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.PackedCoordinateSequence;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.routing.algorithm.EarliestArrivalSearch;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.impl.StreetVertexIndexServiceImpl;
import org.opentripplanner.routing.services.StreetVertexIndexService;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * These library functions are used by the streetless and streetful stop linkers, and in profile transfer generation.
 * Ideally they could also be used in long distance mode and profile routing for the street segments.
 * For each stop, it finds the closest stops on all other patterns. This reduces the number of transfer edges
 * significantly compared to simple radius-constrained all-to-all stop linkage.
 */
public class NearbyStopFinder {

    private static Logger LOG = LoggerFactory.getLogger(NearbyStopFinder.class);
    private static GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();

    public  final boolean useStreets;
    private Graph graph;
    private double radiusMeters;

    /* Fields used when finding stops via the street network. */
    private EarliestArrivalSearch earliestArrivalSearch;

    /* Fields used when finding stops without a street network. */
    private StreetVertexIndexService streetIndex;

    /**
     * Construct a NearbyStopFinder for the given graph and search radius, choosing whether to search via the street
     * network or straight line distance based on the presence of OSM street data in the graph.
     */
    public NearbyStopFinder(Graph graph, double radiusMeters) {
        this (graph, radiusMeters, graph.hasStreets);
    }

    /**
     * Construct a NearbyStopFinder for the given graph and search radius.
     * @param useStreets if true, search via the street network instead of using straight-line distance.
     */
    public NearbyStopFinder(Graph graph, double radiusMeters, boolean useStreets) {
        this.graph = graph;
        this.useStreets = useStreets;
        this.radiusMeters = radiusMeters;
        if (useStreets) {
            earliestArrivalSearch = new EarliestArrivalSearch();
            // 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.
            earliestArrivalSearch.maxDuration = (int) (radiusMeters / new RoutingRequest().walkSpeed);
        } else {
            // FIXME use the vertex index already in the graph if it exists.
            streetIndex = new StreetVertexIndexServiceImpl(graph);
        }
    }

    /**
     * Find all unique nearby stops that are the closest stop on some trip pattern.
     * Note that the result will include the origin vertex if it is an instance of TransitStop.
     * This is intentional: we don't want to return the next stop down the line for trip patterns that pass through the
     * origin vertex.
     */
    public Set findNearbyStopsConsideringPatterns (Vertex vertex) {

        /* Track the closest stop on each pattern passing nearby. */
        SimpleIsochrone.MinMap closestStopForPattern =
                new SimpleIsochrone.MinMap();

        /* Iterate over nearby stops via the street network or using straight-line distance, depending on the graph. */
        for (NearbyStopFinder.StopAtDistance stopAtDistance : findNearbyStops(vertex)) {
            /* Filter out destination stops that are already reachable via pathways or transfers. */
            // FIXME why is the above comment relevant here? how does the next line achieve this?
            TransitStop ts1 = stopAtDistance.tstop;
            if (!ts1.isStreetLinkable()) continue;
            /* Consider this destination stop as a candidate for every trip pattern passing through it. */
            for (TripPattern pattern : graph.index.patternsForStop.get(ts1.getStop())) {
                closestStopForPattern.putMin(pattern, stopAtDistance);
            }
        }

        /* Make a transfer from the origin stop to each destination stop that was the closest stop on any pattern. */
        Set uniqueStops = Sets.newHashSet();
        uniqueStops.addAll(closestStopForPattern.values());
        return uniqueStops;

    }


    /**
     * Return all stops within a certain radius of the given vertex, using network distance along streets.
     * Use the correct method depending on whether the graph has street data or not.
     * If the origin vertex is a TransitStop, the result will include it; this characteristic is essential for
     * associating the correct stop with each trip pattern in the vicinity.
     */
    public List findNearbyStops (Vertex vertex) {
        return useStreets ? findNearbyStopsViaStreets(vertex) : findNearbyStopsEuclidean(vertex);
    }


    /**
     * Return all stops within a certain radius of the given vertex, using network distance along streets.
     * If the origin vertex is a TransitStop, the result will include it.
     */
    public List findNearbyStopsViaStreets (Vertex originVertex) {

        RoutingRequest routingRequest = new RoutingRequest(TraverseMode.WALK);
        routingRequest.clampInitialWait = (0L);
        routingRequest.setRoutingContext(graph, originVertex, null);
        ShortestPathTree spt = earliestArrivalSearch.getShortestPathTree(routingRequest);

        List stopsFound = Lists.newArrayList();
        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 (targetVertex == originVertex) continue;
                if (targetVertex instanceof TransitStop) {
                    stopsFound.add(stopAtDistanceForState(state));
                }
            }
        }
        /* Add the origin vertex if needed. The SPT does not include the initial state. FIXME shouldn't it? */
        if (originVertex instanceof TransitStop) {
            stopsFound.add(new StopAtDistance((TransitStop)originVertex, 0));
        }
        routingRequest.cleanup();
        return stopsFound;

    }

    /**
     * Return all stops within a certain radius of the given vertex, using straight-line distance independent of streets.
     * If the origin vertex is a TransitStop, the result will include it.
     */
    public List findNearbyStopsEuclidean (Vertex originVertex) {
        List stopsFound = Lists.newArrayList();
        Coordinate c0 = originVertex.getCoordinate();
        for (TransitStop ts1 : streetIndex.getNearbyTransitStops(c0, radiusMeters)) {
            double distance = SphericalDistanceLibrary.distance(c0, ts1.getCoordinate());
            if (distance < radiusMeters) {
                Coordinate coordinates[] = new Coordinate[] {c0, ts1.getCoordinate()};
                StopAtDistance sd = new StopAtDistance(ts1, distance);
                sd.geom = geometryFactory.createLineString(coordinates);
                stopsFound.add(sd);
            }
        }
        return stopsFound;
    }

    /**
     * Represents a stop that is comparable to other stops on the basis of its distance from some point.
     */
    public static class StopAtDistance implements Comparable {

        public TransitStop tstop;
        public double      dist;
        public LineString  geom;
        public List  edges;

        public StopAtDistance(TransitStop tstop, double dist) {
            this.tstop = tstop;
            this.dist = dist;
        }

        @Override
        public int compareTo(StopAtDistance that) {
            return (int) (this.dist) - (int) (that.dist);
        }

        public String toString() {
            return String.format("stop %s at %.1f meters", tstop, dist);
        }

    }

    /**
     * Given a State at a TransitStop, bundle the TransitStop together with information about how far away it is
     * and the geometry of the path leading up to the given State.
     *
     * TODO this should probably be merged with similar classes in Profile routing.
     */
    public static StopAtDistance stopAtDistanceForState (State state) {
        double distance = 0.0;
        GraphPath graphPath = new GraphPath(state, false);
        CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence();
        List edges = new ArrayList<>();
        for (Edge edge : graphPath.edges) {
            if (edge instanceof StreetEdge) {
                LineString geometry = edge.getGeometry();
                if (geometry != null) {
                    if (coordinates.size() == 0) {
                        coordinates.extend(geometry.getCoordinates());
                    } else {
                        coordinates.extend(geometry.getCoordinates(), 1);
                    }
                }
                distance += edge.getDistance();
            }
            edges.add(edge);
        }
        if (coordinates.size() < 2) {   // Otherwise the walk step generator breaks.
            ArrayList coordinateList = new ArrayList(2);
            coordinateList.add(graphPath.states.get(1).getVertex().getCoordinate());
            State lastState = graphPath.states.getLast().getBackState();
            coordinateList.add(lastState.getVertex().getCoordinate());
            coordinates = new CoordinateArrayListSequence(coordinateList);
        }
        StopAtDistance sd = new StopAtDistance((TransitStop) state.getVertex(), distance);
        sd.geom = geometryFactory.createLineString(new PackedCoordinateSequence.Double(coordinates.toCoordinateArray()));
        sd.edges = edges;
        return sd;
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy