package org.opentrafficsim.road.gtu.strategical.route;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.djunits.value.vdouble.scalar.Time;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.network.LinkInterface;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.network.NodeInterface;
import org.opentrafficsim.core.network.route.CompleteRoute;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.road.gtu.lane.InternalLaneBasedGtu;
import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
import org.opentrafficsim.road.gtu.strategical.AbstractLaneBasedStrategicalPlanner;
import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
import org.opentrafficsim.road.network.lane.CrossSectionElement;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LanePosition;
/**
* Strategical planner, route-based, with personal driving characteristics, which contain settings for the tactical planner. The
* tactical planner will only consult the route when the GTU has multiple possibilities on a node, so the route does not have to
* be complete. As long as all 'splitting' nodes are part of the route and have a valid successor node (connected by a Link),
* the strategical planner is able to make a plan.
*
* Copyright (c) 2013-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License.
*
* $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
* initial version Nov 26, 2015
* @author Alexander Verbraeck
* @author Peter Knoppers
*/
public class LaneBasedStrategicalRoutePlanner extends AbstractLaneBasedStrategicalPlanner
implements LaneBasedStrategicalPlanner, Serializable
{
/** */
private static final long serialVersionUID = 20150724L;
/** The route to drive. */
private Route route;
/** Origin node. */
private final NodeInterface origin;
/** Destination node. */
private final NodeInterface destination;
/** The fixed tactical planner to use for the GTU. */
private final LaneBasedTacticalPlanner fixedTacticalPlanner;
/** Route supplier. */
private final RouteGeneratorOD routeGenerator;
/**
* Constructor for a strategical planner without route. This can only be used if the network does not have splits, or split
* fractions are used.
* @param fixedTacticalPlanner LaneBasedTacticalPlanner; the tactical planner to use for the GTU
* @param gtu InternalLaneBasedGtu; GTU
* @throws GtuException if fixed tactical planner == null
*/
public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final InternalLaneBasedGtu gtu)
throws GtuException
{
this(fixedTacticalPlanner, null, gtu, null, null, RouteGeneratorOD.NULL);
}
/**
* Constructor for a strategical planner with route.
* @param fixedTacticalPlanner LaneBasedTacticalPlanner; the tactical planner to use for the GTU
* @param route Route; the route to drive
* @param gtu InternalLaneBasedGtu; GTU
* @param origin Node; origin node
* @param destination Node; destination node
* @throws GtuException if fixed tactical planner == null
*/
public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final Route route,
final InternalLaneBasedGtu gtu, final NodeInterface origin, final NodeInterface destination) throws GtuException
{
this(fixedTacticalPlanner, route, gtu, origin, destination, RouteGeneratorOD.NULL);
}
/**
* Constructor for a strategical planner with route generator.
* @param fixedTacticalPlanner LaneBasedTacticalPlanner; the tactical planner to use for the GTU
* @param gtu InternalLaneBasedGtu; GTU
* @param origin Node; origin node
* @param destination Node; destination node
* @param routeGenerator RouteGeneratorOD; route generator
* @throws GtuException if fixed tactical planner == null
*/
public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final InternalLaneBasedGtu gtu,
final NodeInterface origin, final NodeInterface destination, final RouteGeneratorOD routeGenerator)
throws GtuException
{
this(fixedTacticalPlanner, null, gtu, origin, destination, routeGenerator);
}
/**
* Constructor for a strategical planner with route. If the route is {@code null}, a shortest path to the destination is
* derived.
* @param fixedTacticalPlanner LaneBasedTacticalPlanner; the tactical planner to use for the GTU
* @param route Route; the route to drive
* @param gtu InternalLaneBasedGtu; GTU
* @param origin Node; origin node
* @param destination Node; destination node
* @param routeGenerator RouteGeneratorOD; route generator
* @throws GtuException if fixed tactical planner == null
*/
public LaneBasedStrategicalRoutePlanner(final LaneBasedTacticalPlanner fixedTacticalPlanner, final Route route,
final InternalLaneBasedGtu gtu, final NodeInterface origin, final NodeInterface destination,
final RouteGeneratorOD routeGenerator) throws GtuException
{
super(gtu);
this.route = route;
this.origin = origin;
this.destination = destination;
this.fixedTacticalPlanner = fixedTacticalPlanner;
Throw.when(fixedTacticalPlanner == null, GtuException.class,
"Fixed Tactical Planner for a Strategical planner is null");
this.routeGenerator = routeGenerator;
}
/** {@inheritDoc} */
@Override
public final LaneBasedTacticalPlanner getTacticalPlanner()
{
return this.fixedTacticalPlanner;
}
/** {@inheritDoc} */
@Override
public LaneBasedTacticalPlanner getTacticalPlanner(final Time time)
{
return this.fixedTacticalPlanner; // fixed anyway
}
/** {@inheritDoc} */
@Override
public final NodeInterface nextNode(final LinkInterface link, final GtuType gtuType) throws NetworkException
{
assureRoute(gtuType);
return nextLink(link, gtuType).getEndNode();
}
/** {@inheritDoc} */
@Override
public final LinkInterface nextLink(final LinkInterface link, final GtuType gtuType) throws NetworkException
{
assureRoute(gtuType);
NodeInterface nextNode = link.getEndNode();
if ((null != this.route) && (!this.route.contains(nextNode)))
{
link.getSimulator().getLogger().always().warn("nextNode {} is not in route {}", nextNode, this.route);
NodeInterface prevNode = link.getStartNode();
link.getSimulator().getLogger().always().warn(" other node of link is {}", prevNode);
int index = 0;
for (NodeInterface node : this.route.getNodes())
{
link.getSimulator().getLogger().always().warn("{} {}{}", index, node.equals(prevNode) ? "--->" : " ", node);
index++;
}
}
return nextLink(nextNode, link, gtuType);
}
/** {@inheritDoc} */
@Override
public final NodeInterface nextNode(final NodeInterface node, final LinkInterface previousLink, final GtuType gtuType)
throws NetworkException
{
assureRoute(gtuType);
LinkInterface link = nextLink(node, previousLink, gtuType);
return link.getEndNode();
}
/** {@inheritDoc} */
@Override
public final LinkInterface nextLink(final NodeInterface node, final LinkInterface previousLink, final GtuType gtuType)
throws NetworkException
{
assureRoute(gtuType);
// if there is no split, don't ask the route
if (node.getOutgoingLinks().size() == 0 && previousLink != null)
{
// end node
throw new NetworkException(
"LaneBasedStrategicalRoutePlanner is asked for a next link, but node " + node + " has no successors");
}
if (node.getOutgoingLinks().size() == 1 && previousLink == null)
{
// start node
return node.getOutgoingLinks().iterator().next();
}
if (node.getOutgoingLinks().size() == 2)
{
for (LinkInterface link : node.getOutgoingLinks())
{
if (!link.equals(previousLink))
{
return link;
}
}
}
// if we only have one way to go, don't bother about the route yet
Set links = node.getOutgoingLinks().toSet();
for (Iterator linkIterator = links.iterator(); linkIterator.hasNext();)
{
LinkInterface link = linkIterator.next();
if (link.equals(previousLink))
{
// No u-turn...
linkIterator.remove();
}
else
{
// does the link forbid us to go in?
if ((!link.getLinkType().isCompatible(gtuType)))
{
linkIterator.remove();
}
else
{
// are there no lanes from the node into this link in the outgoing direction?
boolean out = false;
CrossSectionLink csLink = (CrossSectionLink) link;
// TODO: Is there a reason not to iterate over csLink.getLanes()?
for (CrossSectionElement cse : csLink.getCrossSectionElementList())
{
if (cse instanceof Lane)
{
Lane lane = (Lane) cse;
if ((link.getStartNode().equals(node) && lane.getLaneType().isCompatible(gtuType))
|| (link.getEndNode().equals(node) && lane.getLaneType().isCompatible(gtuType)))
{
out = true;
}
}
}
if (!out)
{
linkIterator.remove();
}
}
}
}
if (links.size() == 1)
{
return links.iterator().next();
}
// more than 2 links... We have to check the route!
if (getRoute() == null)
{
throw new NetworkException("LaneBasedStrategicalRoutePlanner does not have a route");
}
int i = this.route.getNodes().indexOf(node);
if (i == -1)
{
throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from " + previousLink
+ ", but node " + node + " not in route " + this.route);
}
if (i == this.route.getNodes().size() - 1)
{
throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from " + previousLink
+ ", but the GTU reached the last node for route " + this.route);
}
NodeInterface nextNode = this.route.getNode(i + 1);
LinkInterface result = null;
for (LinkInterface link : links)
{
// TODO this takes the first in the set of links that connects the correct nodes; does not handle parallel links
// consistently
LinkInterface ld = null;
if (link.getStartNode().equals(nextNode) && link.getEndNode().equals(node))
{
ld = link;
}
if (link.getEndNode().equals(nextNode) && link.getStartNode().equals(node))
{
ld = link;
}
if (null != result && null != ld)
{
throw new NetworkException("Cannot choose among multiple links from " + node + " to " + nextNode);
}
else if (null == result)
{
result = ld;
}
}
if (null == result)
{
throw new NetworkException("LaneBasedStrategicalRoutePlanner is asked for a next link coming from "
+ previousLink.getId() + ", but no link could be found connecting node " + node + " and node " + nextNode
+ " for route " + this.route);
}
return result;
}
/** {@inheritDoc} */
@Override
public final Route getRoute()
{
assureRoute(getEgoGtu().getGTUType());
if (this.route == null && this.destination != null)
{
try
{
LanePosition pos = getEgoGtu().getReferencePosition();
CrossSectionLink link = pos.getLane().getParentLink();
NodeInterface from = link.getStartNode();
if (this.routeGenerator != null)
{
this.route = this.routeGenerator.getRoute(from, this.destination, getEgoGtu().getGTUType());
}
if (this.route == null)
{
this.route = link.getNetwork().getShortestRouteBetween(getEgoGtu().getGTUType(), from, this.destination);
}
}
catch (GtuException | NetworkException exception)
{
throw new RuntimeException("Route could not be determined.", exception);
}
}
return this.route;
}
/**
* Assures a route is available if a route is already present, or a destination and route supplier are provided.
* @param gtuType GtuType; the type of the GTU for which a route must be assured
*/
private void assureRoute(final GtuType gtuType)
{
if (this.route == null && this.destination != null && !this.routeGenerator.equals(RouteGeneratorOD.NULL))
{
LanePosition ref =
Try.assign(() -> getEgoGtu().getReferencePosition(), "Could not retrieve GTU reference position.");
List nodes = new ArrayList<>();
if (this.origin != null)
{
nodes.addAll(this.routeGenerator.getRoute(this.origin, ref.getLane().getParentLink().getStartNode(), gtuType)
.getNodes());
}
else
{
nodes.add(ref.getLane().getParentLink().getStartNode());
}
Route newRoute =
this.routeGenerator.getRoute(ref.getLane().getParentLink().getEndNode(), this.destination, gtuType);
if (null == newRoute)
{
System.err.println("this.routeGenerator.getRoute() returned null");
throw new RuntimeException("getRoute failed");
}
List newNodes = newRoute.getNodes();
if (newNodes == null)
{
System.err.println("Route.getNodes() returned null");
newRoute.getNodes();
}
nodes.addAll(newNodes);
this.route =
Try.assign(
() -> new CompleteRoute("Route for " + gtuType + " from " + this.origin + "to " + this.destination
+ " via " + ref.getLane().getParentLink(), gtuType, nodes),
"No route possible over nodes %s", nodes);
// System.out.println("RouteSupplier route for GTU " + getGtu().getId() + ": " + this.route);
}
}
/** {@inheritDoc} */
@Override
public final NodeInterface getOrigin()
{
return this.origin;
}
/** {@inheritDoc} */
@Override
public final NodeInterface getDestination()
{
return this.destination;
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "LaneBasedStrategicalRoutePlanner [route=" + this.route + ", fixedTacticalPlanner=" + this.fixedTacticalPlanner
+ "]";
}
}