View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.structure;
2   
3   import java.util.Collection;
4   import java.util.Iterator;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.NoSuchElementException;
9   import java.util.function.BiFunction;
10  import java.util.function.Function;
11  
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djutils.exceptions.Throw;
14  import org.opentrafficsim.road.gtu.lane.perception.structure.NavigatingIterable.Entry;
15  
16  /**
17   * Iterable over entries (with distance and merge distance stored) of objects. The iterable uses a navigator, lister and
18   * distancer.
19   * <ul>
20   * <li><i>navigator</i>; returns a collection of lane records to continue a search from a covered lane record.</li>
21   * <li><i>lister</i>; returns a list of objects from a lane record. The list must be ordered in the search direction (close to
22   * far). Objects of any type may be returned as the navigating iterator will check whether objects are of type {@code T}. In
23   * order to only include objects in the correct range, the lister must account for the start distance of the record, and any
24   * possible relative position of a GTU.</li>
25   * <li><i>distancer</i>; returns distance of an object. The distancer must account for any possible relative position of the
26   * GTUs.</li>
27   * </ul>
28   * <p>
29   * Copyright (c) 2024-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
30   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
31   * </p>
32   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
33   * @param <T> type of object
34   * @param <L> type of lane structure record
35   */
36  public class NavigatingIterable<T, L extends LaneRecordInterface<L>> implements Iterable<Entry<T>>
37  {
38  
39      /** Class of object type to be found from lanes. */
40      private final Class<T> clazz;
41  
42      /** Range within which objects are included. */
43      private final Length range;
44  
45      /** Start collection of lane records. */
46      private final Collection<L> start;
47  
48      /** Navigator which gives the next records. */
49      private final Function<L, Collection<L>> navigator;
50  
51      /** Obtains ordered list of objects from lane. */
52      private final Function<L, List<?>> lister;
53  
54      /** Returns distance of object. */
55      private final BiFunction<T, L, Length> distancer;
56  
57      /**
58       * Constructor.
59       * @param clazz class of lane-based object type.
60       * @param range range within which objects are included.
61       * @param start start collection of lane records.
62       * @param navigator navigator.
63       * @param lister obtains ordered list of objects from lane.
64       * @param distancer returns distance of object.
65       */
66      public NavigatingIterable(final Class<T> clazz, final Length range, final Collection<L> start,
67              final Function<L, Collection<L>> navigator, final Function<L, List<?>> lister,
68              final BiFunction<T, L, Length> distancer)
69      {
70          this.clazz = clazz;
71          this.range = range;
72          this.start = start;
73          this.navigator = navigator;
74          this.lister = lister;
75          this.distancer = distancer;
76      }
77  
78      @Override
79      public Iterator<Entry<T>> iterator()
80      {
81          // map of currently iterated records and their object iterators, create map from initial start
82          Map<L, ObjectIterator> map = new LinkedHashMap<>();
83          for (L startRecord : this.start)
84          {
85              map.put(startRecord, new ObjectIterator(startRecord, NavigatingIterable.this.clazz, NavigatingIterable.this.lister,
86                      NavigatingIterable.this.distancer));
87          }
88          return new Iterator<Entry<T>>()
89          {
90              /** Next entry as found by {@code hasNext()}. */
91              private Entry<T> next;
92  
93              @Override
94              public boolean hasNext()
95              {
96                  if (this.next != null)
97                  {
98                      return true;
99                  }
100                 // update the map with records that have something to produce
101                 Map<L, ObjectIterator> mapCopy = new LinkedHashMap<>(map);
102                 for (Map.Entry<L, ObjectIterator> mapEntry : mapCopy.entrySet())
103                 {
104                     if (!mapEntry.getValue().hasNext())
105                     {
106                         updateMapRecursive(mapEntry.getKey());
107                     }
108                 }
109                 if (map.isEmpty())
110                 {
111                     this.next = null;
112                 }
113                 else if (map.size() == 1)
114                 {
115                     this.next = map.values().iterator().next().next();
116                 }
117                 else
118                 {
119                     // loop map and find closest object
120                     Length minDistance = Length.POSITIVE_INFINITY;
121                     ObjectIterator closestObjectIterator = null;
122                     for (ObjectIterator objectIterator : map.values())
123                     {
124                         Entry<T> entry = objectIterator.poll();
125                         if (entry.distance().lt(minDistance))
126                         {
127                             minDistance = entry.distance();
128                             closestObjectIterator = objectIterator;
129                         }
130                     }
131                     this.next = closestObjectIterator.next(); // advance the object iterator; it was only polled above
132                 }
133                 if (this.next != null && this.next.distance().gt(NavigatingIterable.this.range))
134                 {
135                     this.next = null; // next object out of range
136                 }
137                 return this.next != null;
138             }
139 
140             @Override
141             public Entry<T> next()
142             {
143                 Throw.when(!hasNext(), NoSuchElementException.class, "No more object of type %s.",
144                         NavigatingIterable.this.clazz);
145                 Entry<T> n = this.next;
146                 this.next = null;
147                 return n;
148             }
149 
150             /**
151              * Updates the map so it contains a record only if it has an object to return. If not, further records are added to
152              * the map through the navigator and consecutively checked.
153              * @param record lane record.
154              */
155             private void updateMapRecursive(final L record)
156             {
157                 if (!map.containsKey(record) || map.get(record).hasNext())
158                 {
159                     return;
160                 }
161                 map.remove(record);
162                 for (L next : NavigatingIterable.this.navigator.apply(record))
163                 {
164                     map.put(next, new ObjectIterator(next, NavigatingIterable.this.clazz, NavigatingIterable.this.lister,
165                             NavigatingIterable.this.distancer));
166                     updateMapRecursive(next);
167                 }
168             }
169         };
170     }
171 
172     /**
173      * Iterator over objects on a {@code LaneRecordInterface}. This is used by {@code NavigatingIterable} to find object.
174      * <p>
175      * Copyright (c) 2024-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
176      * <br>
177      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
178      * </p>
179      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
180      */
181     private class ObjectIterator implements Iterator<Entry<T>>
182     {
183 
184         /** Lane record. */
185         private final L record;
186 
187         /** Class of lane-based object type. */
188         private final Class<T> clazz;
189 
190         /** List of objects from lane, within correct range for downstream. */
191         private final List<?> list;
192 
193         /** Index of current entry. */
194         private int index;
195 
196         /** Returns distance of object. */
197         private final BiFunction<T, L, Length> distancer;
198 
199         /** Poll entry, that next will return. */
200         private Entry<T> poll;
201 
202         /**
203          * Constructor. The lister may return objects of any type. This class will check whether objects are of type T.
204          * @param record lane record.
205          * @param clazz class of lane-based object type.
206          * @param lister obtains ordered list of objects from lane.
207          * @param distancer returns distance of object.
208          */
209         ObjectIterator(final L record, final Class<T> clazz, final Function<L, List<?>> lister,
210                 final BiFunction<T, L, Length> distancer)
211         {
212             this.record = record;
213             this.clazz = clazz;
214             this.list = lister.apply(record);
215             this.distancer = distancer;
216         }
217 
218         @Override
219         public boolean hasNext()
220         {
221             while (this.index < this.list.size() && !this.list.get(this.index).getClass().isAssignableFrom(this.clazz))
222             {
223                 this.index++;
224             }
225             return this.index < this.list.size();
226         }
227 
228         @Override
229         public Entry<T> next()
230         {
231             Entry<T> entry = poll();
232             this.index++;
233             this.poll = null;
234             return entry;
235         }
236 
237         /**
238          * Returns the entry that {@code next()} will return, without advancing the iterator.
239          * @return poll entry.
240          */
241         public Entry<T> poll()
242         {
243             Throw.when(!hasNext(), NoSuchElementException.class, "No more object of type %s.", this.clazz);
244             if (this.poll == null)
245             {
246                 @SuppressWarnings("unchecked") // isAssignableFrom in hasNext() checks this
247                 T t = (T) this.list.get(this.index);
248                 this.poll = new Entry<>(this.distancer.apply(t, this.record), this.record.getMergeDistance(), t);
249             }
250             return this.poll;
251         }
252 
253     }
254 
255     /**
256      * Container for a perceived object with the distance towards it and the distance until the road of the object and the road
257      * of the perceiving GTU merge.
258      * <p>
259      * Copyright (c) 2024-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
260      * <br>
261      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
262      * </p>
263      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
264      * @param <T> type of object
265      * @param distance distance to object
266      * @param merge ego-distance until the road of the object and the road of the perceiving GTU merge
267      * @param object the perceived object
268      */
269     public record Entry<T>(Length distance, Length merge, T object)
270     {
271     }
272 
273 }