DirectInfrastructurePerception.java
- package org.opentrafficsim.road.gtu.lane.perception.categories;
- import java.util.LinkedHashMap;
- import java.util.LinkedHashSet;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Set;
- import java.util.SortedSet;
- import java.util.TreeSet;
- import java.util.WeakHashMap;
- import org.djunits.value.vdouble.scalar.Length;
- import org.djutils.exceptions.Throw;
- import org.djutils.exceptions.Try;
- import org.opentrafficsim.base.TimeStampedObject;
- import org.opentrafficsim.base.parameters.ParameterException;
- import org.opentrafficsim.core.gtu.GTUException;
- import org.opentrafficsim.core.gtu.RelativePosition;
- import org.opentrafficsim.core.network.LateralDirectionality;
- import org.opentrafficsim.core.network.NetworkException;
- import org.opentrafficsim.core.network.route.Route;
- import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
- import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
- import org.opentrafficsim.road.gtu.lane.perception.LaneStructureRecord;
- import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
- import org.opentrafficsim.road.network.lane.Lane;
- import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor;
- import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
- import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
- import org.opentrafficsim.road.network.speed.SpeedLimitTypes;
- /**
- * Perceives information concerning the infrastructure, including splits, lanes, speed limits and road markings. This category
- * is optimized by cooperating closely with the {@code LaneStructure} and only updating internal information when the GTU is on
- * a new {@code Lane}. On the {@code Lane} information is defined relative to the start, and thus easily calculated at each
- * time.
- * <p>
- * Copyright (c) 2013-2022 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/current/license.html">OpenTrafficSim License</a>.
- * <p>
- * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 14, 2016 <br>
- * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
- * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
- * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
- */
- // TODO: more than the lane speed limit and maximum vehicle speed in the speed limit prospect
- public class DirectInfrastructurePerception extends LaneBasedAbstractPerceptionCategory implements InfrastructurePerception
- {
- /** */
- private static final long serialVersionUID = 20160811L;
- /** Infrastructure lane change info per relative lane. */
- private final Map<RelativeLane, TimeStampedObject<SortedSet<InfrastructureLaneChangeInfo>>> infrastructureLaneChangeInfo =
- new LinkedHashMap<>();
- /** Speed limit prospect per relative lane. */
- private Map<RelativeLane, TimeStampedObject<SpeedLimitProspect>> speedLimitProspect = new LinkedHashMap<>();
- /** Legal Lane change possibilities per relative lane and lateral direction. */
- private final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<
- LaneChangePossibility>>> legalLaneChangePossibility = new LinkedHashMap<>();
- /** Physical Lane change possibilities per relative lane and lateral direction. */
- private final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<
- LaneChangePossibility>>> physicalLaneChangePossibility = new LinkedHashMap<>();
- /** Cross-section. */
- private TimeStampedObject<SortedSet<RelativeLane>> crossSection;
- /** Cache for anyNextOk. */
- private final Map<LaneStructureRecord, Boolean> anyNextOkCache = new WeakHashMap<>();
- /** Set of records with accessible end as they are cut off. */
- private final Set<LaneStructureRecord> cutOff = new LinkedHashSet<>();
- /** Root. */
- private LaneStructureRecord root;
- /** Lanes registered to the GTU used to check if an update is required. */
- private Set<Lane> lanes;
- /** Route. */
- private Route route;
- /**
- * @param perception LanePerception; perception
- */
- public DirectInfrastructurePerception(final LanePerception perception)
- {
- super(perception);
- }
- /** {@inheritDoc} */
- @Override
- public void updateAll() throws GTUException, ParameterException
- {
- updateCrossSection();
- // clean-up
- Set<RelativeLane> cs = getCrossSection();
- this.infrastructureLaneChangeInfo.keySet().retainAll(cs);
- this.legalLaneChangePossibility.keySet().retainAll(cs);
- this.physicalLaneChangePossibility.keySet().retainAll(cs);
- this.speedLimitProspect.keySet().retainAll(cs);
- // only if required
- LaneStructureRecord newRoot = getPerception().getLaneStructure().getRootRecord();
- if (this.root == null || !newRoot.equals(this.root) || !this.lanes.equals(getPerception().getGtu().positions(
- RelativePosition.REFERENCE_POSITION).keySet()) || !Objects.equals(this.route, getPerception().getGtu()
- .getStrategicalPlanner().getRoute()) || this.cutOff.stream().filter((record) -> !record.isCutOffEnd())
- .count() > 0)
- {
- this.cutOff.clear();
- this.root = newRoot;
- this.lanes = getPerception().getGtu().positions(RelativePosition.REFERENCE_POSITION).keySet();
- this.route = getPerception().getGtu().getStrategicalPlanner().getRoute();
- // TODO: this is not suitable if we change lane and consider e.g. dynamic speed signs, they will be forgotten
- this.speedLimitProspect.clear();
- for (RelativeLane lane : getCrossSection())
- {
- updateInfrastructureLaneChangeInfo(lane);
- updateLegalLaneChangePossibility(lane, LateralDirectionality.LEFT);
- updateLegalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
- updatePhysicalLaneChangePossibility(lane, LateralDirectionality.LEFT);
- updatePhysicalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
- }
- }
- // speed limit prospect
- for (RelativeLane lane : getCrossSection())
- {
- updateSpeedLimitProspect(lane);
- }
- for (RelativeLane lane : getCrossSection())
- {
- if (!this.infrastructureLaneChangeInfo.containsKey(lane))
- {
- updateInfrastructureLaneChangeInfo(lane); // new lane in cross section
- updateLegalLaneChangePossibility(lane, LateralDirectionality.LEFT);
- updateLegalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
- updatePhysicalLaneChangePossibility(lane, LateralDirectionality.LEFT);
- updatePhysicalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
- }
- }
- }
- /** {@inheritDoc} */
- @Override
- public final void updateInfrastructureLaneChangeInfo(final RelativeLane lane) throws GTUException, ParameterException
- {
- if (this.infrastructureLaneChangeInfo.containsKey(lane) && this.infrastructureLaneChangeInfo.get(lane).getTimestamp()
- .equals(getTimestamp()))
- {
- // already done at this time
- return;
- }
- updateCrossSection();
- // start at requested lane
- SortedSet<InfrastructureLaneChangeInfo> resultSet = new TreeSet<>();
- LaneStructureRecord record = getPerception().getLaneStructure().getFirstRecord(lane);
- try
- {
- record = getPerception().getLaneStructure().getFirstRecord(lane);
- if (!record.allowsRoute(getGtu().getStrategicalPlanner().getRoute(), getGtu().getGTUType()))
- {
- resultSet.add(InfrastructureLaneChangeInfo.fromInaccessibleLane(record.isDeadEnd()));
- this.infrastructureLaneChangeInfo.put(lane, new TimeStampedObject<>(resultSet, getTimestamp()));
- return;
- }
- }
- catch (NetworkException exception)
- {
- throw new GTUException("Route has no destination.", exception);
- }
- Map<LaneStructureRecord, InfrastructureLaneChangeInfo> currentSet = new LinkedHashMap<>();
- Map<LaneStructureRecord, InfrastructureLaneChangeInfo> nextSet = new LinkedHashMap<>();
- RelativePosition front = getPerception().getGtu().getFront();
- currentSet.put(record, new InfrastructureLaneChangeInfo(0, record, front, record.isDeadEnd(),
- LateralDirectionality.NONE));
- while (!currentSet.isEmpty())
- {
- // move lateral
- nextSet.putAll(currentSet);
- for (LaneStructureRecord laneRecord : currentSet.keySet())
- {
- while (laneRecord.legalLeft() && !nextSet.containsKey(laneRecord.getLeft()))
- {
- InfrastructureLaneChangeInfo info = nextSet.get(laneRecord).left(laneRecord.getLeft(), front, laneRecord
- .getLeft().isDeadEnd());
- nextSet.put(laneRecord.getLeft(), info);
- laneRecord = laneRecord.getLeft();
- }
- }
- for (LaneStructureRecord laneRecord : currentSet.keySet())
- {
- while (laneRecord.legalRight() && !nextSet.containsKey(laneRecord.getRight()))
- {
- InfrastructureLaneChangeInfo info = nextSet.get(laneRecord).right(laneRecord.getRight(), front, laneRecord
- .getRight().isDeadEnd());
- nextSet.put(laneRecord.getRight(), info);
- laneRecord = laneRecord.getRight();
- }
- }
- // move longitudinal
- currentSet = nextSet;
- nextSet = new LinkedHashMap<>();
- InfrastructureLaneChangeInfo bestOk = null;
- InfrastructureLaneChangeInfo bestNotOk = null;
- boolean deadEnd = false;
- for (LaneStructureRecord laneRecord : currentSet.keySet())
- {
- boolean anyOk = Try.assign(() -> anyNextOk(laneRecord), "Route has no destination.");
- if (anyOk)
- {
- // add to nextSet
- for (LaneStructureRecord next : laneRecord.getNext())
- {
- try
- {
- if (next.allowsRoute(getGtu().getStrategicalPlanner().getRoute(), getGtu().getGTUType()))
- {
- InfrastructureLaneChangeInfo prev = currentSet.get(laneRecord);
- InfrastructureLaneChangeInfo info = new InfrastructureLaneChangeInfo(prev
- .getRequiredNumberOfLaneChanges(), next, front, next.isDeadEnd(), prev
- .getLateralDirectionality());
- nextSet.put(next, info);
- }
- }
- catch (NetworkException exception)
- {
- throw new RuntimeException("Network exception while considering route on next lane.", exception);
- }
- }
- // take best ok
- if (bestOk == null || currentSet.get(laneRecord).getRequiredNumberOfLaneChanges() < bestOk
- .getRequiredNumberOfLaneChanges())
- {
- bestOk = currentSet.get(laneRecord);
- }
- }
- else
- {
- // take best not ok
- deadEnd = deadEnd || currentSet.get(laneRecord).isDeadEnd();
- if (bestNotOk == null || currentSet.get(laneRecord).getRequiredNumberOfLaneChanges() < bestNotOk
- .getRequiredNumberOfLaneChanges())
- {
- bestNotOk = currentSet.get(laneRecord);
- }
- }
- }
- if (bestOk == null)
- {
- break;
- }
- // if there are lanes that are not okay and only -further- lanes that are ok, we need to change to one of the ok's
- if (bestNotOk != null && bestOk.getRequiredNumberOfLaneChanges() > bestNotOk.getRequiredNumberOfLaneChanges())
- {
- bestOk.setDeadEnd(deadEnd);
- resultSet.add(bestOk);
- }
- currentSet = nextSet;
- nextSet = new LinkedHashMap<>();
- }
- // save
- this.infrastructureLaneChangeInfo.put(lane, new TimeStampedObject<>(resultSet, getTimestamp()));
- }
- /**
- * Returns whether the given record end is ok to pass. If not, a lane change is required before this end. The method will
- * also return true if the next node is the end node of the route, if the lane is cut off due to limited perception range,
- * or when there is a {@code SinkSensor} on the lane.
- * @param record LaneStructureRecord; checked record
- * @return whether the given record end is ok to pass
- * @throws NetworkException if destination could not be obtained
- * @throws GTUException if the GTU could not be obtained
- */
- private boolean anyNextOk(final LaneStructureRecord record) throws NetworkException, GTUException
- {
- if (record.isCutOffEnd())
- {
- this.cutOff.add(record);
- return true; // always ok if cut-off
- }
- // check cache
- Boolean ok = this.anyNextOkCache.get(record);
- if (ok != null)
- {
- return ok;
- }
- // sink
- for (SingleSensor s : record.getLane().getSensors())
- {
- // XXX for now, we do allow to lower speed for a DestinationSensor (e.g., to brake for parking)
- if (s instanceof SinkSensor)
- {
- this.anyNextOkCache.put(record, true);
- return true; // ok towards sink
- }
- }
- // check destination
- Route currentRoute = getGtu().getStrategicalPlanner().getRoute();
- try
- {
- if (currentRoute != null && currentRoute.destinationNode().equals(record.getToNode()))
- {
- this.anyNextOkCache.put(record, true);
- return true;
- }
- }
- catch (NetworkException exception)
- {
- throw new RuntimeException("Could not determine destination node.", exception);
- }
- // check dead-end
- if (record.getNext().isEmpty())
- {
- this.anyNextOkCache.put(record, false);
- return false; // never ok if dead-end
- }
- // check if we have a route
- if (currentRoute == null)
- {
- this.anyNextOkCache.put(record, true);
- return true; // if no route assume ok, i.e. simple networks without routes
- }
- // finally check route
- ok = record.allowsRouteAtEnd(currentRoute, getGtu().getGTUType());
- this.anyNextOkCache.put(record, ok);
- return ok;
- }
- /** {@inheritDoc} */
- @Override
- public final void updateSpeedLimitProspect(final RelativeLane lane) throws GTUException, ParameterException
- {
- updateCrossSection();
- checkLaneIsInCrossSection(lane);
- TimeStampedObject<SpeedLimitProspect> tsSlp = this.speedLimitProspect.get(lane);
- SpeedLimitProspect slp;
- if (tsSlp != null)
- {
- slp = tsSlp.getObject();
- slp.update(getGtu().getOdometer());
- }
- else
- {
- slp = new SpeedLimitProspect(getGtu().getOdometer());
- slp.addSpeedInfo(Length.ZERO, SpeedLimitTypes.MAX_VEHICLE_SPEED, getGtu().getMaximumSpeed(), getGtu());
- }
- try
- {
- Lane laneObj = getGtu().getReferencePosition().getLane();
- if (!slp.containsAddSource(laneObj))
- {
- slp.addSpeedInfo(Length.ZERO, SpeedLimitTypes.FIXED_SIGN, laneObj.getSpeedLimit(getGtu().getGTUType()),
- laneObj);
- }
- }
- catch (NetworkException exception)
- {
- throw new RuntimeException("Could not obtain speed limit from lane for perception.", exception);
- }
- this.speedLimitProspect.put(lane, new TimeStampedObject<>(slp, getTimestamp()));
- }
- /** {@inheritDoc} */
- @Override
- public final void updateLegalLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat)
- throws GTUException, ParameterException
- {
- updateLaneChangePossibility(lane, lat, true, this.legalLaneChangePossibility);
- }
- /** {@inheritDoc} */
- @Override
- public final void updatePhysicalLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat)
- throws GTUException, ParameterException
- {
- updateLaneChangePossibility(lane, lat, false, this.physicalLaneChangePossibility);
- }
- /**
- * Updates the distance over which lane changes remains legally or physically possible.
- * @param lane RelativeLane; lane from which the lane change possibility is requested
- * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
- * @param legal boolean; legal, or physical otherwise
- * @param possibilityMap
- * Map<RelativeLane,Map<LateralDirectionality,TimeStampedObject<LaneChangePossibility>>>;
- * Map<RelativeLane,Map<LateralDirectionality,TimeStampedObject<LaneChangePossibility>>>; legal
- * or physical possibility map
- * @throws GTUException if the GTU was not initialized or if the lane is not in the cross section
- * @throws ParameterException if a parameter is not defined
- */
- private void updateLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat, final boolean legal,
- final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<LaneChangePossibility>>> possibilityMap)
- throws GTUException, ParameterException
- {
- updateCrossSection();
- checkLaneIsInCrossSection(lane);
- if (possibilityMap.get(lane) == null)
- {
- possibilityMap.put(lane, new LinkedHashMap<>());
- }
- LaneStructureRecord record = getPerception().getLaneStructure().getFirstRecord(lane);
- // check tail
- Length tail = getPerception().getGtu().getRear().getDx();
- while (record != null && record.getStartDistance().gt(tail) && !record.getPrev().isEmpty() && ((lat.isLeft() && record
- .possibleLeft(legal)) || (lat.isRight() && record.possibleRight(legal))))
- {
- if (record.getPrev().size() > 1)
- {
- // assume not possible at a merge
- possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(record.getPrev().get(0),
- tail, true), getTimestamp()));
- return;
- }
- else if (record.getPrev().isEmpty())
- {
- // dead-end, no lane upwards prevents a lane change
- break;
- }
- record = record.getPrev().get(0);
- if ((lat.isLeft() && !record.possibleLeft(legal)) || (lat.isRight() && !record.possibleRight(legal)))
- {
- // this lane prevents a lane change for the tail
- possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(record, tail, true),
- getTimestamp()));
- return;
- }
- }
- LaneStructureRecord prevRecord = null;
- record = getPerception().getLaneStructure().getFirstRecord(lane);
- Length dx;
- if ((lat.isLeft() && record.possibleLeft(legal)) || (lat.isRight() && record.possibleRight(legal)))
- {
- dx = getPerception().getGtu().getFront().getDx();
- while (record != null && ((lat.isLeft() && record.possibleLeft(legal)) || (lat.isRight() && record.possibleRight(
- legal))))
- {
- // TODO: splits
- prevRecord = record;
- record = record.getNext().isEmpty() ? null : record.getNext().get(0);
- }
- }
- else
- {
- dx = getPerception().getGtu().getRear().getDx();
- while (record != null && ((lat.isLeft() && !record.possibleLeft(legal)) || (lat.isRight() && !record.possibleRight(
- legal))))
- {
- // TODO: splits
- prevRecord = record;
- record = record.getNext().isEmpty() ? null : record.getNext().get(0);
- }
- }
- possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(prevRecord, dx, true),
- getTimestamp()));
- }
- /**
- * @param lane RelativeLane; lane to check
- * @throws GTUException if the lane is not in the cross section
- */
- private void checkLaneIsInCrossSection(final RelativeLane lane) throws GTUException
- {
- Throw.when(!getCrossSection().contains(lane), GTUException.class,
- "The requeasted lane %s is not in the most recent cross section.", lane);
- }
- /** {@inheritDoc} */
- @Override
- public final void updateCrossSection() throws GTUException, ParameterException
- {
- if (this.crossSection != null && this.crossSection.getTimestamp().equals(getTimestamp()))
- {
- // already done at this time
- return;
- }
- this.crossSection = new TimeStampedObject<>(getPerception().getLaneStructure().getExtendedCrossSection(),
- getTimestamp());
- }
- /** {@inheritDoc} */
- @Override
- public final SortedSet<InfrastructureLaneChangeInfo> getInfrastructureLaneChangeInfo(final RelativeLane lane)
- {
- return this.infrastructureLaneChangeInfo.get(lane).getObject();
- }
- /** {@inheritDoc} */
- @Override
- public final SpeedLimitProspect getSpeedLimitProspect(final RelativeLane lane)
- {
- return this.speedLimitProspect.get(lane).getObject();
- }
- /** {@inheritDoc} */
- @Override
- public final Length getLegalLaneChangePossibility(final RelativeLane fromLane, final LateralDirectionality lat)
- {
- return this.legalLaneChangePossibility.get(fromLane).get(lat).getObject().getDistance(lat);
- }
- /** {@inheritDoc} */
- @Override
- public final Length getPhysicalLaneChangePossibility(final RelativeLane fromLane, final LateralDirectionality lat)
- {
- return this.physicalLaneChangePossibility.get(fromLane).get(lat).getObject().getDistance(lat);
- }
- /** {@inheritDoc} */
- @Override
- public final SortedSet<RelativeLane> getCrossSection()
- {
- return this.crossSection.getObject();
- }
- /**
- * Returns time stamped infrastructure lane change info of a lane. A set is returned as multiple points may force lane
- * changes. Which point is considered most critical is a matter of driver interpretation and may change over time. This is
- * shown below. Suppose vehicle A needs to take the off-ramp, and that behavior is that the minimum distance per required
- * lane change determines how critical it is. First, 400m before the lane-drop, the off-ramp is critical. 300m downstream,
- * the lane-drop is critical. Info is sorted by distance, closest first.
- *
- * <pre>
- * _______
- * _ _A_ _\_________
- * _ _ _ _ _ _ _ _ _
- * _________ _ _ ___
- * \_______
- * (-) Lane-drop: 1 lane change in 400m (400m per lane change)
- * (--------) Off-ramp: 3 lane changes in 900m (300m per lane change, critical)
- *
- * (-) Lane-drop: 1 lane change in 100m (100m per lane change, critical)
- * (--------) Off-ramp: 3 lane changes in 600m (200m per lane change)
- * </pre>
- *
- * @param lane RelativeLane; relative lateral lane
- * @return time stamped infrastructure lane change info of a lane
- */
- public final TimeStampedObject<SortedSet<InfrastructureLaneChangeInfo>> getTimeStampedInfrastructureLaneChangeInfo(
- final RelativeLane lane)
- {
- return this.infrastructureLaneChangeInfo.get(lane);
- }
- /**
- * Returns the time stamped prospect for speed limits on a lane (dynamic speed limits may vary between lanes).
- * @param lane RelativeLane; relative lateral lane
- * @return time stamped prospect for speed limits on a lane
- */
- public final TimeStampedObject<SpeedLimitProspect> getTimeStampedSpeedLimitProspect(final RelativeLane lane)
- {
- return this.speedLimitProspect.get(lane);
- }
- /**
- * Returns the time stamped distance over which a lane change remains legally possible.
- * @param fromLane RelativeLane; lane from which the lane change possibility is requested
- * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
- * @return time stamped distance over which a lane change remains possible
- * @throws NullPointerException if {@code lat == null}
- */
- public final TimeStampedObject<Length> getTimeStampedLegalLaneChangePossibility(final RelativeLane fromLane,
- final LateralDirectionality lat)
- {
- TimeStampedObject<LaneChangePossibility> tsLcp = this.legalLaneChangePossibility.get(fromLane).get(lat);
- LaneChangePossibility lcp = tsLcp.getObject();
- return new TimeStampedObject<>(lcp.getDistance(lat), tsLcp.getTimestamp());
- }
- /**
- * Returns the time stamped distance over which a lane change remains physically possible.
- * @param fromLane RelativeLane; lane from which the lane change possibility is requested
- * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
- * @return time stamped distance over which a lane change remains possible
- * @throws NullPointerException if {@code lat == null}
- */
- public final TimeStampedObject<Length> getTimeStampedPhysicalLaneChangePossibility(final RelativeLane fromLane,
- final LateralDirectionality lat)
- {
- TimeStampedObject<LaneChangePossibility> tsLcp = this.physicalLaneChangePossibility.get(fromLane).get(lat);
- LaneChangePossibility lcp = tsLcp.getObject();
- return new TimeStampedObject<>(lcp.getDistance(lat), tsLcp.getTimestamp());
- }
- /**
- * Returns a time stamped set of relative lanes representing the cross section. Lanes are sorted left to right.
- * @return time stamped set of relative lanes representing the cross section
- */
- public final TimeStampedObject<SortedSet<RelativeLane>> getTimeStampedCrossSection()
- {
- return this.crossSection;
- }
- /** {@inheritDoc} */
- @Override
- public final String toString()
- {
- return "DirectInfrastructurePerception";
- }
- /**
- * Helper class to return the distance over which a lane change is or is not possible. The distance is based on a
- * LaneStructureRecord, and does not need an update as such.
- * <p>
- * Copyright (c) 2013-2022 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/node/13">OpenTrafficSim License</a>.
- * <p>
- * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 feb. 2018 <br>
- * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
- * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
- * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
- */
- private class LaneChangePossibility
- {
- /** Structure the end of which determines the available distance. */
- private final LaneStructureRecord record;
- /** Relative distance towards nose or tail. */
- private final double dx;
- /** Whether to apply legal accessibility. */
- private final boolean legal;
- /**
- * @param record LaneStructureRecord; structure the end of which determines the available distance
- * @param dx Length; relative distance towards nose or tail
- * @param legal boolean; whether to apply legal accessibility
- */
- LaneChangePossibility(final LaneStructureRecord record, final Length dx, final boolean legal)
- {
- this.record = record;
- this.dx = dx.si;
- this.legal = legal;
- }
- /**
- * Returns the distance over which a lane change is (>0) or is not (<0) possible.
- * @param lat LateralDirectionality; lateral direction
- * @return Length distance over which a lane change is (>0) or is not (<0) possible
- */
- final Length getDistance(final LateralDirectionality lat)
- {
- double d = this.record.getStartDistance().si + this.record.getLane().getLength().si - this.dx;
- if ((lat.isLeft() && this.record.possibleLeft(this.legal)) || (lat.isRight() && this.record.possibleRight(
- this.legal)))
- {
- return Length.instantiateSI(d); // possible over d
- }
- return Length.instantiateSI(-d); // not possible over d
- }
- }
- }