package org.opentripplanner.graph_builder.module.linking; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import gnu.trove.iterator.TObjectIntIterator; import gnu.trove.map.TObjectIntMap; import gnu.trove.map.hash.TObjectIntHashMap; import jersey.repackaged.com.google.common.collect.Maps; import org.junit.Test; import org.opentripplanner.common.geometry.GeometryUtils; import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.common.model.P2; import org.opentripplanner.profile.StopTreeCache; import org.opentripplanner.routing.edgetype.StreetEdge; import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.edgetype.StreetTraversalPermission; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.impl.DefaultStreetVertexIndexFactory; import org.opentripplanner.routing.vertextype.IntersectionVertex; import org.opentripplanner.routing.vertextype.SplitterVertex; import org.opentripplanner.routing.vertextype.StreetVertex; import org.opentripplanner.routing.vertextype.TransitStop; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import static org.junit.Assert.*; import static org.opentripplanner.graph_builder.module.FakeGraph.*; public class LinkingTest { /** maximum difference in walk distance, in meters, that is acceptable between the graphs */ public static final int EPSILON = 1; /** * Ensure that splitting edges yields edges that are identical in length for forward and back edges. * StreetEdges have lengths expressed internally in mm, and we want to be sure that not only do they * sum to the same values but also that they */ @Test public void testSplitting () { GeometryFactory gf= GeometryUtils.getGeometryFactory(); double x = -122.123; double y = 37.363; for (double delta = 0; delta <= 2; delta += 0.005) { StreetVertex v0 = new IntersectionVertex(null, "zero", x, y); StreetVertex v1 = new IntersectionVertex(null, "one", x + delta, y + delta); LineString geom = gf.createLineString(new Coordinate[] { v0.getCoordinate(), v1.getCoordinate() }); double dist = SphericalDistanceLibrary.distance(v0.getCoordinate(), v1.getCoordinate()); StreetEdge s0 = new StreetEdge(v0, v1, geom, "test", dist, StreetTraversalPermission.ALL, false); StreetEdge s1 = new StreetEdge(v1, v0, (LineString) geom.reverse(), "back", dist, StreetTraversalPermission.ALL, true); // split it but not too close to the end double splitVal = Math.random() * 0.95 + 0.025; 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<StreetEdge> sp0 = s0.split(sv0, true); P2<StreetEdge> 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 assertEquals(sp0.first.getDistance(), sp1.second.getDistance(), 0.0000001); assertEquals(sp0.second.getDistance(), sp1.first.getDistance(), 0.0000001); assertFalse(sp0.first.isBack()); assertFalse(sp0.second.isBack()); assertTrue(sp1.first.isBack()); assertTrue(sp1.second.isBack()); } } /** * Test that all the stops are linked identically * to the street network on two builds of similar graphs * with additional stops in one. * * We do this by building the graphs and then comparing the stop tree caches. */ @Test public void testStopsLinkedIdentically () throws UnsupportedEncodingException { // build the graph without the added stops Graph g1 = buildGraphNoTransit(); addRegularStopGrid(g1); link(g1); Graph g2 = buildGraphNoTransit(); addExtraStops(g2); addRegularStopGrid(g2); link(g2); // compare the linkages for (TransitStop ts : Iterables.filter(g1.getVertices(), TransitStop.class)) { Collection<Edge> stls = stls(ts.getOutgoing()); assertTrue(stls.size() >= 1); StreetTransitLink exemplar = (StreetTransitLink) stls.iterator().next(); TransitStop other = (TransitStop) g2.getVertex(ts.getLabel()); Collection<Edge> ostls = stls(other.getOutgoing()); assertEquals("Unequal number of links from stop " + ts, stls.size(), ostls.size()); StreetTransitLink oe = (StreetTransitLink) ostls.iterator().next(); assertEquals(exemplar.getToVertex().getLat(), oe.getToVertex().getLat(), 1e-10); assertEquals(exemplar.getToVertex().getLon(), oe.getToVertex().getLon(), 1e-10); } // compare the stop tree caches g1.index(new DefaultStreetVertexIndexFactory()); g2.index(new DefaultStreetVertexIndexFactory()); g1.rebuildVertexAndEdgeIndices(); g2.rebuildVertexAndEdgeIndices(); StopTreeCache s1 = g1.index.getStopTreeCache(); StopTreeCache s2 = g2.index.getStopTreeCache(); // convert the caches to be by stop label Map<String, int[]> l1 = cacheByLabel(s1); Map<String, int[]> l2 = cacheByLabel(s2); // do the comparison for (Entry<String, int[]> e : l1.entrySet()) { // graph 2 should contain all stops in graph 1 (and a few more) assertTrue(l2.containsKey(e.getKey())); TObjectIntMap<String> g1t = jaggedArrayToVertexMap(e.getValue(), g1); TObjectIntMap<String> g2t = jaggedArrayToVertexMap(l2.get(e.getKey()), g2); for (TObjectIntIterator<String> it = g1t.iterator(); it.hasNext();) { it.advance(); assertTrue(g2t.containsKey(it.key())); int newv = g2t.get(it.key()); assertTrue("At " + it.key() + " from stop " + g1.getVertex(e.getKey()) + ", difference in walk distances: " + it.value() + "m without extra stops," + newv + "m with", Math.abs(it.value() - newv) <= EPSILON); } } } private TObjectIntMap<String> jaggedArrayToVertexMap(int[] value, Graph g) { TObjectIntMap<String> ret = new TObjectIntHashMap<String>(); for (int i = 0; i < value.length; i++) { Vertex v = g.getVertexById(value[i++]); if (!v.getLabel().startsWith("osm:node")) continue; ret.put(v.getLabel(), value[i]); } return ret; } private static Collection<Edge> stls (Collection<Edge> edges) { return Collections2.filter(edges, new Predicate<Edge>() { @Override public boolean apply(Edge input) { return input instanceof StreetTransitLink; } }); } /** get the stop tree cache indexed by label */ public static Map<String, int[]> cacheByLabel (StopTreeCache c) { Map<String, int[]> ret = Maps.newHashMap(); for (Entry<TransitStop, int[]> e : c.distancesForStop.entrySet()) { ret.put(e.getKey().getLabel(), e.getValue()); } return ret; } }