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

org.opentripplanner.routing.core.RoutingContext Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package org.opentripplanner.routing.core;

import com.google.common.collect.Sets;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.model.FeedScopedId;
import org.opentripplanner.routing.algorithm.astar.strategies.EuclideanRemainingWeightHeuristic;
import org.opentripplanner.routing.algorithm.astar.strategies.RemainingWeightHeuristic;
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.GraphNotFoundException;
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.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TemporaryVertex;
import org.opentripplanner.util.NonLocalizedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * A RoutingContext holds information needed to carry out a search for a particular TraverseOptions, on a specific graph.
 * Includes things like (temporary) endpoint vertices, transfer tables, service day caches, etc.
 *
 * In addition, while the RoutingRequest should only carry parameters _in_ to the routing operation, the routing context
 * should be used to carry information back out, such as debug figures or flags that certain thresholds have been exceeded.
 */
public class RoutingContext implements Cloneable {

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

    /* FINAL FIELDS */

    public RoutingRequest opt; // not final so we can reverse-clone

    public final Graph graph;

    public final Set fromVertices;

    public final Set toVertices;

    public final Set bannedRoutes;
    
    // The back edge associated with the origin - i.e. continuing a previous search.
    // NOTE: not final so that it can be modified post-construction for testing.
    // TODO(flamholz): figure out a better way.
    public Edge originBackEdge;

    public RemainingWeightHeuristic remainingWeightHeuristic;

    /** Indicates that the search timed out or was otherwise aborted. */
    public boolean aborted;

    /** Indicates that a maximum slope constraint was specified but was removed during routing to produce a result. */
    public boolean slopeRestrictionRemoved = false;

    /* CONSTRUCTORS */

    /**
     * Constructor that automatically computes origin/target from RoutingRequest.
     */
    public RoutingContext(RoutingRequest routingRequest, Graph graph) {
        this(routingRequest, graph, (Vertex)null, null, true);
    }

    /**
     * Constructor that takes to/from vertices as input.
     */
    public RoutingContext(RoutingRequest routingRequest, Graph graph, Vertex from, Vertex to) {
        this(routingRequest, graph, from, to, false);
    }

    public RoutingContext(RoutingRequest routingRequest, Graph graph, Set from, Set to) {
        this(routingRequest, graph, from, to, false);
    }

    /**
     * Returns the StreetEdges that overlap between two vertices' edge sets.
     * It does not look at the TemporaryPartialStreetEdges, but the real parents
     * of these edges.
     */
    private Set overlappingParentStreetEdges(Vertex u, Vertex v) {
        // Fetch the parent edges so we aren't stuck with temporary edges.
        Set vEdges = getConnectedParentEdges(v);
        Set uEdges = getConnectedParentEdges(u);
        return Sets.intersection(uEdges, vEdges);
    }

    /**
     * Find all parent edges ({@link TemporaryPartialStreetEdge#getParentEdge()}) for
     * {@link Vertex#getIncoming()} and {@link Vertex#getIncoming()} edges.
     * Edges of other types are ignored.
     */
    private static Set getConnectedParentEdges(Vertex loc) {
        return Stream.concat(loc.getIncoming().stream(), loc.getOutgoing().stream())
                .filter(it -> it instanceof TemporaryPartialStreetEdge)
                .map(it -> ((TemporaryPartialStreetEdge)it).getParentEdge())
                .collect(Collectors.toSet());
    }

    /**
     * Creates a PartialStreetEdge along the input StreetEdge iff its direction makes this possible.
     */
    private void makePartialEdgeAlong(StreetEdge streetEdge, StreetVertex from, StreetVertex to) {
        LineString parent = streetEdge.getGeometry();
        LineString head = GeometryUtils.getInteriorSegment(parent,
                streetEdge.getFromVertex().getCoordinate(), from.getCoordinate());
        LineString tail = GeometryUtils.getInteriorSegment(parent,
                to.getCoordinate(), streetEdge.getToVertex().getCoordinate());

        if (parent.getLength() > head.getLength() + tail.getLength()) {
            LineString partial = GeometryUtils.getInteriorSegment(parent,
                    from.getCoordinate(), to.getCoordinate());

            double lengthRatio = partial.getLength() / parent.getLength();
            double length = streetEdge.getDistanceMeters() * lengthRatio;

            //TODO: localize this
            String name = from.getLabel() + " to " + to.getLabel();
            new TemporaryPartialStreetEdge(streetEdge, from, to, partial, new NonLocalizedString(name), length);
        }
    }

    /**
     * Flexible constructor which may compute to/from vertices.
     * 
     * TODO(flamholz): delete this flexible constructor and move the logic to constructors above appropriately.
     * 
     * @param findPlaces if true, compute origin and target from RoutingRequest using spatial indices.
     */
    private RoutingContext(
            RoutingRequest routingRequest,
            Graph graph,
            Set from,
            Set to,
            boolean findPlaces
    ) {
        if (graph == null) {
            throw new GraphNotFoundException();
        }
        this.opt = routingRequest;
        this.graph = graph;

        Set fromVertices;
        Set toVertices;

        if (findPlaces) {
            // normal mode, search for vertices based RoutingRequest and split streets
            fromVertices = graph.streetIndex.getVerticesForLocation(
                opt.from,
                opt,
                false
            );
            toVertices = graph.streetIndex.getVerticesForLocation(
                opt.to,
                opt,
                true
            );

        } else {
            // debug mode, force endpoint vertices to those specified rather than searching
            fromVertices = from;
            toVertices = to;
        }

        this.fromVertices = routingRequest.arriveBy ? toVertices : fromVertices;
        this.toVertices = routingRequest.arriveBy ? fromVertices : toVertices;

        if (graph.index != null) {
            this.bannedRoutes = routingRequest.getBannedRoutes(graph.index.getAllRoutes());
        } else {
            this.bannedRoutes = Collections.emptySet();
        }

        adjustForSameFromToEdge();

        remainingWeightHeuristic = new EuclideanRemainingWeightHeuristic();
    }

    private RoutingContext(
            RoutingRequest routingRequest,
            Graph graph,
            Vertex from,
            Vertex to,
            boolean findPlaces
    ) {
        this(
                routingRequest,
                graph,
                Collections.singleton(from),
                Collections.singleton(to),
                findPlaces
        );
    }

    /**
     * If the from and to vertices are generated and lie along some of the same edges, we need to wire
     * them up along those edges so that we don't get odd circuitous routes for really short trips.
     */
    private void adjustForSameFromToEdge() {
        if (fromVertices != null && toVertices != null) {
            Set fromStreetVertices = new HashSet<>();
            for (Vertex from : fromVertices) {
                if (from == null) { continue; }
                for (Edge outgoing : from.getOutgoing()) {
                    Vertex toVertex = outgoing.getToVertex();
                    if (outgoing instanceof TemporaryFreeEdge && toVertex instanceof StreetVertex
                        && toVertex
                        .getOutgoing()
                        .stream()
                        .anyMatch(edge -> edge instanceof TemporaryPartialStreetEdge)) {
                        // The vertex is connected with an TemporaryFreeEdge connector to the
                        // TemporaryPartialStreetEdge
                        fromStreetVertices.add((StreetVertex) toVertex);
                    }
                    else if (outgoing instanceof TemporaryPartialStreetEdge
                        && from instanceof StreetVertex) {
                        fromStreetVertices.add((StreetVertex) from);
                    }
                }
            }

            Set toStreetVertices = new HashSet<>();
            for (Vertex to : toVertices) {
                if (to == null) { continue; }
                for (Edge incoming : to.getIncoming()) {
                    Vertex fromVertex = incoming.getFromVertex();
                    if (incoming instanceof TemporaryFreeEdge && fromVertex instanceof StreetVertex
                        && fromVertex
                        .getIncoming()
                        .stream()
                        .anyMatch(edge -> edge instanceof TemporaryPartialStreetEdge)) {
                        // The vertex is connected with an TemporaryFreeEdge connector to the
                        // TemporaryPartialStreetEdge
                        toStreetVertices.add((StreetVertex) fromVertex);
                    }
                    else if (incoming instanceof TemporaryPartialStreetEdge
                        && to instanceof StreetVertex) {
                        toStreetVertices.add((StreetVertex) to);
                    }
                }
            }

            for (StreetVertex fromStreetVertex : fromStreetVertices) {
                for (StreetVertex toStreetVertex : toStreetVertices) {
                    Set overlap = overlappingParentStreetEdges(fromStreetVertex,
                        toStreetVertex
                    );
                    for (StreetEdge pse : overlap) {
                        makePartialEdgeAlong(pse, fromStreetVertex, toStreetVertex);
                    }
                }
            }
        }
    }

    /* INSTANCE METHODS */

    public void checkIfVerticesFound() {
        List routingErrors = new ArrayList<>();

        // check origin present when not doing an arrive-by batch search
        if (fromVertices == null && !(opt.oneToMany == true && opt.arriveBy == true)) {
            routingErrors.add(
                new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE)
            );
        }

        // check destination present when not doing a depart-after batch search
        if (toVertices == null && !(opt.oneToMany == true && opt.arriveBy == false)) {
            routingErrors.add(
                new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)
            );
        }

        if (routingErrors.size() > 0) {
            throw new RoutingValidationException(routingErrors);
        }
    }

    /**
     * Tear down this routing context, removing any temporary edges from
     * the "permanent" graph objects. This enables all temporary objects
     * for garbage collection.
     */
    public void destroy() {
        if (fromVertices != null) {
            for (Vertex fromVertex : fromVertices) {
                TemporaryVertex.dispose(fromVertex);
            }
        }
        if (toVertices != null) {
            for (Vertex toVertex : toVertices) {
                TemporaryVertex.dispose(toVertex);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy