package org.opentrafficsim.road.gtu.lane.tactical; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.djunits.value.vdouble.scalar.Length; import org.djutils.draw.DrawRuntimeException; import org.djutils.draw.line.PolyLine3d; import org.djutils.immutablecollections.ImmutableSet; import org.opentrafficsim.base.OTSClassUtil; import org.opentrafficsim.base.parameters.ParameterTypeClass; import org.opentrafficsim.base.parameters.ParameterTypeDuration; import org.opentrafficsim.base.parameters.ParameterTypeLength; import org.opentrafficsim.base.parameters.ParameterTypes; import org.opentrafficsim.core.gtu.GtuException; import org.opentrafficsim.core.network.LateralDirectionality; import org.opentrafficsim.core.network.LinkInterface; import org.opentrafficsim.core.network.NetworkException; import org.opentrafficsim.core.network.NodeInterface; import org.opentrafficsim.road.gtu.lane.InternalLaneBasedGtu; import org.opentrafficsim.road.gtu.lane.perception.LanePerception; import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel; import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LMRS; 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; /** * A lane-based tactical planner generates an operational plan for the lane-based GTU. It can ask the strategic planner for * assistance on the route to take when the network splits. This abstract class contains a number of helper methods that make it * easy to implement a tactical planner. *

* 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 25, 2015
* @author Alexander Verbraeck * @author Peter Knoppers */ public abstract class AbstractLaneBasedTacticalPlanner implements LaneBasedTacticalPlanner, Serializable { /** Tactical planner parameter. */ public static final ParameterTypeClass TACTICAL_PLANNER; static { Class type = LaneBasedTacticalPlanner.class; TACTICAL_PLANNER = new ParameterTypeClass<>("tactical planner", "Tactical planner class.", OTSClassUtil.getTypedClass(type), LMRS.class); } /** */ private static final long serialVersionUID = 20151125L; /** Look ahead parameter type. */ protected static final ParameterTypeLength LOOKAHEAD = ParameterTypes.LOOKAHEAD; /** Time step parameter type. */ protected static final ParameterTypeDuration DT = ParameterTypes.DT; /** The car-following model. */ private CarFollowingModel carFollowingModel; /** The perception. */ private final LanePerception lanePerception; /** GTU. */ private final InternalLaneBasedGtu gtu; /** * Instantiates a tactical planner. * @param carFollowingModel CarFollowingModel; car-following model * @param gtu InternalLaneBasedGtu; GTU * @param lanePerception LanePerception; perception */ public AbstractLaneBasedTacticalPlanner(final CarFollowingModel carFollowingModel, final InternalLaneBasedGtu gtu, final LanePerception lanePerception) { setCarFollowingModel(carFollowingModel); this.gtu = gtu; this.lanePerception = lanePerception; } /** {@inheritDoc} */ @Override public final InternalLaneBasedGtu getEgoGtu() { return this.gtu; } /** {@inheritDoc} */ @Override public LanePathInfo buildLanePathInfo(final Length maxHeadway) throws GtuException, NetworkException { LanePosition dlp = this.gtu.getReferencePosition(); return buildLanePathInfo(maxHeadway, dlp.getLane(), dlp.getPosition()); } /** {@inheritDoc} */ @Override public LanePathInfo buildLanePathInfo(final Length maxHeadway, final Lane startLane, final Length position) throws GtuException, NetworkException { List laneListForward = new ArrayList<>(); Lane lane = startLane; Length startPosition = position; Lane lastLane = lane; laneListForward.add(lastLane); Length distanceToEndOfLane; PolyLine3d path; try { distanceToEndOfLane = lane.getLength().minus(position); path = lane.getCenterLine().extract(position.si, lane.getLength().si); } catch (DrawRuntimeException exception) { // System.err.println(gtu + ": " + exception.getMessage()); // System.err.println(lane + ", len=" + lane.getLength()); // System.err.println(position); // throw new GTUException(exception); // section on current lane too short, floating point operations cause only a single point at the end of the lane path = null; distanceToEndOfLane = Length.ZERO; laneListForward.clear(); startPosition = Length.ZERO; } while (distanceToEndOfLane.lt(maxHeadway)) { ImmutableSet lanes = lane.nextLanes(this.gtu.getGTUType()); if (lanes.size() == 0) { // Dead end. Return with the list as is. return new LanePathInfo(path, laneListForward, startPosition); } else if (lanes.size() == 1) { // Ask the strategical planner what the next link should be (if known), because the strategical planner knows // best! LinkInterface ld = this.gtu.getStrategicalPlanner().nextLink(lane.getParentLink(), this.gtu.getGTUType()); lane = lanes.iterator().next(); if (ld != null && !lane.getParentLink().equals(ld)) { // Lane not on route anymore. return with the list as is. return new LanePathInfo(path, laneListForward, startPosition); } } else { // Multiple next lanes; ask the strategical planner where to go. // Note: this is not necessarily a split; it could e.g. be a bike path on a road LinkInterface ld; try { ld = this.gtu.getStrategicalPlanner().nextLink(lane.getParentLink(), this.gtu.getGTUType()); } catch (NetworkException ne) { // no route found. // return the data structure up to this point... return new LanePathInfo(path, laneListForward, startPosition); } LinkInterface nextLink = ld; Lane newLane = null; for (Lane nextLane : lanes) { if (nextLane.getParentLink().equals(nextLink)) { newLane = nextLane; break; } } if (newLane == null) { // we cannot reach the next node on this lane -- we have to make a lane change! // return the data structure up to this point... return new LanePathInfo(path, laneListForward, startPosition); } // otherwise: continue! lane = newLane; } path = concatenateNull(path, lane.getCenterLine()); // path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lane.getCenterLine()); lastLane = lane; laneListForward.add(lastLane); distanceToEndOfLane = distanceToEndOfLane.plus(lastLane.getLength()); } return new LanePathInfo(path, laneListForward, startPosition); } /** * Concatenate two paths, where the first may be {@code null}. * @param path PolyLine3d; path, may be {@code null} * @param centerLine PolyLine3d; center line of lane to add * @return concatenated line */ public static PolyLine3d concatenateNull(final PolyLine3d path, final PolyLine3d centerLine) { if (path == null) { return centerLine; } return PolyLine3d.concatenate(Lane.MARGIN.si, path, centerLine); } /** {@inheritDoc} */ @Override public NextSplitInfo determineNextSplit(final Length maxHeadway) throws GtuException, NetworkException { NodeInterface nextSplitNode = null; Set correctCurrentLanes = new LinkedHashSet<>(); LanePosition dlp = this.gtu.getReferencePosition(); Lane referenceLane = dlp.getLane(); double refFrac = dlp.getPosition().si / referenceLane.getLength().si; LinkInterface lastLink = referenceLane.getParentLink(); Length lengthForward; Length position = dlp.getPosition(); NodeInterface lastNode; lengthForward = referenceLane.getLength().minus(position); lastNode = referenceLane.getParentLink().getEndNode(); // see if we have a split within maxHeadway distance while (lengthForward.lt(maxHeadway) && nextSplitNode == null) { // calculate the number of "outgoing" links Set links = lastNode.getOutgoingLinks().toSet(); // safe copy // calculate the number of incoming and outgoing lanes on the link boolean laneChange = false; if (links.size() == 1) { for (CrossSectionElement cse : ((CrossSectionLink) lastLink).getCrossSectionElementList()) { if (cse instanceof Lane) { Lane lane = (Lane) cse; if (lane.nextLanes(this.gtu.getGTUType()).size() == 0) { laneChange = true; } } } } // see if we have a lane drop if (laneChange) { nextSplitNode = lastNode; // which lane(s) we are registered on and adjacent lanes link to a lane // that does not drop? for (CrossSectionElement cse : referenceLane.getParentLink().getCrossSectionElementList()) { if (cse instanceof Lane) { Lane l = (Lane) cse; // if (noLaneDrop(gtu, maxHeadway, l, position, referenceLaneDirectionality)) if (noLaneDrop(maxHeadway, l, l.getLength().times(refFrac))) { correctCurrentLanes.add(l); } } } return new NextSplitInfo(nextSplitNode, correctCurrentLanes); } // see if we have a split if (links.size() > 1) { nextSplitNode = lastNode; LinkInterface ld = this.gtu.getStrategicalPlanner().nextLink(nextSplitNode, lastLink, this.gtu.getGTUType()); // which lane(s) we are registered on and adjacent lanes link to a lane // that is on the route at the next split? for (CrossSectionElement cse : referenceLane.getParentLink().getCrossSectionElementList()) { if (cse instanceof Lane) { Lane l = (Lane) cse; // if (connectsToPath(gtu, maxHeadway, l, position, referenceLaneDirectionality, ld.getLink())) if (connectsToPath(maxHeadway, l, l.getLength().times(refFrac), ld)) { correctCurrentLanes.add(l); } } } if (correctCurrentLanes.size() > 0) { return new NextSplitInfo(nextSplitNode, correctCurrentLanes); } // split, but no lane on current link to right direction Set correctLanes = new LinkedHashSet<>(); Set wrongLanes = new LinkedHashSet<>(); for (CrossSectionElement cse : ((CrossSectionLink) lastLink).getCrossSectionElementList()) { if (cse instanceof Lane) { Lane l = (Lane) cse; if (connectsToPath(maxHeadway.plus(l.getLength()), l, Length.ZERO, ld)) { correctLanes.add(l); } else { wrongLanes.add(l); } } } for (Lane wrongLane : wrongLanes) { for (Lane adjLane : wrongLane.accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, this.gtu.getGTUType())) { if (correctLanes.contains(adjLane)) { return new NextSplitInfo(nextSplitNode, correctCurrentLanes, LateralDirectionality.LEFT); } } for (Lane adjLane : wrongLane.accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, this.gtu.getGTUType())) { if (correctLanes.contains(adjLane)) { return new NextSplitInfo(nextSplitNode, correctCurrentLanes, LateralDirectionality.RIGHT); } } } return new NextSplitInfo(nextSplitNode, correctCurrentLanes, null); } if (links.size() == 0) { return new NextSplitInfo(null, correctCurrentLanes); } // just one link LinkInterface link = links.iterator().next(); // determine direction for the path lastNode = link.getEndNode(); lastLink = links.iterator().next(); lengthForward = lengthForward.plus(lastLink.getLength()); } return new NextSplitInfo(null, correctCurrentLanes); } /** * Determine whether the lane is directly connected to our route, in other words: if we would (continue to) drive on the * given lane, can we take the right branch at the nextSplitNode without switching lanes? * @param maxHeadway Length; the maximum length for use in the calculation * @param startLane Lane; the first lane in the list * @param startLanePosition Length; the position on the start lane * @param linkAfterSplit Link; the link after the split to which we should connect * @return boolean; true if the lane (XXXXX which lane?) is connected to our path * @throws GtuException when the vehicle is not on one of the lanes on which it is registered * @throws NetworkException when the strategic planner is not able to return a next node in the route */ protected boolean connectsToPath(final Length maxHeadway, final Lane startLane, final Length startLanePosition, final LinkInterface linkAfterSplit) throws GtuException, NetworkException { List lanes = buildLanePathInfo(maxHeadway, startLane, startLanePosition).getLaneList(); for (Lane lane : lanes) { if (lane.getParentLink().equals(linkAfterSplit)) { return true; } } return false; } /** * Determine whether the lane does not drop, in other words: if we would (continue to) drive on the given lane, can we * continue to drive at the nextSplitNode without switching lanes? * @param maxHeadway Length; the maximum length for use in the calculation * @param startLane Lane; the first lane in the list * @param startLanePosition Length; the position on the start lane * @return boolean; true if the lane (XXX which lane?) is connected to our path * @throws GtuException when the vehicle is not on one of the lanes on which it is registered * @throws NetworkException when the strategic planner is not able to return a next node in the route */ protected boolean noLaneDrop(final Length maxHeadway, final Lane startLane, final Length startLanePosition) throws GtuException, NetworkException { LanePathInfo lpi = buildLanePathInfo(maxHeadway, startLane, startLanePosition); return lpi.getPath().getLength() >= maxHeadway.si; } /** * Make a list of links on which to drive next, with a maximum headway relative to the reference point of the GTU. * @param maxHeadway Length; the maximum length for which links should be returned * @return List<LinkDirection>; a list of links on which to drive next * @throws GtuException when the vehicle is not on one of the lanes on which it is registered * @throws NetworkException when the strategic planner is not able to return a next node in the route */ protected List buildLinkListForward(final Length maxHeadway) throws GtuException, NetworkException { List linkList = new ArrayList<>(); LanePosition dlp = this.gtu.getReferencePosition(); Lane referenceLane = dlp.getLane(); LinkInterface lastLink = referenceLane.getParentLink(); linkList.add(lastLink); Length lengthForward; Length position = dlp.getPosition(); NodeInterface lastNode; lengthForward = referenceLane.getLength().minus(position); lastNode = referenceLane.getParentLink().getEndNode(); // see if we have a split within maxHeadway distance while (lengthForward.lt(maxHeadway)) { // calculate the number of "outgoing" links Set links = lastNode.getOutgoingLinks().toSet(); // is a safe copy Iterator linkIterator = links.iterator(); while (linkIterator.hasNext()) { LinkInterface link = linkIterator.next(); if (link.equals(lastLink) || !link.getLinkType().isCompatible(this.gtu.getGTUType())) { linkIterator.remove(); } } if (links.size() == 0) { return linkList; // the path stops here... } LinkInterface link; if (links.size() > 1) { link = this.gtu.getStrategicalPlanner().nextLink(lastLink, this.gtu.getGTUType()); } else { link = links.iterator().next(); } // determine direction for the path lastNode = lastLink.getEndNode(); lastLink = link; linkList.add(lastLink); lengthForward = lengthForward.plus(lastLink.getLength()); } return linkList; } /** {@inheritDoc} */ @Override public final CarFollowingModel getCarFollowingModel() { return this.carFollowingModel; } /** * Sets the car-following model. * @param carFollowingModel CarFollowingModel; Car-following model to set. */ public final void setCarFollowingModel(final CarFollowingModel carFollowingModel) { this.carFollowingModel = carFollowingModel; } /** {@inheritDoc} */ @Override public final LanePerception getPerception() { return this.lanePerception; } }