org.opentripplanner.graph_builder.module.map.StreetMatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
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 - 2025 Weber Informatics LLC | Privacy Policy