View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.util.HashMap;
4   import java.util.HashSet;
5   import java.util.Map;
6   import java.util.Set;
7   import java.util.TreeMap;
8   
9   import org.djunits.unit.LengthUnit;
10  import org.djunits.value.vdouble.scalar.Length;
11  import org.djunits.value.vdouble.scalar.Time;
12  import org.opentrafficsim.core.gtu.GTUDirectionality;
13  import org.opentrafficsim.core.gtu.GTUException;
14  import org.opentrafficsim.core.gtu.GTUType;
15  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
16  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
17  import org.opentrafficsim.core.gtu.perception.AbstractPerception;
18  import org.opentrafficsim.core.network.LateralDirectionality;
19  import org.opentrafficsim.core.network.Link;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.core.network.Node;
22  import org.opentrafficsim.core.network.route.Route;
23  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
24  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
25  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
26  import org.opentrafficsim.road.network.lane.Lane;
27  
28  import nl.tudelft.simulation.language.Throw;
29  
30  /**
31   * The perception module of a GTU based on lanes. It is responsible for perceiving (sensing) the environment of the GTU, which
32   * includes the locations of other GTUs. Perception is done at a certain time, and the perceived information might have a
33   * limited validity. In that sense, Perception is stateful. Information can be requested as often as needed, but will only be
34   * recalculated when asked explicitly. This abstract class provides the building blocks for lane-based perception. <br>
35   * Perception for lane-based GTUs involves information about GTUs in front of the owner GTU on the same lane (the 'leader' GTU),
36   * parallel vehicles (important if we want to change lanes), distance to other vehicles on parallel lanes, as well in front as
37   * to the back (important if we want to change lanes), and information about obstacles, traffic lights, speed signs, and ending
38   * lanes.
39   * <p>
40   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
41   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
42   * </p>
43   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
44   * initial version Nov 15, 2015 <br>
45   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
46   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
47   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
48   */
49  public abstract class AbstractLanePerception extends AbstractPerception implements LanePerception
50  {
51  
52      /** */
53      private static final long serialVersionUID = 20151128L;
54  
55      /** Lane structure to perform the perception with. */
56      private LaneStructure laneStructure = null;
57  
58      /** Most recent update time of lane structure. */
59      private Time updateTime = null;
60  
61      /**
62       * Create a new LanePerception module. Because the constructor is often called inside the constructor of a GTU, this
63       * constructor does not ask for the pointer to the GTU, as it is often impossible to provide at the time of construction.
64       * Use the setter of the GTU instead.
65       * @param gtu GTU
66       */
67      public AbstractLanePerception(final LaneBasedGTU gtu)
68      {
69          super(gtu);
70      }
71  
72      /** {@inheritDoc} */
73      @Override
74      public final LaneBasedGTU getGtu()
75      {
76          return (LaneBasedGTU) super.getGtu();
77      }
78  
79      /** {@inheritDoc} */
80      @Override
81      public final LaneStructure getLaneStructure() throws ParameterException
82      {
83          if (this.laneStructure == null || this.updateTime.lt(getGtu().getSimulator().getSimulatorTime().getTime()))
84          {
85              // downstream structure length
86              Length down = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.PERCEPTION);
87              // upstream structure length
88              Length up = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACK);
89              // structure length downstream of split on link not on route
90              Length downSplit = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
91              // structure length upstream of merge on link not on route
92              Length upMerge = Length.max(up, downSplit);
93              // negative values for upstream
94              up = up.neg();
95              upMerge = upMerge.neg();
96              // Create Lane Structure
97              DirectedLanePosition dlp;
98              try
99              {
100                 dlp = getGtu().getReferencePosition();
101             }
102             catch (GTUException exception)
103             {
104                 // Should not happen, we get the lane from the GTU
105                 throw new RuntimeException("Could not get fraction on root lane.", exception);
106             }
107             Lane rootLane = dlp.getLane();
108             GTUDirectionality direction = dlp.getGtuDirection();
109             double fraction = dlp.getPosition().si / rootLane.getLength().si;
110             LaneStructureRecord rootLSR =
111                     new LaneStructureRecord(rootLane, direction, rootLane.getLength().multiplyBy(-fraction));
112             this.laneStructure = new LaneStructure(rootLSR, downSplit);
113             this.laneStructure.addLaneStructureRecord(rootLSR, RelativeLane.CURRENT);
114             this.relativeLaneMap.clear();
115             this.relativeLaneMap.put(rootLSR, RelativeLane.CURRENT);
116             startBuild(rootLSR, fraction, getGtu().getGTUType(), down, downSplit, up, upMerge);
117 
118             // TODO possibly optimize by using a 'singleton' lane structure source, per GTUType
119             // TODO possibly build and destroy at edges only
120             this.updateTime = getGtu().getSimulator().getSimulatorTime().getTime();
121         }
122         return this.laneStructure;
123     }
124 
125     /**
126      * Local map where relative lanes are store per record, such that other records can be linked to the correct relative lane.
127      */
128     private final Map<LaneStructureRecord, RelativeLane> relativeLaneMap = new HashMap<>();
129 
130     /** Set of lanes that can be ignored as they are beyond build bounds. */
131     private final Set<Lane> ignoreSet = new HashSet<>();
132 
133     /**
134      * Starts the build from the current lane and creates an initial lateral set with correct start distances based on the
135      * fraction.
136      * 
137      * <pre>
138      *  ---------
139      * |  /|\    |
140      *  ---|-----
141      * |  /|\    |
142      *  ---|-----
143      * |   o     | rootLSR
144      *  ---|-----
145      * |  \|/    |
146      *  ---------
147      *  
148      * (---) fraction
149      * </pre>
150      * 
151      * @param rootLSR record where the GTU is currently
152      * @param fraction fractional position where the gtu is
153      * @param gtuType GTU type
154      * @param down maximum downstream distance to build structure
155      * @param downSplit maximum downstream distance past split not following the route to build structure
156      * @param up maximum upstream distance to build structure
157      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
158      */
159     private void startBuild(final LaneStructureRecord rootLSR, final double fraction, final GTUType gtuType, final Length down,
160             final Length downSplit, final Length up, final Length upMerge)
161     {
162         // Build initial lateral set
163         Set<LaneStructureRecord> recordSet = new HashSet<>();
164         Set<Lane> laneSet = new HashSet<>();
165         recordSet.add(rootLSR);
166         laneSet.add(rootLSR.getLane());
167         for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
168                 LateralDirectionality.RIGHT })
169         {
170             LaneStructureRecord current = rootLSR;
171             RelativeLane relativeLane = RelativeLane.CURRENT;
172             Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
173             while (!adjacentLanes.isEmpty())
174             {
175                 Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
176                         "Multiple adjacent lanes encountered during construction of lane map.");
177                 relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
178                 Lane lane = adjacentLanes.iterator().next();
179                 LaneStructureRecord adjacentRecord =
180                         constructRecord(lane, current.getDirection(), lane.getLength().multiplyBy(-fraction), relativeLane);
181                 if (latDirection.isLeft())
182                 {
183                     if (lane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType).contains(current.getLane()))
184                     {
185                         adjacentRecord.setRight(current);
186                     }
187                     current.setLeft(adjacentRecord);
188                 }
189                 else
190                 {
191                     if (lane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType).contains(current.getLane()))
192                     {
193                         adjacentRecord.setLeft(current);
194                     }
195                     current.setRight(adjacentRecord);
196                 }
197                 recordSet.add(adjacentRecord);
198                 laneSet.add(lane);
199                 current = adjacentRecord;
200                 adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
201             }
202         }
203         try
204         {
205             this.ignoreSet.clear();
206             buildDownstreamRecursive(recordSet, gtuType, down, up, downSplit, upMerge);
207             this.ignoreSet.clear();
208             buildUpstreamRecursive(recordSet, gtuType, down, up, upMerge);
209         }
210         catch (GTUException | NetworkException exception)
211         {
212             throw new RuntimeException("Exception while building lane map.", exception);
213         }
214     }
215 
216     /**
217      * Extends the lane structure with the downstream lanes of the current set. Per downstream link, a new set results, which
218      * are extended laterally before performing the next downstream step. If the lateral extension finds new lanes, the
219      * structure is extended upstream from those lanes over a limited distance.
220      * 
221      * <pre>
222      *  --------- ---------
223      * |       --|-)     --|-?A       ?: possible next steps
224      *  --------- ---------
225      * |       --|-)     --|-?A
226      *  --------- =============       A, B: two separate downstream links
227      * |       --|-)         --|-?B
228      *  ----===== ------|------
229      *     | C?(-|--   \|/   --|-?B   C: extend upstream if merge
230      *      ----- -------------
231      * </pre>
232      * 
233      * @param recordSet current lateral set of records
234      * @param gtuType GTU type
235      * @param down maximum downstream distance to build structure
236      * @param up maximum upstream distance to build structure
237      * @param downSplit maximum downstream distance past split not following the route to build structure
238      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
239      * @throws GTUException if an inconsistency in the lane map is encountered
240      * @throws NetworkException exception during movement over the network
241      */
242     private void buildDownstreamRecursive(final Set<LaneStructureRecord> recordSet, final GTUType gtuType, final Length down,
243             final Length up, final Length downSplit, final Length upMerge) throws GTUException, NetworkException
244     {
245         // Loop lanes and put downstream lanes in sets per downstream link
246         Map<Link, Set<Lane>> laneSets = new HashMap<>();
247         Map<Link, TreeMap<RelativeLane, LaneStructureRecord>> recordSets = new HashMap<>();
248         Map<Link, Length> maxStart = new HashMap<>();
249         for (LaneStructureRecord laneRecord : recordSet)
250         {
251             if (!laneRecord.isCutOffEnd())
252             {
253                 for (Lane nextLane : laneRecord.getLane().nextLanes(gtuType).keySet())
254                 {
255                     Link nextLink = nextLane.getParentLink();
256                     if (!laneSets.containsKey(nextLink))
257                     {
258                         laneSets.put(nextLink, new HashSet<>());
259                         recordSets.put(nextLink, new TreeMap<>());
260                         maxStart.put(nextLink, new Length(Double.MIN_VALUE, LengthUnit.SI));
261                     }
262                     laneSets.get(nextLink).add(nextLane);
263                     RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
264                     Length start = laneRecord.getStartDistance().plus(laneRecord.getLane().getLength());
265                     maxStart.put(nextLink, Length.max(maxStart.get(nextLink), start));
266                     LaneStructureRecord nextRecord = constructRecord(nextLane,
267                             laneRecord.getLane().nextLanes(gtuType).get(nextLane), start, relativeLane);
268                     if (start.plus(nextLane.getLength()).ge(down))
269                     {
270                         nextRecord.setCutOffEnd(down.minus(start));
271                     }
272                     recordSets.get(nextLink).put(relativeLane, nextRecord);
273                     laneRecord.addNext(nextRecord);
274                     nextRecord.addPrev(laneRecord);
275                 }
276             }
277             else
278             {
279                 for (Lane nextLane : laneRecord.getLane().nextLanes(gtuType).keySet())
280                 {
281                     this.ignoreSet.add(nextLane); // beyond 'down', do not add in lateral step
282                 }
283             }
284         }
285         // loop links to connect the lanes laterally and continue the build
286         Link currentLink = recordSet.iterator().next().getLane().getParentLink();
287         GTUDirectionality direction = recordSet.iterator().next().getDirection();
288         for (Link link : laneSets.keySet())
289         {
290             connectLaterally(recordSets.get(link), gtuType);
291             Set<LaneStructureRecord> set = new HashSet<>(recordSets.get(link).values()); // collection to set
292             set = extendLateral(set, gtuType, down, up, upMerge, true);
293             // reduce remaining downstream length if not on route, to at most 'downSplit'
294             Node nextNode = direction.isPlus() ? currentLink.getEndNode() : currentLink.getStartNode();
295             Length downLimit = down;
296             Route route = getGtu().getStrategicalPlanner().getRoute();
297             if (route != null && (!route.contains(nextNode) // if no route, do not limit
298                     || !((LaneBasedStrategicalRoutePlanner) getGtu().getStrategicalPlanner())
299                             .nextLinkDirection(nextNode, currentLink, gtuType).equals(link)))
300             {
301                 // as each lane has a separate start distance, use the maximum value from maxStart
302                 downLimit = Length.min(downLimit, maxStart.get(link).plus(downSplit));
303             }
304             buildDownstreamRecursive(set, gtuType, downLimit, up, downSplit, upMerge);
305         }
306     }
307 
308     /**
309      * Extends the lane structure with (multiple) left and right lanes of the current set. The extended lateral set is returned
310      * for the downstream or upstream build to continue.
311      * 
312      * <pre>
313      *  ---- ---------
314      * | ?(-|-- /|\   |
315      *  ---- ----|----   ?: extend upstream of merge if doMerge = true
316      * | ?(-|-- /|\   |
317      *  ---- ----|----  
318      *      |         | } 
319      *       ---------   } recordSet
320      *      |         | }
321      *       ----|---- 
322      *      |   \|/   |
323      *       ---------
324      * </pre>
325      * 
326      * @param recordSet current lateral set of records
327      * @param gtuType GTU type
328      * @param down maximum downstream distance to build structure
329      * @param up maximum upstream distance to build structure
330      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
331      * @param downstreamBuild whether building downstream
332      * @return laterally extended set
333      * @throws GTUException if an inconsistency in the lane map is encountered
334      * @throws NetworkException exception during movement over the network
335      */
336     private Set<LaneStructureRecord> extendLateral(final Set<LaneStructureRecord> recordSet, final GTUType gtuType,
337             final Length down, final Length up, final Length upMerge, final boolean downstreamBuild)
338             throws GTUException, NetworkException
339     {
340         Set<Lane> laneSet = new HashSet<>();
341         for (LaneStructureRecord laneStructureRecord : recordSet)
342         {
343             laneSet.add(laneStructureRecord.getLane());
344         }
345         for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
346                 LateralDirectionality.RIGHT })
347         {
348             Set<LaneStructureRecord> expandSet = new HashSet<>();
349             Length startDistance = null;
350             Length endDistance = null;
351             for (LaneStructureRecord laneRecord : recordSet)
352             {
353                 LaneStructureRecord current = laneRecord;
354                 startDistance = current.getStartDistance();
355                 endDistance = current.getStartDistance().plus(current.getLane().getLength());
356                 RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
357                 Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
358                 while (!adjacentLanes.isEmpty())
359                 {
360                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
361                             "Multiple adjacent lanes encountered during construction of lane map.");
362                     Lane laneAdjacent = adjacentLanes.iterator().next();
363                     Length adjacentStart = downstreamBuild ? startDistance : endDistance.minus(laneAdjacent.getLength());
364                     // skip is lane already in set, no effective length in structure, or in ignore list
365                     if (!laneSet.contains(laneAdjacent) && !adjacentStart.plus(laneAdjacent.getLength()).le(up)
366                             && !adjacentStart.ge(down) && !this.ignoreSet.contains(laneAdjacent))
367                     {
368                         laneSet.add(laneAdjacent);
369                         relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
370                         LaneStructureRecord recordAdjacent =
371                                 constructRecord(laneAdjacent, laneRecord.getDirection(), adjacentStart, relativeLane);
372                         expandSet.add(recordAdjacent);
373                         if (latDirection.isLeft())
374                         {
375                             if (laneAdjacent.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType)
376                                     .contains(current.getLane()))
377                             {
378                                 recordAdjacent.setRight(current);
379                             }
380                             current.setLeft(recordAdjacent);
381                         }
382                         else
383                         {
384                             if (laneAdjacent.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType)
385                                     .contains(current.getLane()))
386                             {
387                                 recordAdjacent.setLeft(current);
388                             }
389                             current.setRight(recordAdjacent);
390                         }
391                         if (adjacentStart.plus(laneAdjacent.getLength()).ge(down))
392                         {
393                             recordAdjacent.setCutOffEnd(down.minus(adjacentStart));
394                         }
395                         if (adjacentStart.le(up))
396                         {
397                             recordAdjacent.setCutOffStart(up.minus(adjacentStart));
398                         }
399                         current = recordAdjacent;
400                         adjacentLanes = current.getLane().accessibleAdjacentLanes(latDirection, gtuType);
401                     }
402                     else
403                     {
404                         break;
405                     }
406                 }
407             }
408             if (downstreamBuild & !expandSet.isEmpty())
409             {
410                 // limit search range and search upstream of merge
411                 buildUpstreamRecursive(expandSet, gtuType, down, startDistance.plus(upMerge), upMerge);
412             }
413             recordSet.addAll(expandSet);
414         }
415         return recordSet;
416     }
417 
418     /**
419      * Extends the lane structure with the upstream lanes of the current set. Per upstream link, a new set results, which are
420      * expanded laterally before performing the next upstream step.
421      * @param recordSet current lateral set of records
422      * @param gtuType GTU type
423      * @param down maximum downstream distance to build structure
424      * @param up maximum upstream distance to build structure
425      * @param upMerge maximum upstream distance upstream of downstream merges to build structure
426      * @throws GTUException if an inconsistency in the lane map is encountered
427      * @throws NetworkException exception during movement over the network
428      */
429     private void buildUpstreamRecursive(final Set<LaneStructureRecord> recordSet, final GTUType gtuType, final Length down,
430             final Length up, final Length upMerge) throws GTUException, NetworkException
431     {
432         // Loop lanes and put upstream lanes in sets per upstream link
433         Map<Link, Set<Lane>> laneSets = new HashMap<>();
434         Map<Link, TreeMap<RelativeLane, LaneStructureRecord>> recordSets = new HashMap<>();
435         Map<Link, Length> minStart = new HashMap<>();
436         for (LaneStructureRecord laneRecord : recordSet)
437         {
438             if (!laneRecord.isCutOffStart())
439             {
440                 for (Lane prevLane : laneRecord.getLane().prevLanes(gtuType).keySet())
441                 {
442                     Link prevLink = prevLane.getParentLink();
443                     if (!laneSets.containsKey(prevLink))
444                     {
445                         laneSets.put(prevLink, new HashSet<>());
446                         recordSets.put(prevLink, new TreeMap<>());
447                         minStart.put(prevLink, new Length(Double.MAX_VALUE, LengthUnit.SI));
448                     }
449                     laneSets.get(prevLink).add(prevLane);
450                     RelativeLane relativeLane = this.relativeLaneMap.get(laneRecord);
451                     Length start = laneRecord.getStartDistance().minus(prevLane.getLength());
452                     minStart.put(prevLink, Length.min(minStart.get(prevLink), start));
453                     LaneStructureRecord prevRecord = constructRecord(prevLane,
454                             laneRecord.getLane().prevLanes(gtuType).get(prevLane), start, relativeLane);
455                     if (start.le(up))
456                     {
457                         prevRecord.setCutOffStart(up.minus(start));
458                     }
459                     recordSets.get(prevLink).put(relativeLane, prevRecord);
460                     laneRecord.addPrev(prevRecord);
461                     prevRecord.addNext(laneRecord);
462                 }
463             }
464             else
465             {
466                 for (Lane prevLane : laneRecord.getLane().prevLanes(gtuType).keySet())
467                 {
468                     this.ignoreSet.add(prevLane); // beyond 'up', do not add in lateral step
469                 }
470             }
471         }
472         // loop links to connect the lanes laterally and continue the build
473         for (Link link : laneSets.keySet())
474         {
475             connectLaterally(recordSets.get(link), gtuType);
476             Set<LaneStructureRecord> set = new HashSet<>(recordSets.get(link).values()); // collection to set
477             set = extendLateral(set, gtuType, down, up, upMerge, false);
478             buildUpstreamRecursive(set, gtuType, down, up, upMerge);
479         }
480     }
481 
482     /**
483      * Creates a lane structure record and adds it to relevant maps.
484      * @param lane lane
485      * @param direction direction
486      * @param startDistance distance at start of record
487      * @param relativeLane relative lane
488      * @return created lane structure record
489      */
490     private LaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance,
491             final RelativeLane relativeLane)
492     {
493         LaneStructureRecord record = new LaneStructureRecord(lane, direction, startDistance);
494         this.laneStructure.addLaneStructureRecord(record, relativeLane);
495         this.relativeLaneMap.put(record, relativeLane);
496         return record;
497     }
498 
499     /**
500      * Connects the lane structure records laterally if appropriate.
501      * @param map Map<RelativeLane, LaneStructureRecord>; map
502      * @param gtuType gtu type
503      */
504     private void connectLaterally(final Map<RelativeLane, LaneStructureRecord> map, final GTUType gtuType)
505     {
506         for (RelativeLane relativeLane : map.keySet())
507         {
508             if (map.containsKey(relativeLane.getRight()))
509             {
510                 Lane thisLane = map.get(relativeLane).getLane();
511                 Lane rightLane = map.get(relativeLane.getRight()).getLane();
512                 if (thisLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtuType).contains(rightLane))
513                 {
514                     map.get(relativeLane).setRight(map.get(relativeLane.getRight()));
515                 }
516                 if (rightLane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtuType).contains(thisLane))
517                 {
518                     map.get(relativeLane.getRight()).setLeft(map.get(relativeLane));
519                 }
520             }
521         }
522     }
523 
524 }