/* 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.graph_builder.impl.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 com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.index.strtree.STRtree; import com.vividsolutions.jts.linearref.LinearLocation; import com.vividsolutions.jts.linearref.LocationIndexedLine; import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier; 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<Edge> 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<MatchState> states = new BinHeap<MatchState>(); List<Edge> 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<MatchState> seen = new HashSet<MatchState>(); 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<Coordinate> coords = new ArrayList<Coordinate>(); 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<Edge> toEdgeList(MatchState next) { ArrayList<Edge> edges = new ArrayList<Edge>(); 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; } }