RollingLaneStructure.java
package org.opentrafficsim.road.gtu.lane.perception;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Time;
import org.djutils.event.EventInterface;
import org.djutils.event.EventListenerInterface;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.djutils.immutablecollections.ImmutableMap;
import org.opentrafficsim.core.gtu.GTUDirectionality;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.gtu.RelativePosition;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.network.route.Route;
import org.opentrafficsim.core.perception.Historical;
import org.opentrafficsim.core.perception.HistoricalValue;
import org.opentrafficsim.core.perception.HistoryManager;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;
import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.DirectedLanePosition;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LaneDirection;
import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
/**
* This data structure can clearly indicate the lane structure ahead of us, e.g. in the following situation:
*
* <pre>
* (---- a ----)(---- b ----)(---- c ----)(---- d ----)(---- e ----)(---- f ----)(---- g ----)
* __________ __________
* / _________ 1 / _________ 2
* / / / /
* __________/ / _______________________/ /
* 1 ____________ ____________ /_ _ _ _ _ _/____________ /_ _ _ _ _ _ _ _ _ _ _ _ /
* 0 |_ _X_ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ \____________
* -1 |____________|_ _ _ _ _ _ |____________|____________| __________|____________|____________| 3
* -2 / __________/ \ \
* ________/ / \ \___________
* 5 _________/ \____________ 4
* </pre>
*
* When the GTU is looking ahead, it needs to know that when it continues to destination 3, it needs to shift one lane to the
* right at some point, but <b>not</b> two lanes to the right in link b, and not later than at the end of link f. When it needs
* to go to destination 1, it needs to shift to the left in link c. When it has to go to destination 2, it has to shift to the
* left, but not earlier than at link e. At node [de], it is possible to leave the rightmost lane of link e, and go to
* destination 4. The rightmost lane just splits into two lanes at the end of link d, and the GTU can either continue driving to
* destination 3, turn right to destination 4. This means that the right lane of link d has <b>two</b> successor lanes.
* <p>
* In the data structures, lanes are numbered laterally. Suppose that the lane where vehicle X resides would be number 0.
* Consistent with "left is positive" for angles, the lane right of X would have number -1, and entry 5 would have number -2.
* <p>
* In the data structure, this can be indicated as follows (N = next, P = previous, L = left, R = right, D = lane drop, . =
* continued but not in this structure). The merge lane in b is considered "off limits" for the GTUs on the "main" lane -1; the
* "main" lane 0 is considered off limits from the exit lanes on c, e, and f. Still, we need to maintain pointers to these
* lanes, as we are interested in the GTUs potentially driving next to us, feeding into our lane, etc.
*
* <pre>
* 1 0 -1 -2
*
* ROOT
* _____|_____ ___________ ___________
* |_-_|_._|_R_|----|_L_|_._|_-_| |_-_|_._|_-_| a
* | | |
* _____V_____ _____V_____ _____V_____
* |_-_|_N_|_R_|----|_L_|_N_|_R_|<---|_L_|_D_|_-_| b
* | |
* ___________ _____V_____ _____V_____
* |_-_|_N_|_R_|<---|_L_|_N_|_R_|----|_L_|_N_|_-_| c
* | | |
* _____V_____ _____V_____ _____V_____
* |_-_|_._|_-_| |_-_|_N_|_R_|----|_L_|_NN|_-_| d
* | ||_______________
* ___________ _____V_____ _____V_____ _____V_____
* |_-_|_N_|_R_|<---|_L_|_N_|_R_|----|_L_|_N_|_-_| |_-_|_N_|_-_| e
* | | | |
* _____V_____ _____V_____ _____V_____ _____V_____
* |_-_|_N_|_R_|<---|_L_|_D_|_R_|----|_L_|_N_|_-_| |_-_|_._|_-_| f
* | |
* _____V_____ _____V_____
* |_-_|_._|_-_| |_-_|_._|_-_| g
* </pre>
* <p>
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
* initial version Feb 20, 2016 <br>
* @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
*/
public class RollingLaneStructure implements LaneStructure, Serializable, EventListenerInterface
{
/** */
private static final long serialVersionUID = 20160400L;
/** The lanes from which we observe the situation. */
private final Historical<RollingLaneStructureRecord> root;
/** Look ahead distance. */
private Length lookAhead;
/** Route the structure is based on. */
private Route previousRoute;
/** Whether the previous plan was deviative. */
private boolean previouslyDeviative = false;
/** Lane structure records of the cross section. */
private TreeMap<RelativeLane, RollingLaneStructureRecord> crossSectionRecords = new TreeMap<>();
/** First lane structure records. */
private TreeMap<RelativeLane, RollingLaneStructureRecord> firstRecords = new TreeMap<>();
/** Lane structure records grouped per relative lane. */
private Map<RelativeLane, Set<RollingLaneStructureRecord>> relativeLaneMap = new LinkedHashMap<>();
/** Relative lanes storage per record, such that other records can be linked to the correct relative lane. */
private Map<LaneStructureRecord, RelativeLane> relativeLanes = new LinkedHashMap<>();
/** Set of lanes that can be ignored as they are beyond build bounds. */
private final Set<Lane> ignoreSet = new LinkedHashSet<>();
/** Upstream edges. */
private final Set<RollingLaneStructureRecord> upstreamEdge = new LinkedHashSet<>();
/** Downstream edges. */
private final Set<RollingLaneStructureRecord> downstreamEdge = new LinkedHashSet<>();
/** Downstream distance over which the structure is made. */
private final Length down;
/** Upstream distance over which the structure is made. */
private final Length up;
/** Downstream distance at splits (links not on route) included in the structure. */
private final Length downSplit;
/** Upstream distance at downstream merges (links not on route) included in the structure. */
private final Length upMerge;
/** GTU. */
private final LaneBasedGTU containingGtu;
/** the animation access. */
@SuppressWarnings("checkstyle:visibilitymodifier")
public AnimationAccess animationAccess = new AnimationAccess();
/**
* Constructor.
* @param lookAhead Length; distance over which visual objects are included
* @param down Length; downstream distance over which the structure is made
* @param up Length; upstream distance over which the structure is made, should include a margin for reaction time
* @param downSplit Length; downstream distance at splits (links not on route) included in the structure
* @param upMerge Length; upstream distance at downstream merges (links not on route) included in the structure
* @param gtu LaneBasedGTU; GTU
*/
public RollingLaneStructure(final Length lookAhead, final Length down, final Length up, final Length downSplit,
final Length upMerge, final LaneBasedGTU gtu)
{
HistoryManager historyManager = gtu.getSimulator().getReplication().getHistoryManager(gtu.getSimulator());
this.root = new HistoricalValue<>(historyManager);
this.lookAhead = lookAhead;
this.down = down;
this.up = up;
this.downSplit = downSplit;
this.upMerge = upMerge;
this.containingGtu = gtu;
try
{
gtu.addListener(this, LaneBasedGTU.LANE_CHANGE_EVENT);
}
catch (RemoteException exception)
{
throw new RuntimeException(exception);
}
}
/**
* Updates the underlying structure shifting the root position to the input.
* @param pos DirectedLanePosition; current position of the GTU
* @param route Route; current route of the GTU
* @param gtuType GTUType; GTU type
* @throws GTUException on a problem while updating the structure
*/
@Override
public final void update(final DirectedLanePosition pos, final Route route, final GTUType gtuType) throws GTUException
{
/*
* Implementation note: the LaneStructure was previously generated by AbstractLanePerception every time step. This
* functionality has been moved to LaneStructure itself, in a manner that can update the LaneStructure. Start distances
* of individual records are therefore made dynamic, calculated relative to a neighboring source record. For many time
* steps this means that only these distances have to be updated. In other cases, the sources for start distances are
* changed concerning the records that were involved in the previous time step. The LaneStructure now also maintains an
* upstream and a downstream edge, i.e. set of records. These are moved forward as the GTU moves.
*/
// fractional position
Lane lane = pos.getLane();
GTUDirectionality direction = pos.getGtuDirection();
Length position = pos.getPosition();
double fracPos = direction.isPlus() ? position.si / lane.getLength().si : 1.0 - position.si / lane.getLength().si;
boolean deviative = this.containingGtu.getOperationalPlan() instanceof LaneBasedOperationalPlan
&& ((LaneBasedOperationalPlan) this.containingGtu.getOperationalPlan()).isDeviative();
// TODO on complex networks, e.g. with sections connectors where lane changes are not possible, the update may fail
if (this.previousRoute != route || this.root.get() == null || deviative != this.previouslyDeviative)
{
// create new LaneStructure
this.previousRoute = route;
// clear
this.upstreamEdge.clear();
this.downstreamEdge.clear();
this.crossSectionRecords.clear();
this.relativeLanes.clear();
this.relativeLaneMap.clear();
this.firstRecords.clear();
// build cross-section
RollingLaneStructureRecord newRoot = constructRecord(lane, direction, null, RecordLink.CROSS,
RelativeLane.CURRENT);
this.root.set(newRoot);
this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
RollingLaneStructureRecord current = newRoot;
RelativeLane relativeLane = RelativeLane.CURRENT;
Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current
.getDirection());
while (!adjacentLanes.isEmpty())
{
Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
"Multiple adjacent lanes encountered during construction of lane map.");
relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
Lane adjacentLane = adjacentLanes.iterator().next();
RollingLaneStructureRecord adjacentRecord = constructRecord(adjacentLane, direction, current,
RecordLink.CROSS, relativeLane);
this.crossSectionRecords.put(relativeLane, adjacentRecord);
if (latDirection.isLeft())
{
if (adjacentLane.accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, current
.getDirection()).contains(current.getLane()))
{
adjacentRecord.setRight(current, gtuType);
}
current.setLeft(adjacentRecord, gtuType);
}
else
{
if (adjacentLane.accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, current
.getDirection()).contains(current.getLane()))
{
adjacentRecord.setLeft(current, gtuType);
}
current.setRight(adjacentRecord, gtuType);
}
current = adjacentRecord;
adjacentLanes = current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current
.getDirection());
}
}
this.upstreamEdge.addAll(this.crossSectionRecords.values());
this.downstreamEdge.addAll(this.crossSectionRecords.values());
this.firstRecords.putAll(this.crossSectionRecords);
// set the start distances so the upstream expand can work
newRoot.updateStartDistance(fracPos, this);
// expand upstream edge
expandUpstreamEdge(gtuType, fracPos);
// derive first records
deriveFirstRecords();
}
else
{
// update LaneStructure
RollingLaneStructureRecord newRoot = this.root.get();
if (!lane.equals(newRoot.getLane()))
{
// find the root, and possible lateral shift if changed lane
newRoot = null;
RelativeLane lateralMove = null;
double closest = Double.POSITIVE_INFINITY;
for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
{
for (RollingLaneStructureRecord record : this.relativeLaneMap.get(relativeLane))
{
if (record.getLane().equals(lane) && record.getStartDistance().si < closest && record
.getStartDistance().si + record.getLength().si > 0.0)
{
newRoot = record;
lateralMove = relativeLane;
// multiple records may be present for the current lane due to a loop
closest = record.getStartDistance().si;
}
}
if (newRoot != null)
{
break;
}
}
// newRoot.getPrev().contains(newRoot.getStartDistanceSource())
this.root.set(newRoot);
// update start distance sources
updateStartDistanceSources();
// shift if changed lane
if (!lateralMove.isCurrent())
{
RelativeLane delta = new RelativeLane(lateralMove.getLateralDirectionality().flip(), lateralMove
.getNumLanes());
TreeMap<RelativeLane, Set<RollingLaneStructureRecord>> newRelativeLaneMap = new TreeMap<>();
for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
{
RelativeLane newRelativeLane = relativeLane.add(delta);
newRelativeLaneMap.put(newRelativeLane, this.relativeLaneMap.get(relativeLane));
}
this.relativeLaneMap = newRelativeLaneMap;
Map<LaneStructureRecord, RelativeLane> newRelativeLanes = new LinkedHashMap<>();
for (LaneStructureRecord record : this.relativeLanes.keySet())
{
newRelativeLanes.put(record, this.relativeLanes.get(record).add(delta));
}
this.relativeLanes = newRelativeLanes;
}
this.crossSectionRecords.clear();
this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
RollingLaneStructureRecord record = newRoot;
RollingLaneStructureRecord next = newRoot;
RelativeLane delta = new RelativeLane(latDirection, 1);
RelativeLane relLane = RelativeLane.CURRENT;
while (next != null)
{
next = latDirection.isLeft() ? record.getLeft() : record.getRight();
if (next != null)
{
next.changeStartDistanceSource(record, RecordLink.CROSS);
relLane = relLane.add(delta);
this.crossSectionRecords.put(relLane, next);
record = next;
}
}
}
}
newRoot.updateStartDistance(fracPos, this);
// update upstream edges
retreatUpstreamEdge();
// derive first records
deriveFirstRecords();
}
this.previouslyDeviative = deviative;
// update downstream edges
expandDownstreamEdge(gtuType, fracPos, route);
}
/**
* Derives the first downstream records so the extended cross-section can be returned.
*/
private void deriveFirstRecords()
{
this.firstRecords.clear();
this.firstRecords.putAll(this.crossSectionRecords);
for (RelativeLane lane : this.relativeLaneMap.keySet())
{
getFirstRecord(lane); // store non-null values internally
}
}
/**
* Upstream algorithm from the new source, where records along the way are assigned a new start distance source. These
* records were downstream in the previous time step, but are now upstream. Hence, their start distance source should now
* become their downstream record. This algorithm acts like an upstream tree, where each branch is stopped if there are no
* upstream records, or the upstream records already have their downstream record as source (i.e. the record was already
* upstream in the previous time step).
*/
private void updateStartDistanceSources()
{
// initial cross section
Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
RollingLaneStructureRecord rootRecord = this.root.get();
set.add(rootRecord);
rootRecord.changeStartDistanceSource(null, RecordLink.CROSS);
RollingLaneStructureRecord prev = rootRecord;
RollingLaneStructureRecord next = prev.getLeft();
while (next != null)
{
set.add(next);
next.changeStartDistanceSource(prev, RecordLink.CROSS);
prev = next;
next = next.getLeft();
}
prev = rootRecord;
next = prev.getRight();
while (next != null)
{
set.add(next);
next.changeStartDistanceSource(prev, RecordLink.CROSS);
prev = next;
next = next.getRight();
}
// tree algorithm (branches flattened to a single set)
while (!set.isEmpty())
{
// lateral
Set<RollingLaneStructureRecord> newSet = new LinkedHashSet<>();
for (RollingLaneStructureRecord record : set)
{
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
prev = record;
next = latDirection.isLeft() ? record.getLeft() : record.getRight();
while (next != null && !set.contains(next))
{
next.changeStartDistanceSource(prev, RecordLink.LATERAL_END);
removeDownstream(next, latDirection.flip()); // split not taken can be thrown away
newSet.add(next);
prev = next;
next = latDirection.isLeft() ? next.getLeft() : next.getRight();
}
}
}
set.addAll(newSet);
// longitudinal
newSet.clear();
for (RollingLaneStructureRecord record : set)
{
for (RollingLaneStructureRecord prevRecord : record.getPrev())
{
Iterator<RollingLaneStructureRecord> it = prevRecord.getNext().iterator();
while (it.hasNext())
{
RollingLaneStructureRecord otherDown = it.next();
if (!otherDown.getLane().getParentLink().equals(record.getLane().getParentLink()))
{
// split not taken can be thrown away
otherDown.changeStartDistanceSource(null, null);
// this can throw away records that are laterally connected as they later merge... ??
removeDownstream(otherDown, LateralDirectionality.NONE);
removeRecord(otherDown);
it.remove();
}
}
LaneStructureRecord source = prevRecord.getStartDistanceSource();
if (source == null || (source != null && !source.equals(record)))
{
prevRecord.changeStartDistanceSource(record, RecordLink.UP);
newSet.add(prevRecord);
}
}
}
// next loop
set = newSet;
}
}
/**
* Removes all records downstream of the given record from underlying data structures.
* @param record RollingLaneStructureRecord; record, downstream of which to remove all records
* @param lat LateralDirectionality; records with an adjacent record at this side are not deleted
*/
private void removeDownstream(final RollingLaneStructureRecord record, final LateralDirectionality lat)
{
for (RollingLaneStructureRecord next : record.getNext())
{
RollingLaneStructureRecord adj = lat.isLeft() ? next.getLeft() : lat.isRight() ? next.getRight() : null;
if (adj == null)
{
next.changeStartDistanceSource(null, null);
removeDownstream(next, lat);
removeRecord(next);
}
}
record.clearNextList();
}
/**
* Removes the record from underlying data structures.
* @param record RollingLaneStructureRecord; record to remove
*/
private void removeRecord(final RollingLaneStructureRecord record)
{
RelativeLane lane = this.relativeLanes.get(record);
if (lane != null)
{
this.relativeLaneMap.get(lane).remove(record);
this.relativeLanes.remove(record);
}
record.changeStartDistanceSource(null, null);
}
/**
* On a new build, this method is used to create the upstream map.
* @param gtuType GTUType; GTU type
* @param fractionalPosition double; fractional position on reference link
* @throws GTUException on exception
*/
private void expandUpstreamEdge(final GTUType gtuType, final double fractionalPosition) throws GTUException
{
this.ignoreSet.clear();
for (LaneStructureRecord record : this.upstreamEdge)
{
this.ignoreSet.add(record.getLane());
}
Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
boolean expand = true;
while (expand)
{
expand = false;
// longitudinal
Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.upstreamEdge);
while (iterator.hasNext())
{
RollingLaneStructureRecord prev = iterator.next();
ImmutableMap<Lane, GTUDirectionality> nexts = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
if (prev.getStartDistance().si < this.up.si)
{
// upstream search ends on this lane
prev.setCutOffStart(this.up.minus(prev.getStartDistance()));
for (Lane prevLane : nexts.keySet())
{
this.ignoreSet.add(prevLane); // exclude in lateral search
}
}
else
{
// upstream search goes further upstream
prev.clearCutOffStart();
iterator.remove();
if (!nexts.isEmpty())
{
for (Lane prevLane : nexts.keySet())
{
RelativeLane relativeLane = this.relativeLanes.get(prev);
RollingLaneStructureRecord next = constructRecord(prevLane, nexts.get(prevLane), prev,
RecordLink.UP, relativeLane);
this.ignoreSet.add(prevLane);
next.updateStartDistance(fractionalPosition, this);
connectLaterally(next, gtuType, modifiedEdge);
next.addNext(prev);
prev.addPrev(next);
nextSet.add(next);
modifiedEdge.add(next);
}
}
}
}
this.upstreamEdge.addAll(nextSet);
expand |= !nextSet.isEmpty();
nextSet.clear();
// lateral
Set<RollingLaneStructureRecord> lateralSet = expandLateral(this.upstreamEdge, RecordLink.LATERAL_END, gtuType,
fractionalPosition);
nextSet.addAll(lateralSet);
// next iteration
this.upstreamEdge.addAll(nextSet);
expand |= !nextSet.isEmpty();
nextSet.clear();
}
}
/**
* Helper method for upstream and downstream expansion. This method returns all lanes that can be laterally found from the
* input set.
* @param edge Set<RollingLaneStructureRecord>; input set
* @param recordLink RecordLink; link to add between lateral records, depends on upstream or downstream search
* @param gtuType GTUType; GTU type
* @param fractionalPosition double; fractional position on reference link
* @return Set<LaneStructureRecord>; output set with all laterally found lanes
*/
private Set<RollingLaneStructureRecord> expandLateral(final Set<RollingLaneStructureRecord> edge,
final RecordLink recordLink, final GTUType gtuType, final double fractionalPosition)
{
Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
Set<Lane> laneSet = new LinkedHashSet<>(); // set to check that an adjacent lane is not another lane already in the set
for (LaneStructureRecord record : edge)
{
laneSet.add(record.getLane());
}
Iterator<RollingLaneStructureRecord> iterator = edge.iterator();
while (iterator.hasNext())
{
RollingLaneStructureRecord record = iterator.next();
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
if (record.getRight() != null && latDirection.isRight() || record.getLeft() != null && latDirection.isLeft())
{
// skip if there already is a record on that side
continue;
}
RelativeLane relativeLane = this.relativeLanes.get(record);
RollingLaneStructureRecord prev = record;
Set<Lane> adjacentLanes = prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev
.getDirection());
while (!adjacentLanes.isEmpty())
{
Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
"Multiple adjacent lanes encountered during construction of lane map.");
relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
Lane nextLane = adjacentLanes.iterator().next();
if (!laneSet.contains(nextLane) && !this.ignoreSet.contains(nextLane))
{
RollingLaneStructureRecord next = constructRecord(nextLane, record.getDirection(), prev, recordLink,
relativeLane);
this.ignoreSet.add(nextLane);
next.updateStartDistance(fractionalPosition, this);
nextSet.add(next);
laneSet.add(nextLane);
if (latDirection.isLeft())
{
prev.setLeft(next, gtuType);
if (nextLane.accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, prev
.getDirection()).contains(prev.getLane()))
{
next.setRight(prev, gtuType);
}
for (RollingLaneStructureRecord edgeRecord : edge)
{
if (!edgeRecord.equals(prev) && edgeRecord.getLane().getParentLink().equals(next.getLane()
.getParentLink()))
{
for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
LateralDirectionality.RIGHT, gtuType, edgeRecord.getDirection()))
{
if (adjLane.equals(next.getLane()))
{
edgeRecord.setRight(next, gtuType);
next.setLeft(edgeRecord, gtuType);
}
}
}
}
}
else
{
prev.setRight(next, gtuType);
if (nextLane.accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, prev
.getDirection()).contains(prev.getLane()))
{
next.setLeft(prev, gtuType);
}
for (RollingLaneStructureRecord edgeRecord : edge)
{
if (!edgeRecord.equals(prev) && edgeRecord.getLane().getParentLink().equals(next.getLane()
.getParentLink()))
{
for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
LateralDirectionality.LEFT, gtuType, edgeRecord.getDirection()))
{
if (adjLane.equals(next.getLane()))
{
edgeRecord.setLeft(next, gtuType);
next.setRight(edgeRecord, gtuType);
}
}
}
}
}
// connect longitudinally due to merge or split
Set<RollingLaneStructureRecord> adjs = new LinkedHashSet<>();
if (next.getLeft() != null)
{
adjs.add(next.getLeft());
}
if (next.getRight() != null)
{
adjs.add(next.getRight());
}
for (RollingLaneStructureRecord adj : adjs)
{
for (Lane lane : next.getLane().upstreamLanes(next.getDirection(), gtuType).keySet())
{
for (RollingLaneStructureRecord adjPrev : adj.getPrev())
{
if (lane.equals(adjPrev.getLane()))
{
Try.execute(() -> next.addPrev(adjPrev), "Cut-off record added as prev.");
}
}
}
for (Lane lane : next.getLane().downstreamLanes(next.getDirection(), gtuType).keySet())
{
for (RollingLaneStructureRecord adjNext : adj.getNext())
{
if (lane.equals(adjNext.getLane()))
{
Try.execute(() -> next.addNext(adjNext), "Cut-off record added as next.");
}
}
}
}
prev = next;
adjacentLanes = prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev
.getDirection());
}
else
{
break;
}
}
}
}
return nextSet;
}
/**
* This method makes sure that not all history is maintained forever, and the upstream edge moves with the GTU.
* @throws GTUException on exception
*/
private void retreatUpstreamEdge() throws GTUException
{
boolean moved = true;
Set<RollingLaneStructureRecord> nexts = new LinkedHashSet<>();
while (moved)
{
moved = false;
nexts.clear();
Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
while (iterator.hasNext())
{
RollingLaneStructureRecord prev = iterator.next();
// nexts may contain 'prev' as two lanes are removed from the upstream edge that merge in to 1 downstream lane
if (!nexts.contains(prev) && prev.getStartDistance().si + prev.getLane().getLength().si < this.up.si)
{
for (RollingLaneStructureRecord next : prev.getNext())
{
next.clearPrevList();
next.setCutOffStart(this.up.minus(next.getStartDistance()));
moved = true;
nexts.add(next);
RollingLaneStructureRecord lat = next.getLeft();
while (lat != null && lat.getPrev().isEmpty())
{
nexts.add(lat);
lat = lat.getLeft();
}
lat = next.getRight();
while (lat != null && lat.getPrev().isEmpty())
{
nexts.add(lat);
lat = lat.getRight();
}
}
prev.clearNextList();
removeRecord(prev);
iterator.remove();
}
else
{
Length cutOff = this.up.minus(prev.getStartDistance());
if (cutOff.si > 0)
{
prev.setCutOffStart(cutOff);
}
}
}
this.upstreamEdge.addAll(nexts);
// check adjacent lanes
for (RollingLaneStructureRecord record : nexts)
{
RollingLaneStructureRecord prev = record;
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
while (prev != null)
{
RollingLaneStructureRecord next = latDirection.isLeft() ? prev.getLeft() : prev.getRight();
if (next != null && !this.upstreamEdge.contains(next))
{
moved |= findUpstreamEdge(next);
}
prev = next;
}
}
}
}
}
/**
* Recursive method to find downstream record(s) on the upstream edge, as the edge was moved downstream and a laterally
* connected lane was not yet in the upstream edge. All edge records are added to the edge set.
* @param record RollingLaneStructureRecord; newly found adjacent record after moving the upstream edge downstream
* @return boolean; whether a record was added to the edge, note that no record is added of the record is fully downstream
* of the upstream view distance
* @throws GTUException on exception
*/
private boolean findUpstreamEdge(final RollingLaneStructureRecord record) throws GTUException
{
Length cutOff = this.up.minus(record.getStartDistance());
boolean moved = false;
if (cutOff.gt0())
{
if (cutOff.lt(record.getLane().getLength()))
{
record.clearPrevList();
record.setCutOffStart(cutOff);
this.upstreamEdge.add(record);
moved = true;
}
else
{
if (this.relativeLanes.containsKey(record))
{
// could have been removed from upstream already
removeRecord(record);
}
for (RollingLaneStructureRecord next : record.getNext())
{
moved |= findUpstreamEdge(next);
}
}
}
return moved;
}
/**
* Main downstream search for the map. Can be used at initial build and to update.
* @param gtuType GTUType; GTU type
* @param fractionalPosition double; fractional position on reference link
* @param route Route; route of the GTU
* @throws GTUException on exception
*/
private void expandDownstreamEdge(final GTUType gtuType, final double fractionalPosition, final Route route)
throws GTUException
{
this.ignoreSet.clear();
for (LaneStructureRecord record : this.downstreamEdge)
{
this.ignoreSet.add(record.getLane());
}
Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
Set<RollingLaneStructureRecord> splitSet = new LinkedHashSet<>();
boolean expand = true;
while (expand)
{
expand = false;
// longitudinal
// find links to extend from so we can add lanes if -any- of the next lanes comes within the perception distance
Set<Link> linksToExpandFrom = new LinkedHashSet<>();
Iterator<RollingLaneStructureRecord> iterator = this.downstreamEdge.iterator();
while (iterator.hasNext())
{
RollingLaneStructureRecord record = iterator.next();
if (record.getStartDistance().si + record.getLane().getLength().si < this.down.si)
{
linksToExpandFrom.add(record.getLane().getParentLink());
}
}
Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.downstreamEdge);
iterator = this.downstreamEdge.iterator();
while (iterator.hasNext())
{
RollingLaneStructureRecord record = iterator.next();
ImmutableMap<Lane, GTUDirectionality> nexts = record.getLane().downstreamLanes(record.getDirection(),
gtuType);
if (!linksToExpandFrom.contains(record.getLane().getParentLink()))
{
// downstream search ends on this lane
record.setCutOffEnd(this.down.minus(record.getStartDistance()));
for (Lane nextLane : nexts.keySet())
{
this.ignoreSet.add(nextLane); // exclude in lateral search
}
}
else
{
// downstream search goes further downstream
// in case there are multiple lanes on the same link after a lane split, we need to choose one
LaneDirection nextLaneDirection = new LaneDirection(record.getLane(), record.getDirection())
.getNextLaneDirection(this.containingGtu);
record.clearCutOffEnd();
iterator.remove(); // can remove from edge, no algorithm needs it anymore in the downstream edge
for (Lane nextLane : nexts.keySet())
{
if (nextLaneDirection != null && nextLane.getParentLink().equals(nextLaneDirection.getLane()
.getParentLink()) && !nextLane.equals(nextLaneDirection.getLane()))
{
// skip this lane as its a not chosen lane on the next link after a lane split
continue;
}
RelativeLane relativeLane = this.relativeLanes.get(record);
GTUDirectionality dir = nexts.get(nextLane);
RollingLaneStructureRecord next = constructRecord(nextLane, dir, record, RecordLink.DOWN,
relativeLane);
this.ignoreSet.add(nextLane);
next.updateStartDistance(fractionalPosition, this);
record.addNext(next);
next.addPrev(record);
connectLaterally(next, gtuType, modifiedEdge);
// check route
int from = route == null ? 0 : route.indexOf(next.getFromNode());
int to = route == null ? 1 : route.indexOf(next.getToNode());
if (to < 0 || to - from != 1)
{
// not on our route, add some distance and stop
splitSet.add(next);
}
else
{
// regular edge
nextSet.add(next);
}
modifiedEdge.add(next);
// expand upstream over any possible other lane that merges in to this
Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
set.add(next);
expandUpstreamMerge(set, gtuType, fractionalPosition, route);
}
}
}
this.downstreamEdge.addAll(nextSet);
expand |= !nextSet.isEmpty();
nextSet.clear();
// split
expandDownstreamSplit(splitSet, gtuType, fractionalPosition, route);
splitSet.clear();
// lateral
Set<RollingLaneStructureRecord> lateralSet = expandLateral(this.downstreamEdge, RecordLink.LATERAL_END, gtuType,
fractionalPosition);
nextSet.addAll(lateralSet);
expandUpstreamMerge(lateralSet, gtuType, fractionalPosition, route);
// next iteration
this.downstreamEdge.addAll(nextSet);
expand |= !nextSet.isEmpty();
nextSet.clear();
}
}
/**
* Expand the map to include a limited section downstream of a split, regarding links not on the route.
* @param set Set<RollingLaneStructureRecord>; set of lanes that have been laterally found
* @param gtuType GTUType; GTU type
* @param fractionalPosition double; fractional position on reference link
* @param route Route; route
* @throws GTUException on exception
*/
private void expandDownstreamSplit(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
final double fractionalPosition, final Route route) throws GTUException
{
Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
for (RollingLaneStructureRecord record : set)
{
prevs.put(record, record.getStartDistance().plus(this.downSplit));
}
while (!prevs.isEmpty())
{
for (RollingLaneStructureRecord prev : prevs.keySet())
{
ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().downstreamLanes(prev.getDirection(),
gtuType);
RelativeLane relativeLane = this.relativeLanes.get(prev);
for (Lane nextLane : nextLanes.keySet())
{
GTUDirectionality dir = nextLanes.get(nextLane);
Node fromNode = dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink()
.getEndNode();
Node toNode = dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink()
.getStartNode();
int from = route.indexOf(fromNode);
int to = route.indexOf(toNode);
if (from == -1 || to == -1 || to - from != 1)
{
RollingLaneStructureRecord next = constructRecord(nextLane, dir, prev, RecordLink.DOWN,
relativeLane);
next.updateStartDistance(fractionalPosition, this);
next.addPrev(prev);
prev.addNext(next);
connectLaterally(next, gtuType, nexts.keySet());
Length downHere = prevs.get(prev);
if (next.getStartDistance().si > downHere.si)
{
next.setCutOffEnd(downHere.minus(next.getStartDistance()));
}
else
{
nexts.put(next, downHere);
}
}
}
}
prevs = nexts;
nexts = new LinkedHashMap<>();
}
}
/**
* Expand the map to include a limited section upstream of a merge that is downstream, regarding links not on the route.
* @param set Set<RollingLaneStructureRecord>; set of lanes that have been laterally found
* @param gtuType GTUType; GTU type
* @param fractionalPosition double; fractional position on reference link
* @param route Route; route of the GTU
* @throws GTUException on exception
*/
private void expandUpstreamMerge(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
final double fractionalPosition, final Route route) throws GTUException
{
Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
for (RollingLaneStructureRecord record : set)
{
prevs.put(record, record.getStartDistance().plus(this.upMerge)); // upMerge is negative
}
while (!prevs.isEmpty())
{
for (RollingLaneStructureRecord prev : prevs.keySet())
{
ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
boolean anyAdded = false;
for (Lane nextLane : nextLanes.keySet())
{
GTUDirectionality dir = nextLanes.get(nextLane);
Node fromNode = dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink()
.getEndNode();
Node toNode = dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink()
.getStartNode();
int from = route == null ? 0 : route.indexOf(fromNode);
int to = route == null ? 1 : route.indexOf(toNode);
// TODO we now assume everything is on the route, but merges could be ok without route
// so, without a route we should be able to recognize which upstream 'nextLane' is on the other link
if (from == -1 || to == -1 || to - from != 1)
{
anyAdded = true;
RelativeLane relativeLane = this.relativeLanes.get(prev);
RollingLaneStructureRecord next = constructRecord(nextLane, nextLanes.get(nextLane), prev,
RecordLink.UP, relativeLane);
next.updateStartDistance(fractionalPosition, this);
next.addNext(prev);
prev.addPrev(next);
connectLaterally(next, gtuType, nexts.keySet());
Length upHere = prevs.get(prev);
if (next.getStartDistance().si < upHere.si)
{
next.setCutOffStart(upHere.minus(next.getStartDistance()));
this.upstreamEdge.add(next);
}
else
{
nexts.put(next, upHere);
}
}
}
if (!anyAdded && !set.contains(prev))
{
this.upstreamEdge.add(prev);
}
}
prevs = nexts;
nexts = new LinkedHashMap<>();
}
}
/**
* Helper method of various other methods that laterally couples lanes that have been longitudinally found.
* @param record RollingLaneStructureRecord; longitudinally found lane
* @param gtuType GTUType; GTU type
* @param nextSet Set<RollingLaneStructureRecord>; set of records on current build edge
*/
private void connectLaterally(final RollingLaneStructureRecord record, final GTUType gtuType,
final Set<RollingLaneStructureRecord> nextSet)
{
for (RollingLaneStructureRecord other : nextSet)
{
for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
LateralDirectionality.RIGHT})
{
if ((latDirection.isLeft() ? other.getLeft() : other.getRight()) == null)
{
for (Lane otherLane : other.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, other
.getDirection()))
{
if (otherLane.equals(record.getLane()))
{
if (latDirection.isLeft())
{
other.setLeft(record, gtuType);
record.setRight(other, gtuType);
}
else
{
other.setRight(record, gtuType);
record.setLeft(other, gtuType);
}
}
}
}
}
}
}
/**
* Creates a lane structure record and adds it to relevant maps.
* @param lane Lane; lane
* @param direction GTUDirectionality; direction
* @param startDistanceSource RollingLaneStructureRecord; source of the start distance
* @param recordLink RecordLink; record link
* @param relativeLane RelativeLane; relative lane
* @return created lane structure record
*/
private RollingLaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction,
final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink, final RelativeLane relativeLane)
{
RollingLaneStructureRecord record = new RollingLaneStructureRecord(lane, direction, startDistanceSource, recordLink);
if (!this.relativeLaneMap.containsKey(relativeLane))
{
this.relativeLaneMap.put(relativeLane, new LinkedHashSet<>());
}
this.relativeLaneMap.get(relativeLane).add(record);
this.relativeLanes.put(record, relativeLane);
return record;
}
/** {@inheritDoc} */
@Override
public final LaneStructureRecord getRootRecord()
{
return this.root.get();
}
/**
* @param time Time; time to obtain the root at
* @return rootRecord
*/
public final LaneStructureRecord getRootRecord(final Time time)
{
return this.root.get(time);
}
/** {@inheritDoc} */
@Override
public final SortedSet<RelativeLane> getExtendedCrossSection()
{
return this.firstRecords.navigableKeySet();
}
/**
* Returns the first record on the given lane. This is often a record in the current cross section, but it may be one
* downstream for a lane that starts further downstream.
* @param lane RelativeLane; lane
* @return first record on the given lane, or {@code null} if no such record
*/
@Override
public final RollingLaneStructureRecord getFirstRecord(final RelativeLane lane)
{
if (this.firstRecords.containsKey(lane))
{
return this.firstRecords.get(lane);
}
// not in current cross section, get first via downstream
RelativeLane rel = RelativeLane.CURRENT;
int dMin = Integer.MAX_VALUE;
for (RelativeLane relLane : this.crossSectionRecords.keySet())
{
if (relLane.getLateralDirectionality().equals(lane.getLateralDirectionality()))
{
int d = lane.getNumLanes() - relLane.getNumLanes();
if (d < dMin)
{
rel = relLane;
d = dMin;
}
}
}
RollingLaneStructureRecord record = this.crossSectionRecords.get(rel);
// move downstream until a lateral move is made to the right relative lane
while (rel.getNumLanes() < lane.getNumLanes())
{
RollingLaneStructureRecord adj = lane.getLateralDirectionality().isLeft() ? record.getLeft() : record.getRight();
if (adj != null)
{
rel = lane.getLateralDirectionality().isLeft() ? rel.getLeft() : rel.getRight();
record = adj;
}
else if (!record.getNext().isEmpty())
{
LaneDirection laneDir = new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(
this.containingGtu);
if (laneDir == null)
{
record = null;
break;
}
RollingLaneStructureRecord chosenNext = null;
for (RollingLaneStructureRecord next : record.getNext())
{
if (next.getLane().equals(laneDir.getLane()))
{
chosenNext = next;
break;
}
}
// Throw.when(chosenNext == null, RuntimeException.class,
// "Unexpected exception while deriving first record not on the cross-section.");
record = chosenNext;
if (record == null)
{
// TODO: Temporary fix for Aimsun demo
break;
}
}
else
{
// reached a dead-end
record = null;
break;
}
}
if (record != null)
{
// now move upstream until we are at x = 0
while (record.getPrev().size() == 1 && record.getStartDistance().gt0())
{
record = record.getPrev().get(0);
}
this.firstRecords.put(lane, record);
}
return record;
}
/**
* Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
* relative position, or as far as the lane map goes.
* @param clazz Class<T>; class of objects to find
* @param gtu LaneBasedGTU; gtu
* @param pos RelativePosition.TYPE; relative position to start search from
* @param <T> type of objects to find
* @return Sorted set of objects of requested type per lane
* @throws GTUException if lane is not in current set
*/
@Override
public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjects(
final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
{
Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
{
out.put(relativeLane, getDownstreamObjects(relativeLane, clazz, gtu, pos));
}
return out;
}
/**
* Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
* downstream from the relative position, or as far as the lane map goes.
* @param lane RelativeLane; lane
* @param clazz Class<T>; class of objects to find
* @param gtu LaneBasedGTU; gtu
* @param pos RelativePosition.TYPE; relative position to start search from
* @param <T> type of objects to find
* @return Sorted set of objects of requested type
* @throws GTUException if lane is not in current set
*/
@Override
@SuppressWarnings("unchecked")
public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjects(final RelativeLane lane,
final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
{
LaneStructureRecord record = getFirstRecord(lane);
SortedSet<Entry<T>> set = new TreeSet<>();
if (record != null)
{
double ds = gtu.getRelativePositions().get(pos).getDx().si - gtu.getReference().getDx().si;
if (record.isDownstreamBranch())
{
// the list is ordered, but only for DIR_PLUS, need to do our own ordering
Length minimumPosition;
Length maximumPosition;
if (record.getDirection().isPlus())
{
minimumPosition = Length.instantiateSI(ds - record.getStartDistance().si);
maximumPosition = Length.instantiateSI(record.getLane().getLength().si);
}
else
{
minimumPosition = Length.ZERO;
maximumPosition = Length.instantiateSI(record.getLane().getLength().si + record.getStartDistance().si
- ds);
}
for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
{
if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object
.getDirection().isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection()
.isBackwardOrBoth())))
{
// unchecked, but the above isAssignableFrom assures correctness
double distance = record.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
if (distance <= this.lookAhead.si)
{
set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
}
}
}
}
getDownstreamObjectsRecursive(set, record, clazz, ds);
}
return set;
}
/**
* Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
* downstream from the relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
* @param lane RelativeLane; lane
* @param clazz Class<T>; class of objects to find
* @param gtu LaneBasedGTU; gtu
* @param pos RelativePosition.TYPE; relative position to start search from
* @param <T> type of objects to find
* @param route Route; the route
* @return Sorted set of objects of requested type
* @throws GTUException if lane is not in current set
*/
@Override
public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
{
SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
if (route != null)
{
Iterator<Entry<T>> iterator = set.iterator();
while (iterator.hasNext())
{
Entry<T> entry = iterator.next();
CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode()) || Math.abs(route.indexOf(link
.getStartNode()) - route.indexOf(link.getEndNode())) != 1)
{
iterator.remove();
}
}
}
return set;
}
/**
* Recursive search for lane based objects downstream.
* @param set SortedSet<Entry<T>>; set to store entries into
* @param record LaneStructureRecord; current record
* @param clazz Class<T>; class of objects to find
* @param ds double; distance from reference to chosen relative position
* @param <T> type of objects to find
*/
@SuppressWarnings("unchecked")
private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
final LaneStructureRecord record, final Class<T> clazz, final double ds)
{
if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
{
return;
}
for (LaneStructureRecord next : record.getNext())
{
if (next.isDownstreamBranch())
{
List<LaneBasedObject> list = next.getLane().getLaneBasedObjects();
int iStart, di;
if (record.getDirection().isPlus())
{
iStart = 0;
di = 1;
}
else
{
iStart = list.size() - 1;
di = -1;
}
for (int i = iStart; i >= 0 & i < list.size(); i += di)
{
LaneBasedObject object = list.get(i);
if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object
.getDirection().isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection()
.isBackwardOrBoth())))
{
// unchecked, but the above isAssignableFrom assures correctness
double distance = next.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
if (distance <= this.lookAhead.si)
{
set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
}
else
{
return;
}
}
}
}
getDownstreamObjectsRecursive(set, next, clazz, ds);
}
}
/**
* Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
* relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
* @param clazz Class<T>; class of objects to find
* @param gtu LaneBasedGTU; gtu
* @param pos RelativePosition.TYPE; relative position to start search from
* @param <T> type of objects to find
* @param route Route; the route
* @return Sorted set of objects of requested type per lane
* @throws GTUException if lane is not in current set
*/
@Override
public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(
final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
{
Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
{
out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
}
return out;
}
/**
* Retrieve objects on a lane of a specific type. Returns upstream objects from the relative position for as far as the lane
* map goes. Distances to upstream objects are given as positive values.
* @param lane RelativeLane; lane
* @param clazz Class<T>; class of objects to find
* @param gtu LaneBasedGTU; gtu
* @param pos RelativePosition.TYPE; relative position to start search from
* @param <T> type of objects to find
* @return Sorted set of objects of requested type
* @throws GTUException if lane is not in current set
*/
@Override
@SuppressWarnings("unchecked")
public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
{
SortedSet<Entry<T>> set = new TreeSet<>();
LaneStructureRecord record = this.getFirstRecord(lane);
if (record.getStartDistance().gt0())
{
return set; // this lane is only downstream
}
Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
// the list is ordered, but only for DIR_PLUS, need to do our own ordering
Length minimumPosition;
Length maximumPosition;
if (record.getDirection().isPlus())
{
minimumPosition = Length.ZERO;
maximumPosition = record.getStartDistance().neg().minus(ds);
}
else
{
minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
maximumPosition = record.getLane().getLength();
}
Length distance;
for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
{
if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object.getDirection()
.isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
{
distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
// unchecked, but the above isAssignableFrom assures correctness
set.add(new Entry<>(distance, (T) object));
}
}
getUpstreamObjectsRecursive(set, record, clazz, ds);
return set;
}
/**
* Recursive search for lane based objects upstream.
* @param set SortedSet<Entry<T>>; set to store entries into
* @param record LaneStructureRecord; current record
* @param clazz Class<T>; class of objects to find
* @param ds Length; distance from reference to chosen relative position
* @param <T> type of objects to find
*/
@SuppressWarnings("unchecked")
private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
final LaneStructureRecord record, final Class<T> clazz, final Length ds)
{
for (LaneStructureRecord prev : record.getPrev())
{
Length distance;
for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
{
if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object.getDirection()
.isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
{
distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
// unchecked, but the above isAssignableFrom assures correctness
set.add(new Entry<>(distance, (T) object));
}
}
getUpstreamObjectsRecursive(set, prev, clazz, ds);
}
}
/**
* Print the lane structure as a number of lines in a String.
* @param ls RollingLaneStructure; the lane structure to print
* @param gtu LaneBasedGTU; the GTTU for which the lane structure is printed
* @return a String with information about the RollingLaneStructire
*/
public static String print(final RollingLaneStructure ls, final LaneBasedGTU gtu)
{
StringBuffer s = new StringBuffer();
s.append(gtu.getSimulator().getSimulatorTime() + " " + gtu.getId() + " LANESTRUCTURE: ");
for (LaneStructureRecord lsr : ls.relativeLanes.keySet())
{
s.append(lsr.toString() + " ");
}
int totSize = 0;
for (Set<RollingLaneStructureRecord> set : ls.relativeLaneMap.values())
{
totSize += set.size();
}
s.append("\n relativeLanes.size()=" + ls.relativeLanes.size() + " relativeLaneMap.totalSize()=" + totSize);
return s.toString();
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "LaneStructure [rootLSR=" + this.root + "]";
}
/**
* AnimationAccess provides access to a number of private fields in the structure, which should only be used read-only! <br>
* <br>
* Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
* See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
* The source code and binary code of this software is proprietary information of Delft University of Technology.
* @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
*/
public class AnimationAccess
{
/**
* @return the lane structure records of the cross section
*/
@SuppressWarnings("synthetic-access")
public TreeMap<RelativeLane, RollingLaneStructureRecord> getCrossSectionRecords()
{
return RollingLaneStructure.this.crossSectionRecords;
}
/**
* @return the upstream edge
*/
@SuppressWarnings("synthetic-access")
public Set<RollingLaneStructureRecord> getUpstreamEdge()
{
return RollingLaneStructure.this.upstreamEdge;
}
/**
* @return the downstream edge
*/
@SuppressWarnings("synthetic-access")
public Set<RollingLaneStructureRecord> getDownstreamEdge()
{
return RollingLaneStructure.this.downstreamEdge;
}
}
/** {@inheritDoc} */
@Override
public void notify(final EventInterface event) throws RemoteException
{
// triggers an update of the lane structure at the end of the final plan during the lane change, which is deviative
this.previouslyDeviative = false;
}
}