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 }