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

org.opentripplanner.routing.impl.StreetVertexIndex Maven / Gradle / Ivy

package org.opentripplanner.routing.impl;


import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.index.SpatialIndex;
import org.locationtech.jts.index.strtree.STRtree;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.HashGridSpatialIndex;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.graph_builder.DataImportIssueStore;
import org.opentripplanner.graph_builder.linking.SimpleStreetSplitter;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.api.response.InputField;
import org.opentripplanner.routing.api.response.RoutingError;
import org.opentripplanner.routing.api.response.RoutingErrorCode;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.TemporaryFreeEdge;
import org.opentripplanner.routing.edgetype.TemporaryPartialStreetEdge;
import org.opentripplanner.routing.error.RoutingValidationException;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.TemporaryStreetLocation;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStopVertex;
import org.opentripplanner.util.I18NString;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Indexes all edges and transit vertices of the graph spatially. Has a variety of query methods
 * used during network linking and trip planning.
 * 
 * Creates a TemporaryStreetLocation representing a location on a street that's not at an
 * intersection, based on input latitude and longitude. Instantiating this class is expensive,
 * because it creates a spatial index of all of the intersections in the graph.
 */
public class StreetVertexIndex {

    private Graph graph;

    /**
     * Contains only instances of {@link StreetEdge}
     */
    private SpatialIndex edgeTree;
    private SpatialIndex transitStopTree;
    private SpatialIndex verticesTree;

    // If a point is within MAX_CORNER_DISTANCE, it is treated as at the corner.
    private static final double MAX_CORNER_DISTANCE_METERS = 10;

    private SimpleStreetSplitter simpleStreetSplitter;

    public StreetVertexIndex(Graph graph) {
        this(graph, true);
    }

    public StreetVertexIndex(Graph graph, boolean hashGrid) {
        this.graph = graph;
        if (hashGrid) {
            edgeTree = new HashGridSpatialIndex<>();
            transitStopTree = new HashGridSpatialIndex<>();
            verticesTree = new HashGridSpatialIndex<>();
        } else {
            edgeTree = new STRtree();
            transitStopTree = new STRtree();
            verticesTree = new STRtree();
        }
        postSetup();
        if (!hashGrid) {
            ((STRtree) edgeTree).build();
            ((STRtree) transitStopTree).build();
            simpleStreetSplitter = new SimpleStreetSplitter(
                    this.graph,
                    null,
                    null,
                    false,
                    new DataImportIssueStore(
                            false)
            );
        } else {
            simpleStreetSplitter = new SimpleStreetSplitter(
                    this.graph,
                    (HashGridSpatialIndex) edgeTree,
                    transitStopTree,
                    false,
                    new DataImportIssueStore(false)
            );
        }

    }

    /**
     * Creates a TemporaryStreetLocation on the given street (set of PlainStreetEdges). How far
     * along is controlled by the location parameter, which represents a distance along the edge
     * between 0 (the from vertex) and 1 (the to vertex).
     *
     * @param graph
     *
     * @param label
     * @param name
     * @param edges A collection of nearby edges, which represent one street.
     * @param nearestPoint
     *
     * @return the new TemporaryStreetLocation
     */
    public static TemporaryStreetLocation createTemporaryStreetLocation(Graph graph, String label,
            I18NString name, Iterable edges, Coordinate nearestPoint, boolean endVertex) {
        boolean wheelchairAccessible = false;

        TemporaryStreetLocation location = new TemporaryStreetLocation(label, nearestPoint, name, endVertex);

        for (StreetEdge street : edges) {
            Vertex fromv = street.getFromVertex();
            Vertex tov = street.getToVertex();
            wheelchairAccessible |= street.isWheelchairAccessible();

            /* forward edges and vertices */
            Vertex edgeLocation;
            if (SphericalDistanceLibrary.distance(nearestPoint, fromv.getCoordinate()) < 1) {
                // no need to link to area edges caught on-end
                edgeLocation = fromv;

                if (endVertex) {
                    new TemporaryFreeEdge(edgeLocation, location);
                } else {
                    new TemporaryFreeEdge(location, edgeLocation);
                }
            } else if (SphericalDistanceLibrary.distance(nearestPoint, tov.getCoordinate()) < 1) {
                // no need to link to area edges caught on-end
                edgeLocation = tov;

                if (endVertex) {
                    new TemporaryFreeEdge(edgeLocation, location);
                } else {
                    new TemporaryFreeEdge(location, edgeLocation);
                }
            } else {
                // location is somewhere in the middle of the edge.
                edgeLocation = location;

                // creates links from street head -> location -> street tail.
                createHalfLocation(location, name,
                        nearestPoint, street, endVertex);
            }
        }
        location.setWheelchairAccessible(wheelchairAccessible);
        return location;

    }

    private static void createHalfLocation(TemporaryStreetLocation base, I18NString name,
                Coordinate nearestPoint, StreetEdge street, boolean endVertex) {
        StreetVertex tov = (StreetVertex) street.getToVertex();
        StreetVertex fromv = (StreetVertex) street.getFromVertex();
        LineString geometry = street.getGeometry();

        P2 geometries = getGeometry(street, nearestPoint);

        double totalGeomLength = geometry.getLength();
        double lengthRatioIn = geometries.first.getLength() / totalGeomLength;

        double lengthIn = street.getDistanceMeters() * lengthRatioIn;
        double lengthOut = street.getDistanceMeters() * (1 - lengthRatioIn);

        if (endVertex) {
            TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge(
                    street, fromv, base, geometries.first, name, lengthIn);

            temporaryPartialStreetEdge.setNoThruTraffic(street.isNoThruTraffic());
            temporaryPartialStreetEdge.setStreetClass(street.getStreetClass());
        } else {
            TemporaryPartialStreetEdge temporaryPartialStreetEdge = new TemporaryPartialStreetEdge(
                    street, base, tov, geometries.second, name, lengthOut);

            temporaryPartialStreetEdge.setStreetClass(street.getStreetClass());
            temporaryPartialStreetEdge.setNoThruTraffic(street.isNoThruTraffic());
        }
    }

    private static P2 getGeometry(StreetEdge e, Coordinate nearestPoint) {
        LineString geometry = e.getGeometry();
        return GeometryUtils.splitGeometryAtPoint(geometry, nearestPoint);
    }

    @SuppressWarnings("rawtypes")
    private void postSetup() {
        for (Vertex gv : graph.getVertices()) {
            Vertex v = gv;
            /*
             * We add all edges with geometry, skipping transit, filtering them out after. We do not
             * index transit edges as we do not need them and some GTFS do not have shape data, so
             * long straight lines between 2 faraway stations will wreck performance on a hash grid
             * spatial index.
             * 
             * If one need to store transit edges in the index, we could improve the hash grid
             * rasterizing splitting long segments.
             */
            for (Edge e : gv.getOutgoing()) {
                LineString geometry = e.getGeometry();
                if (geometry == null) {
                    continue;
                }
                Envelope env = geometry.getEnvelopeInternal();
                if (edgeTree instanceof HashGridSpatialIndex)
                    ((HashGridSpatialIndex)edgeTree).insert(geometry, e);
                else
                    edgeTree.insert(env, e);
            }
            if (v instanceof TransitStopVertex) {
                Envelope env = new Envelope(v.getCoordinate());
                transitStopTree.insert(env, v);
            }
            Envelope env = new Envelope(v.getCoordinate());
            verticesTree.insert(env, v);
        }
    }

    /**
     * Get all transit stops within a given distance of a coordinate
     * @return The transit stops within a certain radius of the given location.
     */
    public List getNearbyTransitStops(Coordinate coordinate, double radius) {
        Envelope env = new Envelope(coordinate);
        env.expandBy(SphericalDistanceLibrary.metersToLonDegrees(radius, coordinate.y),
                SphericalDistanceLibrary.metersToDegrees(radius));
        List nearby = getTransitStopForEnvelope(env);
        List results = new ArrayList();
        for (TransitStopVertex v : nearby) {
            if (SphericalDistanceLibrary.distance(v.getCoordinate(), coordinate) <= radius) {
                results.add(v);
            }
        }
        return results;
    }

    /**
     * Returns the vertices intersecting with the specified envelope.
     */
    @SuppressWarnings("unchecked")
    public List getVerticesForEnvelope(Envelope envelope) {
        List vertices = verticesTree.query(envelope);
        // Here we assume vertices list modifiable
        for (Iterator iv = vertices.iterator(); iv.hasNext();) {
            Vertex v = iv.next();
            if (!envelope.contains(new Coordinate(v.getLon(), v.getLat())))
                iv.remove();
        }
        return vertices;
    }

    /**
     * Return the edges whose geometry intersect with the specified envelope. Warning: edges w/o
     * geometry will not be indexed.
     */
    @SuppressWarnings("unchecked")
    public Collection getEdgesForEnvelope(Envelope envelope) {
        List edges = edgeTree.query(envelope);
        for (Iterator ie = edges.iterator(); ie.hasNext();) {
            Edge e = ie.next();
            Envelope eenv = e.getGeometry().getEnvelopeInternal();
            //Envelope eenv = e.getEnvelope();
            if (!envelope.intersects(eenv))
                ie.remove();
        }
        return edges;
    }

    /**
     * @return The transit stops within an envelope.
     */
    @SuppressWarnings("unchecked")
    public List getTransitStopForEnvelope(Envelope envelope) {
        List stopVertices = transitStopTree.query(envelope);
        for (Iterator its = stopVertices.iterator(); its.hasNext();) {
            TransitStopVertex ts = its.next();
            if (!envelope.intersects(new Coordinate(ts.getLon(), ts.getLat())))
                its.remove();
        }
        return stopVertices;
    }

    /**
     * @param coordinate Location to search intersection at. Look in a MAX_CORNER_DISTANCE_METERS radius.
     * @return The nearest intersection, null if none found.
     */
    public StreetVertex getIntersectionAt(Coordinate coordinate) {
        double dLon = SphericalDistanceLibrary.metersToLonDegrees(MAX_CORNER_DISTANCE_METERS,
                coordinate.y);
        double dLat = SphericalDistanceLibrary.metersToDegrees(MAX_CORNER_DISTANCE_METERS);
        Envelope envelope = new Envelope(coordinate);
        envelope.expandBy(dLon, dLat);
        List nearby = getVerticesForEnvelope(envelope);
        StreetVertex nearest = null;
        double bestDistanceMeter = Double.POSITIVE_INFINITY;
        for (Vertex v : nearby) {
            if (v instanceof StreetVertex) {
                v.getLabel().startsWith("osm:");
                double distanceMeter = SphericalDistanceLibrary.fastDistance(coordinate, v.getCoordinate());
                if (distanceMeter < MAX_CORNER_DISTANCE_METERS) {
                    if (distanceMeter < bestDistanceMeter) {
                        bestDistanceMeter = distanceMeter;
                        nearest = (StreetVertex) v;
                    }
                }
            }
        }
        return nearest;
    }

    /**
     * Gets a set of vertices corresponding to the location provided. It first tries to match a
     * Stop/StopCollection by id, and if not successful it uses the coordinates if provided.
     * @param endVertex: whether this is a start vertex (if it's false) or end vertex (if it's true)
     */
    public Set getVerticesForLocation(
            GenericLocation location,
            RoutingRequest options,
            boolean endVertex
    ) {
        // Check if Stop/StopCollection is found by FeedScopeId
        if(location.stopId != null) {
            Set transitStopVertices = graph.getStopVerticesById(location.stopId);
            if (transitStopVertices != null) {
                return transitStopVertices;
            }
        }

        // Check if coordinate is provided and connect it to graph
        Coordinate coordinate = location.getCoordinate();
        if (coordinate != null) {
            //return getClosestVertex(loc, options, endVertex);
            return Collections.singleton(
                    simpleStreetSplitter.getClosestVertex(location, options, endVertex));
        }

        return null;
    }

    @Override
    public String toString() {
        return getClass().getName() + " -- edgeTree: " + edgeTree.toString() + " -- verticesTree: " + verticesTree.toString();
    }

    /**
     * Finds the appropriate vertex for this location.
     * @param endVertex: whether this is a start vertex (if it's false) or end vertex (if it's true)
     */
    public Vertex getVertexForLocation(
            GenericLocation location,
            RoutingRequest options,
            boolean endVertex
    ) {
        // Check if coordinate is provided and connect it to graph
        Coordinate coordinate = location.getCoordinate();
        if (coordinate != null) {
            //return getClosestVertex(loc, options, endVertex);
            return simpleStreetSplitter.getClosestVertex(location, options, endVertex);
        }

        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy