/* 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 (props, 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.algorithm;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.opentripplanner.common.TurnRestriction;
import org.opentripplanner.common.TurnRestrictionType;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.routing.core.ConstantIntersectionTraversalCostModel;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.edgetype.PlainStreetEdge;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
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 TurnCostTest {
private Graph _graph;
private Vertex topRight;
private Vertex bottomLeft;
private PlainStreetEdge maple_main1, broad1_2;
private RoutingRequest proto;
@Before
public void before() {
_graph = new Graph();
// Graph for a fictional grid city with turn restrictions
StreetVertex maple1 = vertex("maple_1st", 2.0, 2.0);
StreetVertex maple2 = vertex("maple_2nd", 1.0, 2.0);
StreetVertex maple3 = vertex("maple_3rd", 0.0, 2.0);
StreetVertex main1 = vertex("main_1st", 2.0, 1.0);
StreetVertex main2 = vertex("main_2nd", 1.0, 1.0);
StreetVertex main3 = vertex("main_3rd", 0.0, 1.0);
StreetVertex broad1 = vertex("broad_1st", 2.0, 0.0);
StreetVertex broad2 = vertex("broad_2nd", 1.0, 0.0);
StreetVertex broad3 = vertex("broad_3rd", 0.0, 0.0);
// Each block along the main streets has unit length and is one-way
PlainStreetEdge maple1_2 = edge(maple1, maple2, 100.0, false);
PlainStreetEdge maple2_3 = edge(maple2, maple3, 100.0, false);
PlainStreetEdge main1_2 = edge(main1, main2, 100.0, false);
PlainStreetEdge main2_3 = edge(main2, main3, 100.0, false);
broad1_2 = edge(broad1, broad2, 100.0, false);
PlainStreetEdge broad2_3 = edge(broad2, broad3, 100.0, false);
// Each cross-street connects
maple_main1 = edge(maple1, main1, 50.0, false);
PlainStreetEdge main_broad1 = edge(main1, broad1, 100.0, false);
PlainStreetEdge maple_main2 = edge(maple2, main2, 50.0, false);
PlainStreetEdge main_broad2 = edge(main2, broad2, 50.0, false);
PlainStreetEdge maple_main3 = edge(maple3, main3, 100.0, false);
PlainStreetEdge main_broad3 = edge(main3, broad3, 100.0, false);
// Turn restrictions are only for driving modes.
// - can't turn from 1st onto Main.
// - can't turn from 2nd onto Main.
// - can't turn from 2nd onto Broad.
DisallowTurn(maple_main1, main1_2);
DisallowTurn(maple_main2, main2_3);
DisallowTurn(main_broad2, broad2_3);
// Hold onto some vertices for the tests
topRight = maple1;
bottomLeft = broad3;
// Make a prototype routing request.
proto = new RoutingRequest();
proto.setCarSpeed(1.0);
proto.setWalkSpeed(1.0);
proto.setBikeSpeed(1.0);
proto.setTurnReluctance(1.0);
proto.setWalkReluctance(1.0);
proto.setStairsReluctance(1.0);
// Turn costs are all 0 by default.
proto.setTraversalCostModel(new ConstantIntersectionTraversalCostModel(0.0));
}
private GraphPath checkForwardRouteDuration(RoutingRequest options, int expectedDuration) {
ShortestPathTree tree = new GenericAStar().getShortestPathTree(options);
GraphPath path = tree.getPath(bottomLeft, false);
assertNotNull(path);
// Without turn costs, this path costs 2x100 + 2x50 = 300.
assertEquals(expectedDuration, path.getDuration());
// Weight == duration when reluctances == 0.
assertEquals(expectedDuration, (int) path.getWeight());
for (State s : path.states) {
assertEquals(s.getElapsedTimeSeconds(), (int) s.getWeight());
}
return path;
}
@Test
public void testForwardDefaultNoTurnCosts() {
RoutingRequest options = proto.clone();
options.setRoutingContext(_graph, topRight, bottomLeft);
// Without turn costs, this path costs 2x100 + 2x50 = 300.
checkForwardRouteDuration(options, 300);
}
@Test
public void testForwardDefaultConstTurnCosts() {
RoutingRequest options = proto.clone();
options.setTraversalCostModel(new ConstantIntersectionTraversalCostModel(10.0));
options.setRoutingContext(_graph, topRight, bottomLeft);
// Without turn costs, this path costs 2x100 + 2x50 = 300.
// Since we traverse 3 intersections, the total cost should be 330.
GraphPath path = checkForwardRouteDuration(options, 330);
// The intersection traversal cost should be applied to the state *after*
// the intersection itself.
List<State> states = path.states;
assertEquals(5, states.size());
assertEquals("maple_1st", states.get(0).getVertex().getLabel());
assertEquals("main_1st", states.get(1).getVertex().getLabel());
assertEquals("main_2nd", states.get(2).getVertex().getLabel());
assertEquals("broad_2nd", states.get(3).getVertex().getLabel());
assertEquals("broad_3rd", states.get(4).getVertex().getLabel());
assertEquals(0, states.get(0).getElapsedTimeSeconds());
assertEquals(50, states.get(1).getElapsedTimeSeconds()); // maple_main1 = 50
assertEquals(160, states.get(2).getElapsedTimeSeconds()); // main1_2 = 100
assertEquals(220, states.get(3).getElapsedTimeSeconds()); // main_broad2 = 50
assertEquals(330, states.get(4).getElapsedTimeSeconds()); // broad2_3 = 100
}
@Test
public void testForwardCarNoTurnCosts() {
RoutingRequest options = proto.clone();
options.setMode(TraverseMode.CAR);
options.setRoutingContext(_graph, topRight, bottomLeft);
// Without turn costs, this path costs 3x100 + 1x50 = 300.
GraphPath path = checkForwardRouteDuration(options, 350);
List<State> states = path.states;
assertEquals(5, states.size());
assertEquals("maple_1st", states.get(0).getVertex().getLabel());
assertEquals("main_1st", states.get(1).getVertex().getLabel());
assertEquals("broad_1st", states.get(2).getVertex().getLabel());
assertEquals("broad_2nd", states.get(3).getVertex().getLabel());
assertEquals("broad_3rd", states.get(4).getVertex().getLabel());
}
@Test
public void testForwardCarConstTurnCosts() {
RoutingRequest options = proto.clone();
options.setTraversalCostModel(new ConstantIntersectionTraversalCostModel(10.0));
options.setMode(TraverseMode.CAR);
options.setRoutingContext(_graph, topRight, bottomLeft);
// Without turn costs, this path costs 3x100 + 1x50 = 350.
// Since there are 3 turns, the total cost should be 380.
GraphPath path = checkForwardRouteDuration(options, 380);
List<State> states = path.states;
assertEquals(5, states.size());
assertEquals("maple_1st", states.get(0).getVertex().getLabel());
assertEquals("main_1st", states.get(1).getVertex().getLabel());
assertEquals("broad_1st", states.get(2).getVertex().getLabel());
assertEquals("broad_2nd", states.get(3).getVertex().getLabel());
assertEquals("broad_3rd", states.get(4).getVertex().getLabel());
assertEquals(0, states.get(0).getElapsedTimeSeconds());
assertEquals(50, states.get(1).getElapsedTimeSeconds()); // maple_main1 = 50
assertEquals(160, states.get(2).getElapsedTimeSeconds()); // main1_2 = 100
assertEquals(270, states.get(3).getElapsedTimeSeconds()); // broad1_2 = 100
assertEquals(380, states.get(4).getElapsedTimeSeconds()); // broad2_3 = 100
}
/****
* Private Methods
****/
private StreetVertex 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, boolean back) {
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);
StreetTraversalPermission perm = StreetTraversalPermission.ALL;
PlainStreetEdge pse = new PlainStreetEdge(vA, vB, geom, name, length, perm, back);
pse.setCarSpeed(1.0f);
return pse;
}
private void DisallowTurn(PlainStreetEdge from, PlainStreetEdge to) {
TurnRestrictionType rType = TurnRestrictionType.NO_TURN;
TraverseModeSet restrictedModes = new TraverseModeSet(TraverseMode.CAR, TraverseMode.CUSTOM_MOTOR_VEHICLE);
TurnRestriction restrict = new TurnRestriction(from, to, rType, restrictedModes);
from.addTurnRestriction(restrict);
}
}