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

org.opentripplanner.routing.impl.StreetVertexIndexServiceImpl 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.analyst.core.Sample;
import org.opentripplanner.analyst.request.SampleFactory;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.HashGridSpatialIndex;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.graph_builder.linking.SimpleStreetSplitter;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.edgetype.*;
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.services.StreetVertexIndexService;
import org.opentripplanner.routing.util.ElevationUtils;
import org.opentripplanner.routing.vertextype.SampleVertex;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.opentripplanner.util.I18NString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 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 StreetVertexIndexServiceImpl implements StreetVertexIndexService {

    private Graph graph;

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

    // private static final double SEARCH_RADIUS_M = 100; // meters
    // private static final double SEARCH_RADIUS_DEG = DistanceLibrary.metersToDegrees(SEARCH_RADIUS_M);

    // Maximum difference in distance for two geometries to be considered coincident, plate-carée Euclidean
    // 0.001 ~= 100m at equator
    public static final double DISTANCE_ERROR = 0.000001;

    // If a point is within MAX_CORNER_DISTANCE, it is treated as at the corner.
    private static final double MAX_CORNER_DISTANCE_METERS = 10;
    
    // Edges will only be found if they are closer than this distance
    // TODO: this default may be too large?
    public static final double MAX_DISTANCE_FROM_STREET_METERS = 1000;
    
    private static final double MAX_DISTANCE_FROM_STREET_DEGREES =
            MAX_DISTANCE_FROM_STREET_METERS * 180 / Math.PI / SphericalDistanceLibrary.RADIUS_OF_EARTH_IN_M;

    static final Logger LOG = LoggerFactory.getLogger(StreetVertexIndexServiceImpl.class);

    private SimpleStreetSplitter simpleStreetSplitter;

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

    public StreetVertexIndexServiceImpl(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);
        } else {
            simpleStreetSplitter = new SimpleStreetSplitter(this.graph,
                (HashGridSpatialIndex) edgeTree, transitStopTree, 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.getDistance() * lengthRatioIn;
        double lengthOut = street.getDistance() * (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()) {
                if (e instanceof PatternEdge || e instanceof SimpleTransfer)
                    continue;
                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 TransitStop) {
                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
     */
    @Override
    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 (TransitStop v : nearby) {
            if (SphericalDistanceLibrary.distance(v.getCoordinate(), coordinate) <= radius) {
                results.add(v);
            }
        }
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    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;
    }

    @SuppressWarnings("unchecked")
    @Override
    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;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List getTransitStopForEnvelope(Envelope envelope) {
        List transitStops = transitStopTree.query(envelope);
        for (Iterator its = transitStops.iterator(); its.hasNext();) {
            TransitStop ts = its.next();
            if (!envelope.intersects(new Coordinate(ts.getLon(), ts.getLat())))
                its.remove();
        }
        return transitStops;
    }

    /**
     * @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;
    }
    
    @Override
    public Vertex getVertexForLocation(GenericLocation loc, RoutingRequest options,
                                       boolean endVertex) {
        Coordinate c = loc.getCoordinate();
        if (c != null) {
            //return getClosestVertex(loc, options, endVertex);
            return simpleStreetSplitter.getClosestVertex(loc, options, endVertex);
        }

        // No Coordinate available.
        String place = loc.place;
        if (place == null) {
            return null;
        }

        // did not match lat/lon, interpret place as a vertex label.
        // this should probably only be used in tests,
        // though it does allow routing from stop to stop.
        return graph.getVertex(place);
    }

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

    @Override
    public Vertex getSampleVertexAt(Coordinate coordinate, boolean dest) {
        SampleFactory sfac = graph.getSampleFactory();

        Sample s = sfac.getSample(coordinate.x, coordinate.y);

        if (s == null)
            return null;

        // create temp vertex
        SampleVertex v = new SampleVertex(graph, coordinate);

        // create edges
        if (dest) {
            if (s.v0 != null)
                new SampleEdge(s.v0, v, s.d0);

            if (s.v1 != null)
                new SampleEdge(s.v1, v, s.d1);
        }
        else {
            if (s.v0 != null)
                new SampleEdge(v, s.v0, s.d0);

            if (s.v1 != null)
                new SampleEdge(v, s.v1, s.d1);
        }

        return v;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy