DirectInfrastructurePerception.java

  1. package org.opentrafficsim.road.gtu.lane.perception.categories;

  2. import java.util.LinkedHashMap;
  3. import java.util.LinkedHashSet;
  4. import java.util.Map;
  5. import java.util.Objects;
  6. import java.util.Set;
  7. import java.util.SortedSet;
  8. import java.util.TreeSet;
  9. import java.util.WeakHashMap;

  10. import org.djunits.value.vdouble.scalar.Length;
  11. import org.djutils.exceptions.Throw;
  12. import org.djutils.exceptions.Try;
  13. import org.opentrafficsim.base.TimeStampedObject;
  14. import org.opentrafficsim.base.parameters.ParameterException;
  15. import org.opentrafficsim.core.gtu.GTUException;
  16. import org.opentrafficsim.core.gtu.RelativePosition;
  17. import org.opentrafficsim.core.network.LateralDirectionality;
  18. import org.opentrafficsim.core.network.NetworkException;
  19. import org.opentrafficsim.core.network.route.Route;
  20. import org.opentrafficsim.road.gtu.lane.perception.InfrastructureLaneChangeInfo;
  21. import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
  22. import org.opentrafficsim.road.gtu.lane.perception.LaneStructureRecord;
  23. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  24. import org.opentrafficsim.road.network.lane.Lane;
  25. import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor;
  26. import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
  27. import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
  28. import org.opentrafficsim.road.network.speed.SpeedLimitTypes;

  29. /**
  30.  * Perceives information concerning the infrastructure, including splits, lanes, speed limits and road markings. This category
  31.  * is optimized by cooperating closely with the {@code LaneStructure} and only updating internal information when the GTU is on
  32.  * a new {@code Lane}. On the {@code Lane} information is defined relative to the start, and thus easily calculated at each
  33.  * time.
  34.  * <p>
  35.  * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  36.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  37.  * <p>
  38.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 14, 2016 <br>
  39.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  40.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  41.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  42.  */
  43. // TODO: more than the lane speed limit and maximum vehicle speed in the speed limit prospect
  44. public class DirectInfrastructurePerception extends LaneBasedAbstractPerceptionCategory implements InfrastructurePerception
  45. {

  46.     /** */
  47.     private static final long serialVersionUID = 20160811L;

  48.     /** Infrastructure lane change info per relative lane. */
  49.     private final Map<RelativeLane, TimeStampedObject<SortedSet<InfrastructureLaneChangeInfo>>> infrastructureLaneChangeInfo =
  50.             new LinkedHashMap<>();

  51.     /** Speed limit prospect per relative lane. */
  52.     private Map<RelativeLane, TimeStampedObject<SpeedLimitProspect>> speedLimitProspect = new LinkedHashMap<>();

  53.     /** Legal Lane change possibilities per relative lane and lateral direction. */
  54.     private final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<
  55.             LaneChangePossibility>>> legalLaneChangePossibility = new LinkedHashMap<>();

  56.     /** Physical Lane change possibilities per relative lane and lateral direction. */
  57.     private final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<
  58.             LaneChangePossibility>>> physicalLaneChangePossibility = new LinkedHashMap<>();

  59.     /** Cross-section. */
  60.     private TimeStampedObject<SortedSet<RelativeLane>> crossSection;

  61.     /** Cache for anyNextOk. */
  62.     private final Map<LaneStructureRecord, Boolean> anyNextOkCache = new WeakHashMap<>();

  63.     /** Set of records with accessible end as they are cut off. */
  64.     private final Set<LaneStructureRecord> cutOff = new LinkedHashSet<>();

  65.     /** Root. */
  66.     private LaneStructureRecord root;

  67.     /** Lanes registered to the GTU used to check if an update is required. */
  68.     private Set<Lane> lanes;

  69.     /** Route. */
  70.     private Route route;

  71.     /**
  72.      * @param perception LanePerception; perception
  73.      */
  74.     public DirectInfrastructurePerception(final LanePerception perception)
  75.     {
  76.         super(perception);
  77.     }

  78.     /** {@inheritDoc} */
  79.     @Override
  80.     public void updateAll() throws GTUException, ParameterException
  81.     {
  82.         updateCrossSection();
  83.         // clean-up
  84.         Set<RelativeLane> cs = getCrossSection();
  85.         this.infrastructureLaneChangeInfo.keySet().retainAll(cs);
  86.         this.legalLaneChangePossibility.keySet().retainAll(cs);
  87.         this.physicalLaneChangePossibility.keySet().retainAll(cs);
  88.         this.speedLimitProspect.keySet().retainAll(cs);
  89.         // only if required
  90.         LaneStructureRecord newRoot = getPerception().getLaneStructure().getRootRecord();
  91.         if (this.root == null || !newRoot.equals(this.root) || !this.lanes.equals(getPerception().getGtu().positions(
  92.             RelativePosition.REFERENCE_POSITION).keySet()) || !Objects.equals(this.route, getPerception().getGtu()
  93.                 .getStrategicalPlanner().getRoute()) || this.cutOff.stream().filter((record) -> !record.isCutOffEnd())
  94.                     .count() > 0)
  95.         {
  96.             this.cutOff.clear();
  97.             this.root = newRoot;
  98.             this.lanes = getPerception().getGtu().positions(RelativePosition.REFERENCE_POSITION).keySet();
  99.             this.route = getPerception().getGtu().getStrategicalPlanner().getRoute();
  100.             // TODO: this is not suitable if we change lane and consider e.g. dynamic speed signs, they will be forgotten
  101.             this.speedLimitProspect.clear();
  102.             for (RelativeLane lane : getCrossSection())
  103.             {
  104.                 updateInfrastructureLaneChangeInfo(lane);
  105.                 updateLegalLaneChangePossibility(lane, LateralDirectionality.LEFT);
  106.                 updateLegalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
  107.                 updatePhysicalLaneChangePossibility(lane, LateralDirectionality.LEFT);
  108.                 updatePhysicalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
  109.             }
  110.         }

  111.         // speed limit prospect
  112.         for (RelativeLane lane : getCrossSection())
  113.         {
  114.             updateSpeedLimitProspect(lane);
  115.         }
  116.         for (RelativeLane lane : getCrossSection())
  117.         {
  118.             if (!this.infrastructureLaneChangeInfo.containsKey(lane))
  119.             {
  120.                 updateInfrastructureLaneChangeInfo(lane); // new lane in cross section
  121.                 updateLegalLaneChangePossibility(lane, LateralDirectionality.LEFT);
  122.                 updateLegalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
  123.                 updatePhysicalLaneChangePossibility(lane, LateralDirectionality.LEFT);
  124.                 updatePhysicalLaneChangePossibility(lane, LateralDirectionality.RIGHT);
  125.             }
  126.         }
  127.     }

  128.     /** {@inheritDoc} */
  129.     @Override
  130.     public final void updateInfrastructureLaneChangeInfo(final RelativeLane lane) throws GTUException, ParameterException
  131.     {
  132.         if (this.infrastructureLaneChangeInfo.containsKey(lane) && this.infrastructureLaneChangeInfo.get(lane).getTimestamp()
  133.             .equals(getTimestamp()))
  134.         {
  135.             // already done at this time
  136.             return;
  137.         }
  138.         updateCrossSection();

  139.         // start at requested lane
  140.         SortedSet<InfrastructureLaneChangeInfo> resultSet = new TreeSet<>();
  141.         LaneStructureRecord record = getPerception().getLaneStructure().getFirstRecord(lane);
  142.         try
  143.         {
  144.             record = getPerception().getLaneStructure().getFirstRecord(lane);
  145.             if (!record.allowsRoute(getGtu().getStrategicalPlanner().getRoute(), getGtu().getGTUType()))
  146.             {
  147.                 resultSet.add(InfrastructureLaneChangeInfo.fromInaccessibleLane(record.isDeadEnd()));
  148.                 this.infrastructureLaneChangeInfo.put(lane, new TimeStampedObject<>(resultSet, getTimestamp()));
  149.                 return;
  150.             }
  151.         }
  152.         catch (NetworkException exception)
  153.         {
  154.             throw new GTUException("Route has no destination.", exception);
  155.         }
  156.         Map<LaneStructureRecord, InfrastructureLaneChangeInfo> currentSet = new LinkedHashMap<>();
  157.         Map<LaneStructureRecord, InfrastructureLaneChangeInfo> nextSet = new LinkedHashMap<>();
  158.         RelativePosition front = getPerception().getGtu().getFront();
  159.         currentSet.put(record, new InfrastructureLaneChangeInfo(0, record, front, record.isDeadEnd(),
  160.             LateralDirectionality.NONE));
  161.         while (!currentSet.isEmpty())
  162.         {
  163.             // move lateral
  164.             nextSet.putAll(currentSet);
  165.             for (LaneStructureRecord laneRecord : currentSet.keySet())
  166.             {
  167.                 while (laneRecord.legalLeft() && !nextSet.containsKey(laneRecord.getLeft()))
  168.                 {
  169.                     InfrastructureLaneChangeInfo info = nextSet.get(laneRecord).left(laneRecord.getLeft(), front, laneRecord
  170.                         .getLeft().isDeadEnd());
  171.                     nextSet.put(laneRecord.getLeft(), info);
  172.                     laneRecord = laneRecord.getLeft();
  173.                 }
  174.             }
  175.             for (LaneStructureRecord laneRecord : currentSet.keySet())
  176.             {
  177.                 while (laneRecord.legalRight() && !nextSet.containsKey(laneRecord.getRight()))
  178.                 {
  179.                     InfrastructureLaneChangeInfo info = nextSet.get(laneRecord).right(laneRecord.getRight(), front, laneRecord
  180.                         .getRight().isDeadEnd());
  181.                     nextSet.put(laneRecord.getRight(), info);
  182.                     laneRecord = laneRecord.getRight();
  183.                 }
  184.             }
  185.             // move longitudinal
  186.             currentSet = nextSet;
  187.             nextSet = new LinkedHashMap<>();
  188.             InfrastructureLaneChangeInfo bestOk = null;
  189.             InfrastructureLaneChangeInfo bestNotOk = null;
  190.             boolean deadEnd = false;
  191.             for (LaneStructureRecord laneRecord : currentSet.keySet())
  192.             {
  193.                 boolean anyOk = Try.assign(() -> anyNextOk(laneRecord), "Route has no destination.");
  194.                 if (anyOk)
  195.                 {
  196.                     // add to nextSet
  197.                     for (LaneStructureRecord next : laneRecord.getNext())
  198.                     {
  199.                         try
  200.                         {
  201.                             if (next.allowsRoute(getGtu().getStrategicalPlanner().getRoute(), getGtu().getGTUType()))
  202.                             {
  203.                                 InfrastructureLaneChangeInfo prev = currentSet.get(laneRecord);
  204.                                 InfrastructureLaneChangeInfo info = new InfrastructureLaneChangeInfo(prev
  205.                                     .getRequiredNumberOfLaneChanges(), next, front, next.isDeadEnd(), prev
  206.                                         .getLateralDirectionality());
  207.                                 nextSet.put(next, info);
  208.                             }
  209.                         }
  210.                         catch (NetworkException exception)
  211.                         {
  212.                             throw new RuntimeException("Network exception while considering route on next lane.", exception);
  213.                         }
  214.                     }
  215.                     // take best ok
  216.                     if (bestOk == null || currentSet.get(laneRecord).getRequiredNumberOfLaneChanges() < bestOk
  217.                         .getRequiredNumberOfLaneChanges())
  218.                     {
  219.                         bestOk = currentSet.get(laneRecord);
  220.                     }
  221.                 }
  222.                 else
  223.                 {
  224.                     // take best not ok
  225.                     deadEnd = deadEnd || currentSet.get(laneRecord).isDeadEnd();
  226.                     if (bestNotOk == null || currentSet.get(laneRecord).getRequiredNumberOfLaneChanges() < bestNotOk
  227.                         .getRequiredNumberOfLaneChanges())
  228.                     {
  229.                         bestNotOk = currentSet.get(laneRecord);
  230.                     }
  231.                 }

  232.             }
  233.             if (bestOk == null)
  234.             {
  235.                 break;
  236.             }
  237.             // 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
  238.             if (bestNotOk != null && bestOk.getRequiredNumberOfLaneChanges() > bestNotOk.getRequiredNumberOfLaneChanges())
  239.             {
  240.                 bestOk.setDeadEnd(deadEnd);
  241.                 resultSet.add(bestOk);
  242.             }
  243.             currentSet = nextSet;
  244.             nextSet = new LinkedHashMap<>();
  245.         }

  246.         // save
  247.         this.infrastructureLaneChangeInfo.put(lane, new TimeStampedObject<>(resultSet, getTimestamp()));
  248.     }

  249.     /**
  250.      * Returns whether the given record end is ok to pass. If not, a lane change is required before this end. The method will
  251.      * 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,
  252.      * or when there is a {@code SinkSensor} on the lane.
  253.      * @param record LaneStructureRecord; checked record
  254.      * @return whether the given record end is ok to pass
  255.      * @throws NetworkException if destination could not be obtained
  256.      * @throws GTUException if the GTU could not be obtained
  257.      */
  258.     private boolean anyNextOk(final LaneStructureRecord record) throws NetworkException, GTUException
  259.     {
  260.         if (record.isCutOffEnd())
  261.         {
  262.             this.cutOff.add(record);
  263.             return true; // always ok if cut-off
  264.         }
  265.         // check cache
  266.         Boolean ok = this.anyNextOkCache.get(record);
  267.         if (ok != null)
  268.         {
  269.             return ok;
  270.         }
  271.         // sink
  272.         for (SingleSensor s : record.getLane().getSensors())
  273.         {
  274.             // XXX for now, we do allow to lower speed for a DestinationSensor (e.g., to brake for parking)
  275.             if (s instanceof SinkSensor)
  276.             {
  277.                 this.anyNextOkCache.put(record, true);
  278.                 return true; // ok towards sink
  279.             }
  280.         }
  281.         // check destination
  282.         Route currentRoute = getGtu().getStrategicalPlanner().getRoute();
  283.         try
  284.         {
  285.             if (currentRoute != null && currentRoute.destinationNode().equals(record.getToNode()))
  286.             {
  287.                 this.anyNextOkCache.put(record, true);
  288.                 return true;
  289.             }
  290.         }
  291.         catch (NetworkException exception)
  292.         {
  293.             throw new RuntimeException("Could not determine destination node.", exception);
  294.         }
  295.         // check dead-end
  296.         if (record.getNext().isEmpty())
  297.         {
  298.             this.anyNextOkCache.put(record, false);
  299.             return false; // never ok if dead-end
  300.         }
  301.         // check if we have a route
  302.         if (currentRoute == null)
  303.         {
  304.             this.anyNextOkCache.put(record, true);
  305.             return true; // if no route assume ok, i.e. simple networks without routes
  306.         }
  307.         // finally check route
  308.         ok = record.allowsRouteAtEnd(currentRoute, getGtu().getGTUType());
  309.         this.anyNextOkCache.put(record, ok);
  310.         return ok;
  311.     }

  312.     /** {@inheritDoc} */
  313.     @Override
  314.     public final void updateSpeedLimitProspect(final RelativeLane lane) throws GTUException, ParameterException
  315.     {
  316.         updateCrossSection();
  317.         checkLaneIsInCrossSection(lane);
  318.         TimeStampedObject<SpeedLimitProspect> tsSlp = this.speedLimitProspect.get(lane);
  319.         SpeedLimitProspect slp;
  320.         if (tsSlp != null)
  321.         {
  322.             slp = tsSlp.getObject();
  323.             slp.update(getGtu().getOdometer());
  324.         }
  325.         else
  326.         {
  327.             slp = new SpeedLimitProspect(getGtu().getOdometer());
  328.             slp.addSpeedInfo(Length.ZERO, SpeedLimitTypes.MAX_VEHICLE_SPEED, getGtu().getMaximumSpeed(), getGtu());
  329.         }
  330.         try
  331.         {
  332.             Lane laneObj = getGtu().getReferencePosition().getLane();
  333.             if (!slp.containsAddSource(laneObj))
  334.             {
  335.                 slp.addSpeedInfo(Length.ZERO, SpeedLimitTypes.FIXED_SIGN, laneObj.getSpeedLimit(getGtu().getGTUType()),
  336.                     laneObj);
  337.             }
  338.         }
  339.         catch (NetworkException exception)
  340.         {
  341.             throw new RuntimeException("Could not obtain speed limit from lane for perception.", exception);
  342.         }
  343.         this.speedLimitProspect.put(lane, new TimeStampedObject<>(slp, getTimestamp()));
  344.     }

  345.     /** {@inheritDoc} */
  346.     @Override
  347.     public final void updateLegalLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat)
  348.             throws GTUException, ParameterException
  349.     {
  350.         updateLaneChangePossibility(lane, lat, true, this.legalLaneChangePossibility);
  351.     }

  352.     /** {@inheritDoc} */
  353.     @Override
  354.     public final void updatePhysicalLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat)
  355.             throws GTUException, ParameterException
  356.     {
  357.         updateLaneChangePossibility(lane, lat, false, this.physicalLaneChangePossibility);
  358.     }

  359.     /**
  360.      * Updates the distance over which lane changes remains legally or physically possible.
  361.      * @param lane RelativeLane; lane from which the lane change possibility is requested
  362.      * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
  363.      * @param legal boolean; legal, or physical otherwise
  364.      * @param possibilityMap
  365.      *            Map&lt;RelativeLane,Map&lt;LateralDirectionality,TimeStampedObject&lt;LaneChangePossibility&gt;&gt;&gt;;
  366.      *            Map&lt;RelativeLane,Map&lt;LateralDirectionality,TimeStampedObject&lt;LaneChangePossibility&gt;&gt;&gt;; legal
  367.      *            or physical possibility map
  368.      * @throws GTUException if the GTU was not initialized or if the lane is not in the cross section
  369.      * @throws ParameterException if a parameter is not defined
  370.      */
  371.     private void updateLaneChangePossibility(final RelativeLane lane, final LateralDirectionality lat, final boolean legal,
  372.             final Map<RelativeLane, Map<LateralDirectionality, TimeStampedObject<LaneChangePossibility>>> possibilityMap)
  373.             throws GTUException, ParameterException
  374.     {
  375.         updateCrossSection();
  376.         checkLaneIsInCrossSection(lane);

  377.         if (possibilityMap.get(lane) == null)
  378.         {
  379.             possibilityMap.put(lane, new LinkedHashMap<>());
  380.         }
  381.         LaneStructureRecord record = getPerception().getLaneStructure().getFirstRecord(lane);
  382.         // check tail
  383.         Length tail = getPerception().getGtu().getRear().getDx();
  384.         while (record != null && record.getStartDistance().gt(tail) && !record.getPrev().isEmpty() && ((lat.isLeft() && record
  385.             .possibleLeft(legal)) || (lat.isRight() && record.possibleRight(legal))))
  386.         {
  387.             if (record.getPrev().size() > 1)
  388.             {
  389.                 // assume not possible at a merge
  390.                 possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(record.getPrev().get(0),
  391.                     tail, true), getTimestamp()));
  392.                 return;
  393.             }
  394.             else if (record.getPrev().isEmpty())
  395.             {
  396.                 // dead-end, no lane upwards prevents a lane change
  397.                 break;
  398.             }
  399.             record = record.getPrev().get(0);
  400.             if ((lat.isLeft() && !record.possibleLeft(legal)) || (lat.isRight() && !record.possibleRight(legal)))
  401.             {
  402.                 // this lane prevents a lane change for the tail
  403.                 possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(record, tail, true),
  404.                     getTimestamp()));
  405.                 return;
  406.             }
  407.         }

  408.         LaneStructureRecord prevRecord = null;
  409.         record = getPerception().getLaneStructure().getFirstRecord(lane);

  410.         Length dx;
  411.         if ((lat.isLeft() && record.possibleLeft(legal)) || (lat.isRight() && record.possibleRight(legal)))
  412.         {
  413.             dx = getPerception().getGtu().getFront().getDx();
  414.             while (record != null && ((lat.isLeft() && record.possibleLeft(legal)) || (lat.isRight() && record.possibleRight(
  415.                 legal))))
  416.             {
  417.                 // TODO: splits
  418.                 prevRecord = record;
  419.                 record = record.getNext().isEmpty() ? null : record.getNext().get(0);
  420.             }
  421.         }
  422.         else
  423.         {
  424.             dx = getPerception().getGtu().getRear().getDx();
  425.             while (record != null && ((lat.isLeft() && !record.possibleLeft(legal)) || (lat.isRight() && !record.possibleRight(
  426.                 legal))))
  427.             {
  428.                 // TODO: splits
  429.                 prevRecord = record;
  430.                 record = record.getNext().isEmpty() ? null : record.getNext().get(0);
  431.             }
  432.         }
  433.         possibilityMap.get(lane).put(lat, new TimeStampedObject<>(new LaneChangePossibility(prevRecord, dx, true),
  434.             getTimestamp()));
  435.     }

  436.     /**
  437.      * @param lane RelativeLane; lane to check
  438.      * @throws GTUException if the lane is not in the cross section
  439.      */
  440.     private void checkLaneIsInCrossSection(final RelativeLane lane) throws GTUException
  441.     {
  442.         Throw.when(!getCrossSection().contains(lane), GTUException.class,
  443.             "The requeasted lane %s is not in the most recent cross section.", lane);
  444.     }

  445.     /** {@inheritDoc} */
  446.     @Override
  447.     public final void updateCrossSection() throws GTUException, ParameterException
  448.     {
  449.         if (this.crossSection != null && this.crossSection.getTimestamp().equals(getTimestamp()))
  450.         {
  451.             // already done at this time
  452.             return;
  453.         }
  454.         this.crossSection = new TimeStampedObject<>(getPerception().getLaneStructure().getExtendedCrossSection(),
  455.             getTimestamp());
  456.     }

  457.     /** {@inheritDoc} */
  458.     @Override
  459.     public final SortedSet<InfrastructureLaneChangeInfo> getInfrastructureLaneChangeInfo(final RelativeLane lane)
  460.     {
  461.         return this.infrastructureLaneChangeInfo.get(lane).getObject();
  462.     }

  463.     /** {@inheritDoc} */
  464.     @Override
  465.     public final SpeedLimitProspect getSpeedLimitProspect(final RelativeLane lane)
  466.     {
  467.         return this.speedLimitProspect.get(lane).getObject();
  468.     }

  469.     /** {@inheritDoc} */
  470.     @Override
  471.     public final Length getLegalLaneChangePossibility(final RelativeLane fromLane, final LateralDirectionality lat)
  472.     {
  473.         return this.legalLaneChangePossibility.get(fromLane).get(lat).getObject().getDistance(lat);
  474.     }

  475.     /** {@inheritDoc} */
  476.     @Override
  477.     public final Length getPhysicalLaneChangePossibility(final RelativeLane fromLane, final LateralDirectionality lat)
  478.     {
  479.         return this.physicalLaneChangePossibility.get(fromLane).get(lat).getObject().getDistance(lat);
  480.     }

  481.     /** {@inheritDoc} */
  482.     @Override
  483.     public final SortedSet<RelativeLane> getCrossSection()
  484.     {
  485.         return this.crossSection.getObject();
  486.     }

  487.     /**
  488.      * Returns time stamped infrastructure lane change info of a lane. A set is returned as multiple points may force lane
  489.      * changes. Which point is considered most critical is a matter of driver interpretation and may change over time. This is
  490.      * shown below. Suppose vehicle A needs to take the off-ramp, and that behavior is that the minimum distance per required
  491.      * lane change determines how critical it is. First, 400m before the lane-drop, the off-ramp is critical. 300m downstream,
  492.      * the lane-drop is critical. Info is sorted by distance, closest first.
  493.      *
  494.      * <pre>
  495.      * _______
  496.      * _ _A_ _\_________
  497.      * _ _ _ _ _ _ _ _ _
  498.      * _________ _ _ ___
  499.      *          \_______
  500.      *     (-)        Lane-drop: 1 lane change  in 400m (400m per lane change)
  501.      *     (--------) Off-ramp:  3 lane changes in 900m (300m per lane change, critical)
  502.      *    
  503.      *     (-)        Lane-drop: 1 lane change  in 100m (100m per lane change, critical)
  504.      *     (--------) Off-ramp:  3 lane changes in 600m (200m per lane change)
  505.      * </pre>
  506.      *
  507.      * @param lane RelativeLane; relative lateral lane
  508.      * @return time stamped infrastructure lane change info of a lane
  509.      */
  510.     public final TimeStampedObject<SortedSet<InfrastructureLaneChangeInfo>> getTimeStampedInfrastructureLaneChangeInfo(
  511.             final RelativeLane lane)
  512.     {
  513.         return this.infrastructureLaneChangeInfo.get(lane);
  514.     }

  515.     /**
  516.      * Returns the time stamped prospect for speed limits on a lane (dynamic speed limits may vary between lanes).
  517.      * @param lane RelativeLane; relative lateral lane
  518.      * @return time stamped prospect for speed limits on a lane
  519.      */
  520.     public final TimeStampedObject<SpeedLimitProspect> getTimeStampedSpeedLimitProspect(final RelativeLane lane)
  521.     {
  522.         return this.speedLimitProspect.get(lane);
  523.     }

  524.     /**
  525.      * Returns the time stamped distance over which a lane change remains legally possible.
  526.      * @param fromLane RelativeLane; lane from which the lane change possibility is requested
  527.      * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
  528.      * @return time stamped distance over which a lane change remains possible
  529.      * @throws NullPointerException if {@code lat == null}
  530.      */
  531.     public final TimeStampedObject<Length> getTimeStampedLegalLaneChangePossibility(final RelativeLane fromLane,
  532.             final LateralDirectionality lat)
  533.     {
  534.         TimeStampedObject<LaneChangePossibility> tsLcp = this.legalLaneChangePossibility.get(fromLane).get(lat);
  535.         LaneChangePossibility lcp = tsLcp.getObject();
  536.         return new TimeStampedObject<>(lcp.getDistance(lat), tsLcp.getTimestamp());
  537.     }

  538.     /**
  539.      * Returns the time stamped distance over which a lane change remains physically possible.
  540.      * @param fromLane RelativeLane; lane from which the lane change possibility is requested
  541.      * @param lat LateralDirectionality; LEFT or RIGHT, null not allowed
  542.      * @return time stamped distance over which a lane change remains possible
  543.      * @throws NullPointerException if {@code lat == null}
  544.      */
  545.     public final TimeStampedObject<Length> getTimeStampedPhysicalLaneChangePossibility(final RelativeLane fromLane,
  546.             final LateralDirectionality lat)
  547.     {
  548.         TimeStampedObject<LaneChangePossibility> tsLcp = this.physicalLaneChangePossibility.get(fromLane).get(lat);
  549.         LaneChangePossibility lcp = tsLcp.getObject();
  550.         return new TimeStampedObject<>(lcp.getDistance(lat), tsLcp.getTimestamp());
  551.     }

  552.     /**
  553.      * Returns a time stamped set of relative lanes representing the cross section. Lanes are sorted left to right.
  554.      * @return time stamped set of relative lanes representing the cross section
  555.      */
  556.     public final TimeStampedObject<SortedSet<RelativeLane>> getTimeStampedCrossSection()
  557.     {
  558.         return this.crossSection;
  559.     }

  560.     /** {@inheritDoc} */
  561.     @Override
  562.     public final String toString()
  563.     {
  564.         return "DirectInfrastructurePerception";
  565.     }

  566.     /**
  567.      * Helper class to return the distance over which a lane change is or is not possible. The distance is based on a
  568.      * LaneStructureRecord, and does not need an update as such.
  569.      * <p>
  570.      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  571.      * <br>
  572.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  573.      * <p>
  574.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 feb. 2018 <br>
  575.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  576.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  577.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  578.      */
  579.     private class LaneChangePossibility
  580.     {

  581.         /** Structure the end of which determines the available distance. */
  582.         private final LaneStructureRecord record;

  583.         /** Relative distance towards nose or tail. */
  584.         private final double dx;

  585.         /** Whether to apply legal accessibility. */
  586.         private final boolean legal;

  587.         /**
  588.          * @param record LaneStructureRecord; structure the end of which determines the available distance
  589.          * @param dx Length; relative distance towards nose or tail
  590.          * @param legal boolean; whether to apply legal accessibility
  591.          */
  592.         LaneChangePossibility(final LaneStructureRecord record, final Length dx, final boolean legal)
  593.         {
  594.             this.record = record;
  595.             this.dx = dx.si;
  596.             this.legal = legal;
  597.         }

  598.         /**
  599.          * Returns the distance over which a lane change is (&gt;0) or is not (&lt;0) possible.
  600.          * @param lat LateralDirectionality; lateral direction
  601.          * @return Length distance over which a lane change is (&gt;0) or is not (&lt;0) possible
  602.          */
  603.         final Length getDistance(final LateralDirectionality lat)
  604.         {
  605.             double d = this.record.getStartDistance().si + this.record.getLane().getLength().si - this.dx;
  606.             if ((lat.isLeft() && this.record.possibleLeft(this.legal)) || (lat.isRight() && this.record.possibleRight(
  607.                 this.legal)))
  608.             {
  609.                 return Length.instantiateSI(d); // possible over d
  610.             }
  611.             return Length.instantiateSI(-d); // not possible over d
  612.         }

  613.     }

  614. }