/* 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.routing.edgetype;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.routing.core.IntersectionTraversalCostModel;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
import org.opentripplanner.routing.vertextype.StreetVertex;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
public class PartialPlainStreetEdgeTest {
private Graph _graph;
private IntersectionVertex v1, v2, v3, v4;
private PlainStreetEdge e1, e1Reverse, e2, e3;
@Before
public void setUp() throws Exception {
_graph = new Graph();
// Graph for a fictional grid city with turn restrictions
v1 = vertex("maple_1st", 2.0, 2.0);
v2 = vertex("maple_2nd", 1.0, 2.0);
v3 = vertex("maple_3rd", 0.0, 2.0);
v4 = vertex("maple_4th", -1.0, 2.0);
e1 = edge(v1, v2, 1.0, StreetTraversalPermission.ALL);
e1Reverse = edge(v2, v1, 1.0, StreetTraversalPermission.ALL);
e2 = edge(v2, v3, 1.0, StreetTraversalPermission.ALL);
e3 = edge(v3, v4, 1.0, StreetTraversalPermission.ALL);
}
@Test
public void testConstruction() {
StreetTraversalPermission perm = StreetTraversalPermission.ALL_DRIVING;
PartialPlainStreetEdge pEdge = new PartialPlainStreetEdge(e1, v1, v2, e1.getGeometry(),
"partial e1", e1.getLength(), perm, true);
assertTrue(pEdge.isEquivalentTo(e1));
assertTrue(pEdge.isPartial());
assertTrue(pEdge.isBack());
assertFalse(pEdge.isReverseOf(e1));
assertTrue(pEdge.isReverseOf(e1Reverse));
assertEquals(e1.getId(), pEdge.getId());
assertEquals(perm, pEdge.getPermission());
assertEquals(e1.getCarSpeed(), pEdge.getCarSpeed(), 0.0);
// Simpler constructor - copies permission from parent edge and sets back to true.
pEdge = new PartialPlainStreetEdge(e1, v1, v2, e1.getGeometry(), "partial e1",
e1.getLength());
assertTrue(pEdge.isEquivalentTo(e1));
assertTrue(pEdge.isPartial());
assertFalse(pEdge.isBack());
assertFalse(pEdge.isReverseOf(e1));
assertTrue(pEdge.isReverseOf(e1Reverse));
assertEquals(e1.getId(), pEdge.getId());
assertEquals(e1.getPermission(), pEdge.getPermission());
assertEquals(e1.getCarSpeed(), pEdge.getCarSpeed(), 0.0);
}
@Test
public void testTraversal() {
RoutingRequest options = new RoutingRequest();
options.setMode(TraverseMode.CAR);
options.setRoutingContext(_graph, v1, v2);
// Partial edge with same endpoints as the parent.
PartialPlainStreetEdge pEdge1 = new PartialPlainStreetEdge(e1, v1, v2, e1.getGeometry(),
"partial e1", e1.getLength());
PartialPlainStreetEdge pEdge2 = new PartialPlainStreetEdge(e2, v2, v3, e2.getGeometry(),
"partial e2", e2.getLength());
// Traverse both the partial and parent edges.
State s0 = new State(options);
State s1 = e1.traverse(s0);
State partialS0 = new State(options);
State partialS1 = pEdge1.traverse(partialS0);
// Traversal of original and partial edges should yield the same results.
assertEquals(s1.getTimeSeconds(), partialS1.getTimeSeconds());
assertEquals(s1.getElapsedTimeSeconds(), partialS1.getElapsedTimeSeconds());
assertEquals(s1.getWeight(), partialS1.getWeight(), 0.0);
// Now traverse the second partial/parent edge pair.
State s2 = e2.traverse(s1);
State partialS2 = pEdge2.traverse(partialS1);
// Same checks as above.
assertEquals(s2.getTimeSeconds(), partialS2.getTimeSeconds());
assertEquals(s2.getElapsedTimeSeconds(), partialS2.getElapsedTimeSeconds());
assertEquals(s2.getWeight(), partialS2.getWeight(), 0.0);
}
@Test
public void testTraversalOfSubdividedEdge() {
Coordinate nearestPoint = new Coordinate(0.5, 2.0);
List<StreetEdge> edges = new ArrayList<StreetEdge>();
edges.add(e2);
StreetLocation intermediate = StreetLocation.createStreetLocation(_graph, "middle of e2", "foo", edges, nearestPoint);
RoutingRequest options = new RoutingRequest();
options.setMode(TraverseMode.CAR);
options.setRoutingContext(_graph, v1, v2);
// All intersections take 10 minutes - we'll notice if one isn't counted.
double turnDurationSecs = 10.0 * 60.0;
options.setTraversalCostModel(new DummyCostModel(turnDurationSecs));
options.setTurnReluctance(1.0);
State s0 = new State(options);
State s1 = e1.traverse(s0);
State s2 = e2.traverse(s1);
State s3 = e3.traverse(s2);
Edge partialE2First = intermediate.getIncoming().iterator().next();
Edge partialE2Second = intermediate.getOutgoing().iterator().next();
System.out.println(intermediate.getIncoming());
System.out.println(intermediate.getOutgoing());
State partialS0 = new State(options);
State partialS1 = e1.traverse(partialS0);
State partialS2A = partialE2First.traverse(partialS1);
State partialS2B = partialE2Second.traverse(partialS2A);
State partialS3 = e3.traverse(partialS2B);
// Should start at the same time.
assertEquals(s0.getTimeSeconds(), partialS0.getTimeSeconds());
// Time and cost should be the same up to a rounding difference.
assertTrue(Math.abs(s3.getTimeSeconds() - partialS3.getTimeSeconds()) <= 1);
assertTrue(Math.abs(s3.getElapsedTimeSeconds() - partialS3.getElapsedTimeSeconds()) <= 1);
assertTrue(Math.abs(s3.getWeight() - partialS3.getWeight()) <= 1);
// All intersections take 0 seconds now.
options.setTraversalCostModel(new DummyCostModel(0.0));
State s0NoCost = new State(options);
State s1NoCost = e1.traverse(s0NoCost);
State s2NoCost = e2.traverse(s1NoCost);
State s3NoCost = e3.traverse(s2NoCost);
State partialS0NoCost = new State(options);
State partialS1NoCost = e1.traverse(partialS0NoCost);
State partialS2ANoCost = partialE2First.traverse(partialS1NoCost);
State partialS2BNoCost = partialE2Second.traverse(partialS2ANoCost);
State partialS3NoCost = e3.traverse(partialS2BNoCost);
// Time and cost should be the same up to a rounding difference.
assertTrue(Math.abs(s3NoCost.getTimeSeconds() - partialS3NoCost.getTimeSeconds()) <= 1);
assertTrue(Math.abs(s3NoCost.getElapsedTimeSeconds() - partialS3NoCost.getElapsedTimeSeconds()) <= 1);
assertTrue(Math.abs(s3NoCost.getWeight() - partialS3NoCost.getWeight()) <= 1);
// Difference in duration and weight between now and before should be
// entirely due to the crossing of 2 intersections at v2 and v3.
double expectedDifference = 2 * 10 * 60.0;
double durationDiff = s3.getTimeSeconds() - s3NoCost.getTimeSeconds();
double partialDurationDiff = partialS3.getTimeSeconds() - partialS3NoCost.getTimeSeconds();
assertTrue(Math.abs(durationDiff - expectedDifference) <= 1);
assertTrue(Math.abs(partialDurationDiff - expectedDifference) <= 1);
// Turn reluctance is 1.0, so weight == duration.
double weightDiff = s3.getWeight() - s3NoCost.getWeight();
double partialWeightDiff = partialS3.getWeight() - partialS3NoCost.getWeight();
assertTrue(Math.abs(weightDiff - expectedDifference) <= 1);
assertTrue(Math.abs(partialWeightDiff - expectedDifference) <= 1);
}
@Test
public void testReverseEdge() {
PartialPlainStreetEdge pEdge1 = new PartialPlainStreetEdge(e1, v1, v2, e1.getGeometry(),
"partial e1", e1.getLength());
PartialPlainStreetEdge pEdge2 = new PartialPlainStreetEdge(e1Reverse, v2, v1, e1Reverse.getGeometry(),
"partial e2", e1Reverse.getLength());
assertFalse(e1.isReverseOf(pEdge1));
assertFalse(pEdge1.isReverseOf(e1));
assertFalse(e1Reverse.isReverseOf(pEdge2));
assertFalse(pEdge2.isReverseOf(e1Reverse));
assertTrue(e1.isReverseOf(pEdge2));
assertTrue(e1Reverse.isReverseOf(pEdge1));
assertTrue(e1Reverse.isReverseOf(e1));
assertTrue(e1.isReverseOf(e1Reverse));
assertTrue(pEdge1.isReverseOf(pEdge2));
assertTrue(pEdge2.isReverseOf(pEdge1));
}
/****
* Private Methods
****/
private IntersectionVertex vertex(String label, double lat, double lon) {
IntersectionVertex v = new IntersectionVertex(_graph, label, lat, lon);
return v;
}
/**
* Create an edge. If twoWay, create two edges (back and forth).
*
* @param vA
* @param vB
* @param length
* @param back true if this is a reverse edge
*/
private PlainStreetEdge edge(StreetVertex vA, StreetVertex vB, double length,
StreetTraversalPermission perm) {
String labelA = vA.getLabel();
String labelB = vB.getLabel();
String name = String.format("%s_%s", labelA, labelB);
Coordinate[] coords = new Coordinate[2];
coords[0] = vA.getCoordinate();
coords[1] = vB.getCoordinate();
LineString geom = GeometryUtils.getGeometryFactory().createLineString(coords);
return new PlainStreetEdge(vA, vB, geom, name, length, perm, false, 5.0f);
}
/**
* Dummy cost model. Returns what you put in.
*/
private static class DummyCostModel implements IntersectionTraversalCostModel {
private double turnCostSecs;
public DummyCostModel(double turnCostSecs) {
this.turnCostSecs = turnCostSecs;
}
@Override
public double computeTraversalCost(IntersectionVertex v, PlainStreetEdge from,
PlainStreetEdge to, TraverseMode mode, RoutingRequest options, float fromSpeed,
float toSpeed) {
return this.turnCostSecs;
}
}
}