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

org.opentripplanner.graph_builder.module.map.StreetMatcher Maven / Gradle / Ivy

package org.opentripplanner.graph_builder.module.map;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

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.common.pqueue.BinHeap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.linearref.LinearLocation;
import org.locationtech.jts.linearref.LocationIndexedLine;
import org.locationtech.jts.simplify.DouglasPeuckerSimplifier;

/**
 * This Performs most of the work for the MapBuilder graph builder module.
 * It determines which sequence of graph edges a GTFS shape probably corresponds to.
 * Note that GTFS shapes are not in any way constrained to OSM edges or even roads.
 */
public class StreetMatcher {
    private static final Logger log = LoggerFactory.getLogger(StreetMatcher.class);
    private static final double DISTANCE_THRESHOLD = 0.0002;

    Graph graph;

    private STRtree index;

    STRtree createIndex() {
        STRtree edgeIndex = new STRtree();
        for (Vertex v : graph.getVertices()) {
            for (Edge e : v.getOutgoing()) {
                if (e instanceof StreetEdge) {
                    Envelope envelope;
                    Geometry geometry = e.getGeometry();
                    envelope = geometry.getEnvelopeInternal();
                    edgeIndex.insert(envelope, e);
                }
            }
        }
        log.debug("Created index");
        return edgeIndex;
    }

    public StreetMatcher(Graph graph) {
        this.graph = graph;
        index = createIndex();
        index.build();
    }

    @SuppressWarnings("unchecked")
    public List match(Geometry routeGeometry) {
        
        routeGeometry = removeDuplicatePoints(routeGeometry);

        if (routeGeometry == null) 
            return null;
        
        routeGeometry = DouglasPeuckerSimplifier.simplify(routeGeometry, 0.00001);

        // initial state: start midway along a block.
        LocationIndexedLine indexedLine = new LocationIndexedLine(routeGeometry);

        LinearLocation startIndex = indexedLine.getStartIndex();

        Coordinate routeStartCoordinate = startIndex.getCoordinate(routeGeometry);
        Envelope envelope = new Envelope(routeStartCoordinate);
        double distanceThreshold = DISTANCE_THRESHOLD;
        envelope.expandBy(distanceThreshold);

        BinHeap states = new BinHeap();
        List nearbyEdges = index.query(envelope);
        while (nearbyEdges.isEmpty()) {
            envelope.expandBy(distanceThreshold);
            distanceThreshold *= 2;
            nearbyEdges = index.query(envelope);
        }

        // compute initial states
        for (Edge initialEdge : nearbyEdges) {
            Geometry edgeGeometry = initialEdge.getGeometry();
            
            LocationIndexedLine indexedEdge = new LocationIndexedLine(edgeGeometry);
            LinearLocation initialLocation = indexedEdge.project(routeStartCoordinate);
            
            double error = MatchState.distance(initialLocation.getCoordinate(edgeGeometry), routeStartCoordinate);
            MidblockMatchState state = new MidblockMatchState(null, routeGeometry, initialEdge, startIndex, initialLocation, error, 0.01);
            states.insert(state, 0); //make sure all initial states are visited by inserting them at 0
        }

        // search for best-matching path
        int seen_count = 0, total = 0;
        HashSet seen = new HashSet();
        while (!states.empty()) {
            double k = states.peek_min_key();
            MatchState state = states.extract_min();
            if (++total % 50000 == 0) {
                log.debug("seen / total: " + seen_count + " / " + total);
            }
            if (seen.contains(state)) {
                ++seen_count;
                continue;
            } else {
                if (k != 0) {
                    //but do not mark states as closed if we start at them
                    seen.add(state);
                }
            }
            if (state instanceof EndMatchState) {
                return toEdgeList(state);
            }
            for (MatchState next : state.getNextStates()) {
                if (seen.contains(next)) {
                    continue;
                }
                states.insert(next, next.getTotalError() - next.getDistanceAlongRoute());
            }
        }
        return null;
    }

    private Geometry removeDuplicatePoints(Geometry routeGeometry) {
        List coords = new ArrayList();
        Coordinate last = null;
        for (Coordinate c : routeGeometry.getCoordinates()) {
            if (!c.equals(last)) {
                last = c;
                coords.add(c);
            }
        }
        if (coords.size() < 2) {
            return null;
        }
        Coordinate[] coordArray = new Coordinate[coords.size()];
        return routeGeometry.getFactory().createLineString(coords.toArray(coordArray));
    }

    private List toEdgeList(MatchState next) {
        ArrayList edges = new ArrayList();
        Edge lastEdge = null;
        while (next != null) {
            Edge edge = next.getEdge();
            if (edge != lastEdge) {
                edges.add(edge);
                lastEdge = edge;
            }
            next = next.parent;
        }
        Collections.reverse(edges);
        return edges;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy