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