From 7480a519f74a1f22ad85d4d1ec39059167273feb Mon Sep 17 00:00:00 2001 From: Marko Burjek Date: Wed, 25 May 2016 19:35:58 +0200 Subject: [PATCH] Use new SimpleStreetLinker on origin/destination Working on #2267. Linking works but code is a little messy. Currently one temporary vertex is created when closest thing is existing from/to vertex. And a second split vertex is created if the closest edge is a street which is splitted. All new edges and vertices are temporary and are removed when thread finishes. --- .../linking/OriginDestinationLinker.java | 94 ++++++++++++++++++ .../linking/SimpleStreetSplitter.java | 70 ++++++++++--- .../routing/edgetype/StreetEdge.java | 97 ++++++++++++------- .../edgetype/TemporaryPartialStreetEdge.java | 24 +++++ .../impl/StreetVertexIndexServiceImpl.java | 8 +- .../vertextype/TemporarySplitterVertex.java | 63 ++++++++++++ .../module/linking/LinkingTest.java | 4 +- 7 files changed, 305 insertions(+), 55 deletions(-) create mode 100644 src/main/java/org/opentripplanner/graph_builder/linking/OriginDestinationLinker.java create mode 100644 src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java diff --git a/src/main/java/org/opentripplanner/graph_builder/linking/OriginDestinationLinker.java b/src/main/java/org/opentripplanner/graph_builder/linking/OriginDestinationLinker.java new file mode 100644 index 00000000000..d7686d08539 --- /dev/null +++ b/src/main/java/org/opentripplanner/graph_builder/linking/OriginDestinationLinker.java @@ -0,0 +1,94 @@ +package org.opentripplanner.graph_builder.linking; + +import com.vividsolutions.jts.geom.Coordinate; +import org.apache.lucene.store.TrackingDirectoryWrapper; +import org.opentripplanner.common.model.GenericLocation; +import org.opentripplanner.common.model.P2; +import org.opentripplanner.routing.core.RoutingRequest; +import org.opentripplanner.routing.core.TraverseMode; +import org.opentripplanner.routing.core.TraverseModeSet; +import org.opentripplanner.routing.edgetype.StreetEdge; +import org.opentripplanner.routing.edgetype.TemporaryFreeEdge; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.routing.graph.Vertex; +import org.opentripplanner.routing.location.TemporaryStreetLocation; +import org.opentripplanner.routing.vertextype.StreetVertex; +import org.opentripplanner.util.NonLocalizedString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Linking seems to work. + * + * + * Created by mabu on 20.5.2016. + */ +public class OriginDestinationLinker extends SimpleStreetSplitter { + private static final Logger LOG = LoggerFactory.getLogger(OriginDestinationLinker.class); + /** + * Construct a new SimpleStreetSplitter. Be aware that only one SimpleStreetSplitter should be + * active on a graph at any given time. + * + * @param graph + */ + public OriginDestinationLinker(Graph graph) { + super(graph); + } + + public Vertex getClosestVertex(GenericLocation location, RoutingRequest options, + boolean endVertex) { + if (endVertex) { + LOG.debug("Finding end vertex for {}", location); + } else { + LOG.debug("Finding start vertex for {}", location); + } + Coordinate coord = location.getCoordinate(); + //TODO: add nice name + TemporaryStreetLocation closest = new TemporaryStreetLocation( + "corner " + Math.random(), coord, new NonLocalizedString("generated point"), endVertex); + + TraverseModeSet modes = options.modes; + TraverseMode nonTransitMode = TraverseMode.WALK; + if (modes.getCar()) + nonTransitMode = TraverseMode.CAR; + else if (modes.getWalk()) + nonTransitMode = TraverseMode.WALK; + else if (modes.getBicycle()) + nonTransitMode = TraverseMode.BICYCLE; + + if(!link(closest, nonTransitMode)) { + LOG.warn("Couldn't link {}", location); + } + return closest; + + } + + /** + * Make the appropriate type of link edges from a vertex + * + * @param from + * @param to + */ + @Override + protected void makeLinkEdges(Vertex from, StreetVertex to) { + TemporaryStreetLocation tse = (TemporaryStreetLocation) from; + if (tse.isEndVertex()) { + LOG.debug("Linking end vertex to {} -> {}", to, tse); + new TemporaryFreeEdge(to, tse); + } else { + LOG.debug("Linking start vertex to {} -> {}", tse, to); + new TemporaryFreeEdge(tse, to); + } + } + + @Override + protected void removeOriginalEdge(StreetEdge edge) { + //Intentionally empty since we are creating temporary edges which should not change the graph + //and they are removed anyway when thread is disposed + } + + @Override + protected void updateIndex(P2 edges) { + //Intentionally empty since we are creating temporary edges which should not change the graph + //and they are removed anyway when thread is disposed + } +} diff --git a/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java b/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java index 864063be9f6..6e48617b66c 100644 --- a/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java +++ b/src/main/java/org/opentripplanner/graph_builder/linking/SimpleStreetSplitter.java @@ -31,6 +31,8 @@ import org.opentripplanner.routing.vertextype.BikeRentalStationVertex; import org.opentripplanner.routing.vertextype.SplitterVertex; import org.opentripplanner.routing.vertextype.StreetVertex; +import org.opentripplanner.routing.vertextype.TemporarySplitterVertex; +import org.opentripplanner.routing.vertextype.TemporaryVertex; import org.opentripplanner.routing.vertextype.TransitStop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -101,8 +103,13 @@ else if (v instanceof BikeParkVertex) } } - /** Link this vertex into the graph */ + /** Link this vertex into the graph to the closest walkable edge */ public boolean link (Vertex vertex) { + return link(vertex, TraverseMode.WALK); + } + + /** Link this vertex into the graph */ + public boolean link(Vertex vertex, TraverseMode traverseMode) { // find nearby street edges // TODO: we used to use an expanding-envelope search, which is more efficient in // dense areas. but first let's see how inefficient this is. I suspect it's not too @@ -130,7 +137,7 @@ public boolean link (Vertex vertex) { public boolean apply(StreetEdge edge) { // note: not filtering by radius here as distance calculation is expensive // we do that below. - return edge.canTraverse(new TraverseModeSet(TraverseMode.WALK)) && + return edge.canTraverse(new TraverseModeSet(traverseMode)) && // only link to edges still in the graph. edge.getToVertex().getIncoming().contains(edge); } @@ -209,43 +216,76 @@ else if (ll.getSegmentIndex() == orig.getNumPoints() - 2 && ll.getSegmentFractio makeLinkEdges(tstop, (StreetVertex) edge.getToVertex()); } - else { + else { + + TemporaryVertex temporaryVertex = null; + boolean endVertex = false; + if (tstop instanceof TemporaryVertex) { + temporaryVertex = (TemporaryVertex) tstop; + endVertex = temporaryVertex.isEndVertex(); + + } // split the edge, get the split vertex - SplitterVertex v0 = split(edge, ll); + SplitterVertex v0 = split(edge, ll, temporaryVertex != null, endVertex); makeLinkEdges(tstop, v0); } } - /** Split the street edge at the given fraction */ - private SplitterVertex split (StreetEdge edge, LinearLocation ll) { + + /** + * Split the street edge at the given fraction + * + * @param edge to be split + * @param ll fraction at which to split the edge + * @param temporarySplit if true this is temporary split at origin/destinations search and only temporary edges vertices are created + * @param endVertex if this is temporary edge this is true if this is end vertex otherwise it doesn't matter + * @return Splitter vertex with added new edges + */ + private SplitterVertex split (StreetEdge edge, LinearLocation ll, boolean temporarySplit, boolean endVertex) { LineString geometry = edge.getGeometry(); // create the geometries Coordinate splitPoint = ll.getCoordinate(geometry); // every edge can be split exactly once, so this is a valid label - SplitterVertex v = new SplitterVertex(graph, "split from " + edge.getId(), splitPoint.x, splitPoint.y, edge); + SplitterVertex v; + if (temporarySplit) { + v = new TemporarySplitterVertex(graph, "split from " + edge.getId(), splitPoint.x, splitPoint.y, + edge, endVertex); + } else { + v = new SplitterVertex(graph, "split from " + edge.getId(), splitPoint.x, splitPoint.y, + edge); + } // make the edges // TODO this is using the StreetEdge implementation of split, which will discard elevation information // on edges that have it - P2 edges = edge.split(v); + P2 edges = edge.split(v, !temporarySplit); - // update indices - idx.insert(edges.first.getGeometry().getEnvelopeInternal(), edges.first); - idx.insert(edges.second.getGeometry().getEnvelopeInternal(), edges.second); + //this functions are created so they can be overridden in OriginDestinationLinker where they should do nothing. + updateIndex(edges); - // (no need to remove original edge, we filter it when it comes out of the index) + removeOriginalEdge(edge); + + return v; + } - // remove original edge + protected void removeOriginalEdge(StreetEdge edge) { + // remove original edge from the graph edge.getToVertex().removeIncoming(edge); edge.getFromVertex().removeOutgoing(edge); + } - return v; + protected void updateIndex(P2 edges) { + // update indices of new edges + idx.insert(edges.first.getGeometry().getEnvelopeInternal(), edges.first); + idx.insert(edges.second.getGeometry().getEnvelopeInternal(), edges.second); + + // (no need to remove original edge, we filter it when it comes out of the index) } /** Make the appropriate type of link edges from a vertex */ - private void makeLinkEdges (Vertex from, StreetVertex to) { + protected void makeLinkEdges (Vertex from, StreetVertex to) { if (from instanceof TransitStop) makeTransitLinkEdges((TransitStop) from, to); else if (from instanceof BikeRentalStationVertex) diff --git a/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java index e67587876e1..8c9ae0dc3f2 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/StreetEdge.java @@ -29,6 +29,7 @@ the License, or (at your option) any later version. import org.opentripplanner.routing.vertextype.OsmVertex; import org.opentripplanner.routing.vertextype.SplitterVertex; import org.opentripplanner.routing.vertextype.StreetVertex; +import org.opentripplanner.routing.vertextype.TemporarySplitterVertex; import org.opentripplanner.traffic.StreetSpeedSnapshot; import org.opentripplanner.util.BitSetUtils; import org.opentripplanner.util.I18NString; @@ -797,48 +798,70 @@ protected void calculateLengthFromGeometry () { } /** Split this street edge and return the resulting street edges */ - public P2 split (SplitterVertex v) { + public P2 split(SplitterVertex v, boolean destructive) { P2 geoms = GeometryUtils.splitGeometryAtPoint(getGeometry(), v.getCoordinate()); - StreetEdge e1 = new StreetEdge((StreetVertex) fromv, v, geoms.first, name, 0, permission, this.isBack()); - StreetEdge e2 = new StreetEdge(v, (StreetVertex) tov, geoms.second, name, 0, permission, this.isBack()); - - // figure the lengths, ensuring that they sum to the length of this edge - e1.calculateLengthFromGeometry(); - e2.calculateLengthFromGeometry(); - - // we have this code implemented in both directions, because splits are fudged half a millimeter - // when the length of this is odd. We want to make sure the lengths of the split streets end up - // exactly the same as their backStreets so that if they are split again the error does not accumulate - // and so that the order in which they are split does not matter. - if (!isBack()) { - // cast before the divide so that the sum is promoted - double frac = (double) e1.length_mm / (e1.length_mm + e2.length_mm); - e1.length_mm = (int) (length_mm * frac); - e2.length_mm = length_mm - e1.length_mm; - } - else { - // cast before the divide so that the sum is promoted - double frac = (double) e2.length_mm / (e1.length_mm + e2.length_mm); - e2.length_mm = (int) (length_mm * frac); - e1.length_mm = length_mm - e2.length_mm; - } - if (e1.length_mm < 0 || e2.length_mm < 0) { - e1.tov.removeIncoming(e1); - e1.fromv.removeOutgoing(e1); - e2.tov.removeIncoming(e2); - e2.fromv.removeOutgoing(e2); - throw new IllegalStateException("Split street is longer than original street!"); - } + StreetEdge e1 = null; + StreetEdge e2 = null; + + if (destructive) { + e1 = new StreetEdge((StreetVertex) fromv, v, geoms.first, name, 0, permission, this.isBack()); + e2 = new StreetEdge(v, (StreetVertex) tov, geoms.second, name, 0, permission, this.isBack()); + + // figure the lengths, ensuring that they sum to the length of this edge + e1.calculateLengthFromGeometry(); + e2.calculateLengthFromGeometry(); + + // we have this code implemented in both directions, because splits are fudged half a millimeter + // when the length of this is odd. We want to make sure the lengths of the split streets end up + // exactly the same as their backStreets so that if they are split again the error does not accumulate + // and so that the order in which they are split does not matter. + if (!isBack()) { + // cast before the divide so that the sum is promoted + double frac = (double) e1.length_mm / (e1.length_mm + e2.length_mm); + e1.length_mm = (int) (length_mm * frac); + e2.length_mm = length_mm - e1.length_mm; + } + else { + // cast before the divide so that the sum is promoted + double frac = (double) e2.length_mm / (e1.length_mm + e2.length_mm); + e2.length_mm = (int) (length_mm * frac); + e1.length_mm = length_mm - e2.length_mm; + } + + if (e1.length_mm < 0 || e2.length_mm < 0) { + e1.tov.removeIncoming(e1); + e1.fromv.removeOutgoing(e1); + e2.tov.removeIncoming(e2); + e2.fromv.removeOutgoing(e2); + throw new IllegalStateException("Split street is longer than original street!"); + } - for (StreetEdge e : new StreetEdge[] { e1, e2 }) { - e.setBicycleSafetyFactor(getBicycleSafetyFactor()); - e.setHasBogusName(hasBogusName()); - e.setStairs(isStairs()); - e.setWheelchairAccessible(isWheelchairAccessible()); - e.setBack(isBack()); + for (StreetEdge e : new StreetEdge[] { e1, e2 }) { + e.setBicycleSafetyFactor(getBicycleSafetyFactor()); + e.setHasBogusName(hasBogusName()); + e.setStairs(isStairs()); + e.setWheelchairAccessible(isWheelchairAccessible()); + e.setBack(isBack()); + } + } else { + if (((TemporarySplitterVertex) v).isEndVertex()) { + e1 = new TemporaryPartialStreetEdge(this, (StreetVertex) fromv, (TemporarySplitterVertex) v, geoms.first, name, 0); + e1.calculateLengthFromGeometry(); + e1.setNoThruTraffic(this.isNoThruTraffic()); + e1.setStreetClass(this.getStreetClass()); + } else { + e2 = new TemporaryPartialStreetEdge(this, (TemporarySplitterVertex) v, (StreetVertex) tov, geoms.second, name, 0); + e2.calculateLengthFromGeometry(); + e2.setNoThruTraffic(this.isNoThruTraffic()); + e2.setStreetClass(this.getStreetClass()); + } } + + + + return new P2(e1, e2); } diff --git a/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java index cb9ea0938a1..0fdb3835555 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/TemporaryPartialStreetEdge.java @@ -16,6 +16,8 @@ the License, or (at your option) any later version. import com.vividsolutions.jts.geom.LineString; import org.opentripplanner.routing.location.TemporaryStreetLocation; import org.opentripplanner.routing.vertextype.StreetVertex; +import org.opentripplanner.routing.vertextype.TemporarySplitterVertex; +import org.opentripplanner.routing.vertextype.TemporaryVertex; import org.opentripplanner.util.I18NString; final public class TemporaryPartialStreetEdge extends PartialStreetEdge implements TemporaryEdge { @@ -45,6 +47,17 @@ public TemporaryPartialStreetEdge(StreetEdge parentEdge, TemporaryStreetLocation } } + public TemporaryPartialStreetEdge(StreetEdge parentEdge, TemporarySplitterVertex v1, + StreetVertex v2, LineString geometry, I18NString name, double length) { + super(parentEdge, v1, v2, geometry, name, length); + + if (v1.isEndVertex()) { + throw new IllegalStateException("A temporary edge is directed away from an end vertex"); + } else { + endEdge = false; + } + } + public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, TemporaryStreetLocation v2, LineString geometry, I18NString name, double length) { super(parentEdge, v1, v2, geometry, name, length); @@ -56,6 +69,17 @@ public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, } } + public TemporaryPartialStreetEdge(StreetEdge parentEdge, StreetVertex v1, + TemporarySplitterVertex v2, LineString geometry, I18NString name, double length) { + super(parentEdge, v1, v2, geometry, name, length); + + if (v2.isEndVertex()) { + endEdge = true; + } else { + throw new IllegalStateException("A temporary edge is directed towards a start vertex"); + } + } + @Override public void dispose() { if (endEdge != null) { diff --git a/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java b/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java index 7786b713079..b04ef5bec25 100644 --- a/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java +++ b/src/main/java/org/opentripplanner/routing/impl/StreetVertexIndexServiceImpl.java @@ -27,6 +27,7 @@ the License, or (at your option) any later version. import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.common.model.GenericLocation; import org.opentripplanner.common.model.P2; +import org.opentripplanner.graph_builder.linking.OriginDestinationLinker; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.TraversalRequirements; import org.opentripplanner.routing.core.TraverseModeSet; @@ -86,6 +87,8 @@ public class StreetVertexIndexServiceImpl implements StreetVertexIndexService { static final Logger LOG = LoggerFactory.getLogger(StreetVertexIndexServiceImpl.class); + private OriginDestinationLinker originDestinationLinker; + public StreetVertexIndexServiceImpl(Graph graph) { this(graph, true); } @@ -106,6 +109,8 @@ public StreetVertexIndexServiceImpl(Graph graph, boolean hashGrid) { ((STRtree) edgeTree).build(); ((STRtree) transitStopTree).build(); } + + originDestinationLinker = new OriginDestinationLinker(this.graph); } /** @@ -564,7 +569,8 @@ public Vertex getVertexForLocation(GenericLocation loc, RoutingRequest options, boolean endVertex) { Coordinate c = loc.getCoordinate(); if (c != null) { - return getClosestVertex(loc, options, endVertex); + //return getClosestVertex(loc, options, endVertex); + return originDestinationLinker.getClosestVertex(loc, options, endVertex); } // No Coordinate available. diff --git a/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java b/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java new file mode 100644 index 00000000000..cd138479ffd --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/vertextype/TemporarySplitterVertex.java @@ -0,0 +1,63 @@ +package org.opentripplanner.routing.vertextype; + +import org.opentripplanner.routing.edgetype.StreetEdge; +import org.opentripplanner.routing.edgetype.TemporaryEdge; +import org.opentripplanner.routing.graph.Edge; +import org.opentripplanner.routing.graph.Graph; + +/** + * TODO: decide what to do with this. Currently temporary vertices have only incoming or outgoing edges + * But this one needs to have both since different vertex is start vertex + * Created by mabu on 20.5.2016. + */ +public class TemporarySplitterVertex extends SplitterVertex implements TemporaryVertex { + final private boolean endVertex; + + public TemporarySplitterVertex(Graph g, String label, double x, double y, StreetEdge streetEdge, + boolean endVertex) { + super(null, label, x, y, streetEdge); + this.endVertex = endVertex; + } + + @Override + public void addIncoming(Edge edge) { + + if (edge instanceof TemporaryEdge) { + if (endVertex) { + super.addIncoming(edge); + } else { + super.addIncoming(edge); + //throw new UnsupportedOperationException("Can't add incoming edge to start vertex"); + } + } else { + throw new UnsupportedOperationException("Can't add permanent edge to temporary vertex"); + } + } + + @Override + public void addOutgoing(Edge edge) { + + if (edge instanceof TemporaryEdge) { + if (endVertex) { + super.addOutgoing(edge); + //throw new UnsupportedOperationException("Can't add outgoing edge to end vertex"); + } else { + super.addOutgoing(edge); + } + } else { + throw new UnsupportedOperationException("Can't add permanent edge to temporary vertex"); + } + } + + @Override + public boolean isEndVertex() { + return endVertex; + } + + @Override + public void dispose() { + for (Object temp : endVertex ? getIncoming() : getOutgoing()) { + ((TemporaryEdge) temp).dispose(); + } + } +} diff --git a/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java b/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java index 7fc150bc999..6ee0099594b 100644 --- a/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java +++ b/src/test/java/org/opentripplanner/graph_builder/module/linking/LinkingTest.java @@ -64,8 +64,8 @@ public void testSplitting () { SplitterVertex sv0 = new SplitterVertex(null, "split", x + delta * splitVal, y + delta * splitVal, s0); SplitterVertex sv1 = new SplitterVertex(null, "split", x + delta * splitVal, y + delta * splitVal, s1); - P2 sp0 = s0.split(sv0); - P2 sp1 = s1.split(sv1); + P2 sp0 = s0.split(sv0, true); + P2 sp1 = s1.split(sv1, true); // distances expressed internally in mm so this epsilon is plenty good enough to ensure that they // have the same values