Package org.opentripplanner.routing.impl

Source Code of org.opentripplanner.routing.impl.StreetVertexIndexServiceImpl

/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.opentripplanner.routing.impl;


import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;

import org.opentripplanner.common.geometry.DistanceLibrary;
import org.opentripplanner.common.geometry.HashGridSpatialIndex;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.TraversalRequirements;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.FreeEdge;
import org.opentripplanner.routing.edgetype.PatternEdge;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.routing.services.StreetVertexIndexService;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Iterables;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.strtree.STRtree;

/**
* 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 StreetLocation 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 {

    // Members are protected so that custom subclasses can access them.

    protected Graph graph;

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

    public DistanceLibrary distanceLibrary = SphericalDistanceLibrary.getInstance();

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

    /* all distance constants here are plate-carée Euclidean, 0.001 ~= 100m at equator */

    // Edges will only be found if they are closer than this distance
    public static final double MAX_DISTANCE_FROM_STREET = 0.01000;

    // Maximum difference in distance for two geometries to be considered coincident
    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;

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

    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();
        }
    }

    @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)
                    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<TransitStop> getNearbyTransitStops(Coordinate coordinate, double radius) {
        Envelope env = new Envelope(coordinate);
        env.expandBy(SphericalDistanceLibrary.metersToLonDegrees(radius, coordinate.y),
                SphericalDistanceLibrary.metersToDegrees(radius));
        List<TransitStop> nearby = getTransitStopForEnvelope(env);
        List<TransitStop> results = new ArrayList<TransitStop>();
        for (TransitStop v : nearby) {
            if (distanceLibrary.distance(v.getCoordinate(), coordinate) <= radius) {
                results.add(v);
            }
        }
        return results;
    }

    /**
     * Convenience helper for when extraEdges is empty/null.
     */
    private Vertex getClosestVertex(final GenericLocation location, RoutingRequest options) {
        return getClosestVertex(location, options, null);
    }

    /**
     * Returns the closest vertex for this GenericLocation. If necessary, this vertex will be created by splitting nearby edges (non-permanently).
     *
     * This method is the heart of the logic that searches for the start and endpoints of RideRequests. As such, it is protected so that subclasses
     * can override the search behavior.
     */
    protected Vertex getClosestVertex(final GenericLocation location, RoutingRequest options,
            List<Edge> extraEdges) {
        LOG.debug("Looking for/making a vertex near {}", location);

        // first, check for intersections very close by
        Coordinate coord = location.getCoordinate();
        StreetVertex intersection = getIntersectionAt(coord);
        String calculatedName = location.name;
        if (intersection != null) {
            // We have an intersection vertex. Check that this vertex has edges we can traverse.
            boolean canEscape = false;
            if (options == null) {
                canEscape = true; // Some tests do not supply options.
            } else {
                TraversalRequirements reqs = new TraversalRequirements(options);
                for (StreetEdge e : Iterables.filter ( options.arriveBy ?
                        intersection.getIncoming() : intersection.getOutgoing(),
                        StreetEdge.class)) {
                    if (reqs.canBeTraversed(e)) {
                        canEscape = true;
                        break;
                    }
                }
            }      
            if (canEscape) {
                // Coordinate is at an intersection or street endpoint, and has traversible edges.
                if (!location.hasName()) {
                    LOG.debug("found intersection {}. not splitting.", intersection);
                    // generate names for corners when no name was given
                    Set<String> uniqueNameSet = new HashSet<String>();
                    for (Edge e : intersection.getOutgoing()) {
                        if (e instanceof StreetEdge) {
                            uniqueNameSet.add(e.getName());
                        }
                    }
                    List<String> uniqueNames = new ArrayList<String>(uniqueNameSet);
                    Locale locale;
                    if (options == null) {
                        locale = new Locale("en");
                    } else {
                        locale = options.locale;
                    }
                    ResourceBundle resources = ResourceBundle.getBundle("internals", locale);
                    String fmt = resources.getString("corner");
                    if (uniqueNames.size() > 1) {
                        calculatedName = String.format(fmt, uniqueNames.get(0), uniqueNames.get(1));
                    } else if (uniqueNames.size() == 1) {
                        calculatedName = uniqueNames.get(0);
                    } else {
                        calculatedName = resources.getString("unnamedStreet");
                    }
                }
                StreetLocation closest = new StreetLocation(graph, "corner " + Math.random(), coord,
                        calculatedName);
                FreeEdge e = new FreeEdge(closest, intersection);
                closest.getExtra().add(e);
                e = new FreeEdge(intersection, closest);
                closest.getExtra().add(e);
                return closest;
            }
        }

        // if no intersection vertices were found, then find the closest transit stop
        // (we can return stops here because this method is not used when street-transit linking)
        double closestStopDistance = Double.POSITIVE_INFINITY;
        Vertex closestStop = null;
        // elsewhere options=null means no restrictions, find anything.
        // here we skip examining stops, as they are really only relevant when transit is being used
        if (options != null && options.modes.isTransit()) {
            for (TransitStop v : getNearbyTransitStops(coord, 1000)) {
                if (!v.isStreetLinkable()) continue;

                double d = distanceLibrary.distance(v.getCoordinate(), coord);
                if (d < closestStopDistance) {
                    closestStopDistance = d;
                    closestStop = v;
                }
            }
        }
        LOG.debug(" best stop: {} distance: {}", closestStop, closestStopDistance);

        // then find closest walkable street
        StreetLocation closestStreet = null;
        CandidateEdgeBundle bundle = getClosestEdges(location, options, extraEdges, null, false);
        CandidateEdge candidate = bundle.best;
        double closestStreetDistance = Double.POSITIVE_INFINITY;
        if (candidate != null) {
            StreetEdge bestStreet = candidate.edge;
            Coordinate nearestPoint = candidate.nearestPointOnEdge;
            closestStreetDistance = distanceLibrary.distance(coord, nearestPoint);
            LOG.debug("best street: {} dist: {}", bestStreet.toString(), closestStreetDistance);
            if (calculatedName == null || "".equals(calculatedName)) {
                calculatedName = bestStreet.getName();
            }
            String closestName = String.format("%s_%s", bestStreet.getName(), location.toString());
            closestStreet = StreetLocation.createStreetLocation(graph, closestName, calculatedName,
                    bundle.toEdgeList(), nearestPoint, coord);
        }

        // decide whether to return street, or street + stop
        if (closestStreet == null) {
            // no street found, return closest stop or null
            LOG.debug("returning only transit stop (no street found)");
            return closestStop; // which will be null if none was found
        } else {
            // street found
            if (closestStop != null) {
                // both street and stop found
                double relativeStopDistance = closestStopDistance / closestStreetDistance;
                if (relativeStopDistance < 1.5) {
                    LOG.debug("linking transit stop to street (distances are comparable)");
                    closestStreet.addExtraEdgeTo(closestStop);
                }
            }
            LOG.debug("returning split street");
            return closestStreet;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Vertex> getVerticesForEnvelope(Envelope envelope) {
        List<Vertex> vertices = verticesTree.query(envelope);
        // Here we assume vertices list modifiable
        for (Iterator<Vertex> 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<Edge> getEdgesForEnvelope(Envelope envelope) {
        List<Edge> edges = edgeTree.query(envelope);
        for (Iterator<Edge> 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<TransitStop> getTransitStopForEnvelope(Envelope envelope) {
        List<TransitStop> transitStops = transitStopTree.query(envelope);
        for (Iterator<TransitStop> its = transitStops.iterator(); its.hasNext();) {
            TransitStop ts = its.next();
            if (!envelope.intersects(new Coordinate(ts.getLon(), ts.getLat())))
                its.remove();
        }
        return transitStops;
    }

    @Override
    public CandidateEdgeBundle getClosestEdges(GenericLocation location,
            TraversalRequirements reqs, List<Edge> extraEdges, Collection<Edge> preferredEdges,
            boolean possibleTransitLinksOnly) {
        Coordinate coordinate = location.getCoordinate();
        Envelope envelope = new Envelope(coordinate);

        // Collect the extra StreetEdges to consider.
        Iterable<StreetEdge> extraStreets = Iterables.filter(graph.getTemporaryEdges(),
                StreetEdge.class);
        if (extraEdges != null) {
            extraStreets = Iterables.concat(Iterables.filter(extraEdges, StreetEdge.class),
                    extraStreets);
        }

        double envelopeGrowthAmount = 0.001; // ~= 100 meters
        double radius = 0;
        CandidateEdgeBundle candidateEdges = new CandidateEdgeBundle();
        while (candidateEdges.size() == 0) {
            // expand envelope -- assumes many close searches and occasional far ones
            envelope.expandBy(envelopeGrowthAmount);
            radius += envelopeGrowthAmount;
            if (radius > MAX_DISTANCE_FROM_STREET) {
                return candidateEdges; // empty list
            }

            Iterable<Edge> nearbyEdges = getEdgesForEnvelope(envelope);
            if (nearbyEdges != null) {
                nearbyEdges = Iterables.concat(nearbyEdges, extraStreets);
            }

            // oh. This is part of the problem: we're not linking to one-way
            // streets, even though that is a perfectly reasonable thing to do.
            // we need to handle that using bundles.
            for (Edge e : nearbyEdges) {
                // Ignore invalid edges.
                if (e == null || e.getFromVertex() == null || !(e instanceof StreetEdge)) {
                    continue;
                }
                StreetEdge se = (StreetEdge)e;

                // Ignore those edges we can't traverse. canBeTraversed checks internally if
                // walking a bike is possible on this StreetEdge.
                if (!reqs.canBeTraversed(se)) {
                    continue;
                }

                // Compute preference value
                double preferrence = 1;
                if (preferredEdges != null && preferredEdges.contains(e)) {
                    preferrence = 3.0;
                }

                TraverseModeSet modes = reqs.modes;
                CandidateEdge ce = new CandidateEdge(se, location, preferrence, modes);

                // Even if an edge is outside the query envelope, bounding boxes can
                // still intersect. In this case, distance to the edge is greater
                // than the query envelope size.
                if (ce.distance < radius) {
                    candidateEdges.add(ce);
                }
            }
        }

        Collection<CandidateEdgeBundle> bundles = candidateEdges.binByDistanceAndAngle();
        // initially set best bundle to the closest bundle
        CandidateEdgeBundle best = null;
        for (CandidateEdgeBundle bundle : bundles) {
            if (best == null || bundle.best.score < best.best.score) {
                if (possibleTransitLinksOnly) {
                    // assuming all platforms are tagged when they are not car streets... #1077
                    if (!(bundle.allowsCars() || bundle.isPlatform()))
                        continue;
                }
                best = bundle;
            }
        }

        return best;
    }

    @Override
    public CandidateEdgeBundle getClosestEdges(GenericLocation location, TraversalRequirements reqs) {
        return getClosestEdges(location, reqs, null, null, false);
    }

    /**
     * Find edges closest to the given location.
     *
     * TODO(flamholz): consider deleting.
     *
     * @param location Point to get edges near
     * @param request RoutingRequest that must be able to traverse the edge (all edges if null)
     * @param extraEdges Any edges not in the graph that might be included (allows trips within one block)
     * @param preferredEdges Any edges to prefer in the search
     * @param possibleTransitLinksOnly only return edges traversable by cars or are platforms
     * @return
     */
    protected CandidateEdgeBundle getClosestEdges(GenericLocation location, RoutingRequest request,
            List<Edge> extraEdges, Collection<Edge> preferredEdges, boolean possibleTransitLinksOnly) {
        // NOTE(flamholz): if request is null, will initialize TraversalRequirements
        // that accept all modes of travel.
        TraversalRequirements reqs = new TraversalRequirements(request);

        return getClosestEdges(location, reqs, extraEdges, preferredEdges, possibleTransitLinksOnly);
    }

    /**
     * @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<Vertex> nearby = getVerticesForEnvelope(envelope);
        StreetVertex nearest = null;
        double bestDistanceMeter = Double.POSITIVE_INFINITY;
        for (Vertex v : nearby) {
            if (v instanceof StreetVertex) {
                double distanceMeter = distanceLibrary.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 location, RoutingRequest options) {
        return getVertexForLocation(location, options, null);
    }

    /**
     * @param other: non-null when another vertex has already been found. When the from vertex has
     * already been made/found, that vertex is passed in when finding/creating the to vertex.
     * TODO: This appears to be for reusing the extra edges list -- is this still needed?
     */
    @Override
    public Vertex getVertexForLocation(GenericLocation loc, RoutingRequest options, Vertex other) {
        Coordinate c = loc.getCoordinate();
        if (c != null) {
            if (other instanceof StreetLocation) {
                return getClosestVertex(loc, options, ((StreetLocation) other).getExtra());
            } else {
                return getClosestVertex(loc, options);
            }
        }

        // 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();
    }
}
TOP

Related Classes of org.opentripplanner.routing.impl.StreetVertexIndexServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.