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.Iterator;
6   import java.util.LinkedList;
7   import java.util.Map;
8   import java.util.Queue;
9   import java.util.Set;
10  import java.util.SortedMap;
11  import java.util.TreeMap;
12  
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djutils.exceptions.Try;
15  import org.opentrafficsim.core.gtu.GTUException;
16  import org.opentrafficsim.core.gtu.RelativePosition;
17  import org.opentrafficsim.core.network.Link;
18  import org.opentrafficsim.core.network.route.Route;
19  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
20  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
21  
22  /**
23   * Abstract iterable that figures out how to find the next nearest object, including splits.
24   * <p>
25   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
27   * <p>
28   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 feb. 2018 <br>
29   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
30   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
31   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
32   * @param <H> headway type
33   * @param <U> underlying object type
34   * @param <C> counter type
35   */
36  public abstract class AbstractPerceptionIterable<H extends Headway, U, C> extends AbstractPerceptionReiterable<H, U>
37  {
38  
39      /** Root record. */
40      private final LaneRecord<?> root;
41  
42      /** Initial position. */
43      private final Length initialPosition;
44  
45      /** Search downstream (or upstream). */
46      private final boolean downstream;
47  
48      /** Max distance. */
49      private final double maxDistance;
50  
51      /** Position to which distance are calculated by subclasses. */
52      private final RelativePosition relativePosition;
53  
54      /** Route of the GTU. */
55      private final Route route;
56  
57      /**
58       * Constructor.
59       * @param perceivingGtu LaneBasedGTU; perceiving GTU
60       * @param root LaneRecord&lt;?&gt;; root record
61       * @param initialPosition Length; initial position
62       * @param downstream boolean; search downstream (or upstream)
63       * @param maxDistance Length; max distance to search
64       * @param relativePosition RelativePosition; position to which distance are calculated by subclasses
65       * @param route Route; route of the GTU, may be {@code null}
66       */
67      public AbstractPerceptionIterable(final LaneBasedGTU perceivingGtu, final LaneRecord<?> root, final Length initialPosition,
68              final boolean downstream, final Length maxDistance, final RelativePosition relativePosition, final Route route)
69      {
70          super(perceivingGtu);
71          this.root = root;
72          this.initialPosition = initialPosition;
73          this.downstream = downstream;
74          this.maxDistance = maxDistance.si;
75          this.relativePosition = relativePosition;
76          this.route = route;
77      }
78  
79      /** {@inheritDoc} */
80      @Override
81      public Iterator<PrimaryIteratorEntry> primaryIterator()
82      {
83          return new PrimaryIterator();
84      }
85  
86      /**
87       * Returns the next object(s) on the lane represented by the record. This should only consider objects on the given lane.
88       * This method should not check the distance towards objects with the maximum distance.
89       * @param record LaneRecord&lt;?&gt;; record representing the lane and direction
90       * @param position Length; position to look beyond
91       * @param counter C; counter
92       * @return next object(s) on the lane or {@code null} if none
93       * @throws GTUException on any exception in the process
94       */
95      protected abstract Entry getNext(LaneRecord<?> record, Length position, C counter) throws GTUException;
96  
97      /**
98       * Returns the distance to the object. The position fed in to this method is directly taken from an {@code Entry} returned
99       * by {@code getNext}. The two methods need to be consistent with each other.
100      * @param object U; underlying object
101      * @param record LaneRecord&lt;?&gt;; record representing the lane and direction
102      * @param position Length; position of the object on the lane
103      * @return Length; distance to the object
104      */
105     protected abstract Length getDistance(U object, LaneRecord<?> record, Length position);
106 
107     /**
108      * Returns the longitudinal length of the relevant relative position such that distances to this points can be calculated.
109      * @return Length; the longitudinal length of the relevant relative position such that distances to this points can be
110      *         calculated
111      */
112     protected Length getDx()
113     {
114         return this.relativePosition.getDx();
115     }
116 
117     /**
118      * The primary iterator is used by all returned iterators to find the next object. This contains the core algorithm to deal
119      * with splits and multiple objects at a single location.
120      * <p>
121      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
122      * <br>
123      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
124      * <p>
125      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 feb. 2018 <br>
126      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
127      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
128      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
129      */
130     private class PrimaryIterator implements Iterator<PrimaryIteratorEntry>
131     {
132 
133         /** Map containing the objects found per branch. */
134         private SortedMap<PrimaryIteratorEntry, LaneRecord<?>> map;
135 
136         /** Position per record where the search was halted. */
137         private Map<LaneRecord<?>, Length> positions = new HashMap<>();
138 
139         /** Items returned to prevent duplicates. */
140         private Set<U> returnedItems = new HashSet<>();
141 
142         /** Sets of remaining objects at the same location. */
143         private Map<LaneRecord<?>, Queue<PrimaryIteratorEntry>> queues = new HashMap<>();
144 
145         /** Counter objects per lane. */
146         private Map<LaneRecord<?>, C> counters = new HashMap<>();
147 
148         /** Record regarding a postponed call to {@code getNext()}. */
149         private LaneRecord<?> postponedRecord = null;
150 
151         /** Position regarding a postponed call to {@code getNext()}. */
152         private Length postponedPosition = null;
153 
154         /** Constructor. */
155         PrimaryIterator()
156         {
157             //
158         }
159 
160         /** {@inheritDoc} */
161         @Override
162         public boolean hasNext()
163         {
164             // (re)start the process
165             startProcess();
166 
167             // check next value
168             return !this.map.isEmpty();
169         }
170 
171         /** {@inheritDoc} */
172         @Override
173         public PrimaryIteratorEntry next()
174         {
175             // (re)start the process
176             startProcess();
177 
178             // get and remove next
179             PrimaryIteratorEntry nextEntry = this.map.firstKey();
180             U next = nextEntry.getObject();
181             LaneRecord<?> record = this.map.get(nextEntry);
182             this.map.remove(nextEntry);
183 
184             // see if we can obtain the next from a queue
185             Queue<PrimaryIteratorEntry> queue = this.queues.get(next);
186             if (queue != null)
187             {
188                 PrimaryIteratorEntry nextNext = queue.poll();
189                 this.map.put(nextNext, record); // next object is made available in the map
190                 if (queue.isEmpty())
191                 {
192                     this.queues.remove(record);
193                 }
194                 preventDuplicateEntries(nextEntry.getObject());
195                 return nextNext;
196             }
197 
198             // prepare for next
199             this.postponedRecord = record;
200             this.postponedPosition = this.positions.get(record); // position;
201             preventDuplicateEntries(nextEntry.getObject());
202             return nextEntry;
203         }
204 
205         /**
206          * Prevents that duplicate (and further) records are returned for the given object as splits later on merge.
207          * @param object U; object for which a {@code PrimaryIteratorEntry} will be returned
208          */
209         private void preventDuplicateEntries(final U object)
210         {
211             this.returnedItems.add(object); // prevents new items to be added over alive branches (that should die out)
212             Iterator<PrimaryIteratorEntry> it = this.map.keySet().iterator();
213             while (it.hasNext())
214             {
215                 PrimaryIteratorEntry entry = it.next();
216                 if (entry.getObject().equals(object))
217                 {
218                     it.remove();
219                 }
220             }
221         }
222 
223         /**
224          * Starts or restarts the process.
225          */
226         @SuppressWarnings("synthetic-access")
227         private void startProcess()
228         {
229             if (this.postponedRecord != null)
230             {
231                 // restart the process; perform prepareNext() that was postponed
232                 prepareNext(this.postponedRecord, this.postponedPosition);
233                 this.postponedRecord = null;
234                 this.postponedPosition = null;
235             }
236             else if (this.map == null)
237             {
238                 // start the process
239                 this.map = new TreeMap<>();
240                 prepareNext(AbstractPerceptionIterable.this.root, AbstractPerceptionIterable.this.initialPosition);
241             }
242         }
243 
244         /**
245          * Iterative method that continues a search on the next lanes if no object is found.
246          * @param record LaneRecord&lt;?&gt;; record
247          * @param position Length; position
248          */
249         @SuppressWarnings("synthetic-access")
250         private void prepareNext(final LaneRecord<?> record, final Length position)
251         {
252             Entry next = Try.assign(() -> AbstractPerceptionIterable.this.getNext(record, position, this.counters.get(record)),
253                     "Exception while deriving next object.");
254             if (next == null)
255             {
256                 this.counters.remove(record);
257                 double distance = AbstractPerceptionIterable.this.downstream
258                         ? record.getStartDistance().si + record.getLength().si : -record.getStartDistance().si;
259                 // TODO this let's us ignore an object that is registered on the next lane, but who's tail may be on this lane
260                 if (distance < AbstractPerceptionIterable.this.maxDistance)
261                 {
262                     if (AbstractPerceptionIterable.this.downstream)
263                     {
264                         for (LaneRecord<?> nextRecord : record.getNext())
265                         {
266                             if (isOnRoute(nextRecord))
267                             {
268                                 prepareNext(nextRecord, Length.createSI(-1e-9));
269                             }
270                         }
271                     }
272                     else
273                     {
274                         for (LaneRecord<?> nextRecord : record.getPrev())
275                         {
276                             if (isOnRoute(nextRecord))
277                             {
278                                 prepareNext(nextRecord, nextRecord.getLength());
279                             }
280                         }
281                     }
282                 }
283             }
284             else
285             {
286                 this.counters.put(record, next.counter);
287                 if (next.isSet())
288                 {
289                     Iterator<U> it = next.set.iterator();
290                     U nextNext = it.next();
291                     if (!this.returnedItems.contains(nextNext))
292                     {
293                         Length distance = getDistance(nextNext, record, next.position);
294                         if (distance == null // null means the object overlaps and is close
295                                 || distance.si <= AbstractPerceptionIterable.this.maxDistance)
296                         {
297                             // next object is made available in the map
298                             this.map.put(new PrimaryIteratorEntry(nextNext, distance), record);
299                             this.positions.put(record, next.position);
300                             if (next.set.size() > 1)
301                             {
302                                 // remaining at this location are made available in a queue
303                                 Queue<PrimaryIteratorEntry> queue = new LinkedList<>();
304                                 while (it.hasNext())
305                                 {
306                                     nextNext = it.next();
307                                     queue.add(new PrimaryIteratorEntry(nextNext, getDistance(nextNext, record, next.position)));
308                                 }
309                                 this.queues.put(record, queue);
310                             }
311                         }
312                     }
313                 }
314                 else if (!this.returnedItems.contains(next.object))
315                 {
316                     Length distance = getDistance(next.object, record, next.position);
317                     if (distance == null // null means the object overlaps and is close
318                             || distance.si <= AbstractPerceptionIterable.this.maxDistance)
319                     {
320                         // next object is made available in the map
321                         this.map.put(new PrimaryIteratorEntry(next.object, distance), record);
322                         this.positions.put(record, next.position);
323                     }
324                 }
325             }
326         }
327 
328     }
329 
330     /**
331      * Returns whether the record is on the route.
332      * @param record LaneRecord&lt;?&gt;; record
333      * @return boolean; whether the record is on the route
334      */
335     final boolean isOnRoute(final LaneRecord<?> record)
336     {
337         if (this.route == null)
338         {
339             return true;
340         }
341         Link link = record.getLane().getParentLink();
342         int from;
343         int to;
344         if (record.getDirection().isPlus())
345         {
346             from = this.route.indexOf(link.getStartNode());
347             to = this.route.indexOf(link.getEndNode());
348         }
349         else
350         {
351             from = this.route.indexOf(link.getEndNode());
352             to = this.route.indexOf(link.getStartNode());
353         }
354         return from != -1 && to != -1 && to - from == 1;
355     }
356 
357     /**
358      * Class of objects for subclasses to return. This can contain either a single object, or a set if there are multiple
359      * objects at a single location.
360      * <p>
361      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
362      * <br>
363      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
364      * <p>
365      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 feb. 2018 <br>
366      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
367      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
368      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
369      */
370     protected class Entry
371     {
372 
373         /** Set. */
374         private final Set<U> set;
375 
376         /** Object. */
377         private final U object;
378 
379         /** Counter. */
380         private final C counter;
381 
382         /** Position on the lane. */
383         private final Length position;
384 
385         /**
386          * Constructor.
387          * @param object U; object
388          * @param counter C; counter, may be {@code null}
389          * @param position Length; position
390          */
391         public Entry(final U object, final C counter, final Length position)
392         {
393             this.set = null;
394             this.object = object;
395             this.counter = counter;
396             this.position = position;
397         }
398 
399         /**
400          * Constructor.
401          * @param set Set&lt;U&gt;; set
402          * @param counter C; counter, may be {@code null}
403          * @param position Length; position
404          */
405         public Entry(final Set<U> set, final C counter, final Length position)
406         {
407             this.set = set;
408             this.object = null;
409             this.counter = counter;
410             this.position = position;
411         }
412 
413         /**
414          * Returns whether this entry contains a set.
415          * @return whether this entry contains a set
416          */
417         final boolean isSet()
418         {
419             return this.set != null;
420         }
421         
422         /**
423          * Returns the underlying object. Use {@code !isSet()} to check whether there is an object.
424          * @return U; underlying set
425          */
426         public U getObject()
427         {
428             return this.object;
429         }
430         
431         /**
432          * Returns the underlying set. Use {@code isSet()} to check whether there is a set.
433          * @return Set&lt;U&gt;; underlying set
434          */
435         public Set<U> getSet()
436         {
437             return this.set;
438         }
439 
440     }
441 
442 }