View Javadoc
1   package org.opentrafficsim.kpi.sampling;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.LinkedHashMap;
6   import java.util.LinkedHashSet;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Map.Entry;
10  import java.util.Set;
11  import java.util.UUID;
12  
13  import org.djunits.value.vdouble.scalar.Duration;
14  import org.djunits.value.vdouble.scalar.Frequency;
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djutils.base.Identifiable;
18  import org.djutils.exceptions.Throw;
19  import org.djutils.immutablecollections.ImmutableIterator;
20  import org.opentrafficsim.kpi.interfaces.GtuData;
21  import org.opentrafficsim.kpi.interfaces.LaneData;
22  import org.opentrafficsim.kpi.interfaces.LinkData;
23  import org.opentrafficsim.kpi.sampling.filter.FilterDataSet;
24  import org.opentrafficsim.kpi.sampling.filter.FilterDataType;
25  
26  /**
27   * A query defines which subset of trajectory information should be included. This is in terms of space-time regions, and in
28   * terms of filter data of trajectories, e.g. only include trajectories of trucks.
29   * <p>
30   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
31   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
32   * </p>
33   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
34   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
35   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
36   * @param <G> GTU data type
37   * @param <L> lane data type
38   */
39  public final class Query<G extends GtuData, L extends LaneData<L>> implements Identifiable
40  {
41  
42      /** unique id. */
43      private final String id;
44  
45      /** Sampling. */
46      private final Sampler<G, L> sampler;
47  
48      /** Description. */
49      private final String description;
50  
51      /** Filter data set. */
52      private final FilterDataSet filterDataSet;
53  
54      /** Update frequency. */
55      private final Frequency updateFrequency;
56  
57      /** Interval to gather statistics over. */
58      private final Duration interval;
59  
60      /** List of space-time regions of this query. */
61      private final List<SpaceTimeRegion<? extends L>> spaceTimeRegions = new ArrayList<>();
62  
63      /**
64       * Constructor.
65       * @param sampler sampler
66       * @param id id
67       * @param description description
68       * @param filterDataSet filter data
69       * @throws NullPointerException if sampling, description or filterDataSet is null
70       */
71      public Query(final Sampler<G, L> sampler, final String id, final String description, final FilterDataSet filterDataSet)
72      {
73          this(sampler, id, description, filterDataSet, null, null);
74      }
75  
76      /**
77       * Constructor with time interval and update frequency.
78       * @param sampler sampler
79       * @param id id, may be {@code null}
80       * @param description description
81       * @param filterDataSet filter data
82       * @param updateFrequency update frequency, used by external controller, may be {@code null}
83       * @param interval interval to gather statistics over, used by external controller, may be {@code null}
84       * @throws NullPointerException if sampling, description or filterDataSet is {@code null}
85       */
86      public Query(final Sampler<G, L> sampler, final String id, final String description, final FilterDataSet filterDataSet,
87              final Frequency updateFrequency, final Duration interval)
88      {
89          Throw.whenNull(sampler, "Sampling may not be null.");
90          Throw.whenNull(description, "Description may not be null.");
91          Throw.whenNull(filterDataSet, "Meta data may not be null.");
92          this.sampler = sampler;
93          this.filterDataSet = new FilterDataSet(filterDataSet);
94          this.id = id == null ? UUID.randomUUID().toString() : id;
95          this.description = description;
96          this.updateFrequency = updateFrequency;
97          this.interval = interval;
98      }
99  
100     @Override
101     public String getId()
102     {
103         return this.id.toString();
104     }
105 
106     /**
107      * Returns the description.
108      * @return description
109      */
110     public String getDescription()
111     {
112         return this.description;
113     }
114 
115     /**
116      * Returns the update frequency.
117      * @return updateFrequency.
118      */
119     public Frequency getUpdateFrequency()
120     {
121         return this.updateFrequency;
122     }
123 
124     /**
125      * Returns the time interval.
126      * @return interval.
127      */
128     public Duration getInterval()
129     {
130         return this.interval;
131     }
132 
133     /**
134      * Returns the number of filter datas.
135      * @return number of filter data entries
136      */
137     public int filterSize()
138     {
139         return this.filterDataSet.size();
140     }
141 
142     /**
143      * Returns an iterator over the filter datas and the related data sets.
144      * @return iterator over filter data entries, removal is not allowed
145      */
146     public Iterator<Entry<FilterDataType<?, ?>, Set<?>>> getFilterDataSetIterator()
147     {
148         return this.filterDataSet.getFilterDataSetIterator();
149     }
150 
151     /**
152      * Defines a region in space and time for which this query is valid. All lanes in the link are included.
153      * @param link link
154      * @param startPosition start position
155      * @param endPosition end position
156      * @param startTime start time
157      * @param endTime end time
158      */
159     public void addSpaceTimeRegionLink(final LinkData<? extends L> link, final Length startPosition, final Length endPosition,
160             final Time startTime, final Time endTime)
161     {
162         Throw.whenNull(link, "Link may not be null.");
163         Throw.whenNull(startPosition, "Start position may not be null.");
164         Throw.whenNull(endPosition, "End position may not be null.");
165         Throw.whenNull(startTime, "Start time may not be null.");
166         Throw.whenNull(endTime, "End time may not be null.");
167         Throw.when(endPosition.lt(startPosition), IllegalArgumentException.class,
168                 "End position should be greater than start position.");
169         Throw.when(endTime.lt(startTime), IllegalArgumentException.class, "End time should be greater than start time.");
170         double fStart = startPosition.si / link.getLength().si;
171         double fEnd = endPosition.si / link.getLength().si;
172         for (L lane : link.getLanes())
173         {
174             Length x0 = lane.getLength().times(fStart);
175             Length x1 = lane.getLength().times(fEnd);
176             addSpaceTimeRegion(lane, x0, x1, startTime, endTime);
177         }
178     }
179 
180     /**
181      * Defines a region in space and time for which this query is valid.
182      * @param lane lane
183      * @param startPosition start position
184      * @param endPosition end position
185      * @param startTime start time
186      * @param endTime end time
187      */
188     public void addSpaceTimeRegion(final L lane, final Length startPosition, final Length endPosition, final Time startTime,
189             final Time endTime)
190     {
191         Throw.whenNull(lane, "Lane direction may not be null.");
192         Throw.whenNull(startPosition, "Start position may not be null.");
193         Throw.whenNull(endPosition, "End position may not be null.");
194         Throw.whenNull(startTime, "Start time may not be null.");
195         Throw.whenNull(endTime, "End time may not be null.");
196         Throw.when(endPosition.lt(startPosition), IllegalArgumentException.class,
197                 "End position should be greater than start position.");
198         Throw.when(endTime.lt(startTime), IllegalArgumentException.class, "End time should be greater than start time.");
199         SpaceTimeRegion<L> spaceTimeRegion = new SpaceTimeRegion<>(lane, startPosition, endPosition, startTime, endTime);
200         this.sampler.registerSpaceTimeRegion(spaceTimeRegion);
201         this.spaceTimeRegions.add(spaceTimeRegion);
202     }
203 
204     /**
205      * Returns the number of space-time regions.
206      * @return number of space-time regions
207      */
208     public int spaceTimeRegionSize()
209     {
210         return this.spaceTimeRegions.size();
211     }
212 
213     /**
214      * Returns an iterator over the space-time regions.
215      * @return iterator over space-time regions, removal is not allowed
216      */
217     public Iterator<SpaceTimeRegion<? extends L>> getSpaceTimeIterator()
218     {
219         return new ImmutableIterator<>(this.spaceTimeRegions.iterator());
220     }
221 
222     /**
223      * Returns a list of TrajectoryGroups in accordance with the query. Each {@code TrajectoryGroup} contains {@code Trajectory}
224      * objects pertaining to a {@code SpaceTimeRegion} from the query. A {@code Trajectory} is only included if all the filter
225      * data of this query accepts the trajectory. This method uses {@code Time.ZERO} as start.
226      * @param endTime end time of interval to get trajectory groups for
227      * @param <T> underlying class of filter data type and its value
228      * @return list of trajectory groups in accordance with the query
229      */
230     public <T> List<TrajectoryGroup<G>> getTrajectoryGroups(final Time endTime)
231     {
232         return getTrajectoryGroups(Time.ZERO, endTime);
233     }
234 
235     /**
236      * Returns a list of TrajectoryGroups in accordance with the query. Each {@code TrajectoryGroup} contains {@code Trajectory}
237      * objects pertaining to a {@code SpaceTimeRegion} from the query. A {@code Trajectory} is only included if all the filter
238      * data of this query accepts the trajectory.
239      * @param startTime start time of interval to get trajectory groups for
240      * @param endTime start time of interval to get trajectory groups for
241      * @param <T> underlying class of filter data type and its value
242      * @return list of trajectory groups in accordance with the query
243      */
244     @SuppressWarnings("unchecked")
245     public <T> List<TrajectoryGroup<G>> getTrajectoryGroups(final Time startTime, final Time endTime)
246     {
247         Throw.whenNull(startTime, "Start t may not be null.");
248         Throw.whenNull(endTime, "End t may not be null.");
249         // Step 1) gather trajectories per GTU, truncated over space and time
250         Map<String, TrajectoryAcceptList> trajectoryAcceptLists = new LinkedHashMap<>();
251         List<TrajectoryGroup<G>> trajectoryGroupList = new ArrayList<>();
252         for (SpaceTimeRegion<? extends L> spaceTimeRegion : this.spaceTimeRegions)
253         {
254             Time start = startTime.gt(spaceTimeRegion.startTime()) ? startTime : spaceTimeRegion.startTime();
255             Time end = endTime.lt(spaceTimeRegion.endTime()) ? endTime : spaceTimeRegion.endTime();
256             TrajectoryGroup<G> trajectoryGroup;
257             if (this.sampler.getSamplerData().getTrajectoryGroup(spaceTimeRegion.lane()) == null)
258             {
259                 trajectoryGroup = new TrajectoryGroup<>(start, spaceTimeRegion.lane());
260             }
261             else
262             {
263                 trajectoryGroup = this.sampler.getSamplerData().getTrajectoryGroup(spaceTimeRegion.lane())
264                         .getTrajectoryGroup(spaceTimeRegion.startPosition(), spaceTimeRegion.endPosition(), start, end);
265             }
266             for (Trajectory<G> trajectory : trajectoryGroup.getTrajectories())
267             {
268                 if (!trajectoryAcceptLists.containsKey(trajectory.getGtuId()))
269                 {
270                     trajectoryAcceptLists.put(trajectory.getGtuId(), new TrajectoryAcceptList());
271                 }
272                 trajectoryAcceptLists.get(trajectory.getGtuId()).addTrajectory(trajectory, trajectoryGroup);
273             }
274             trajectoryGroupList.add(trajectoryGroup);
275         }
276         // Step 2) accept per GTU
277         Iterator<String> iterator = trajectoryAcceptLists.keySet().iterator();
278         while (iterator.hasNext())
279         {
280             String gtuId = iterator.next();
281             TrajectoryAcceptList trajectoryAcceptListCombined = trajectoryAcceptLists.get(gtuId);
282             trajectoryAcceptListCombined.acceptAll(); // refuse only if any filter data type refuses
283             for (FilterDataType<?, ?> filterDataType : this.filterDataSet.getFilterDataTypes())
284             {
285                 // create safe copy per filter data type, with defaults accepts = false
286                 TrajectoryAcceptList trajectoryAcceptListCopy = copyTrajectoryAcceptList(trajectoryAcceptLists.get(gtuId));
287                 // request filter data type to accept or reject
288                 ((FilterDataType<T, ?>) filterDataType).accept(trajectoryAcceptListCopy,
289                         (Set<T>) new LinkedHashSet<>(this.filterDataSet.get(filterDataType)));
290                 // combine acceptance/rejection of filter data types so far
291                 for (int i = 0; i < trajectoryAcceptListCopy.size(); i++)
292                 {
293                     Trajectory<?> trajectory = trajectoryAcceptListCopy.getTrajectory(i);
294                     trajectoryAcceptListCombined.acceptTrajectory(trajectory,
295                             trajectoryAcceptListCombined.isAccepted(trajectory)
296                                     && trajectoryAcceptListCopy.isAccepted(trajectory));
297                 }
298             }
299         }
300         // Step 3) filter trajectories
301         List<TrajectoryGroup<G>> out = new ArrayList<>();
302         for (TrajectoryGroup<G> full : trajectoryGroupList)
303         {
304             TrajectoryGroup<G> filtered = new TrajectoryGroup<>(full.getStartTime(), full.getLane());
305             for (Trajectory<G> trajectory : full.getTrajectories())
306             {
307                 String gtuId = trajectory.getGtuId();
308                 if (trajectory.size() > 0 && trajectoryAcceptLists.get(gtuId).isAccepted(trajectory))
309                 {
310                     filtered.addTrajectory(trajectory);
311                 }
312             }
313             out.add(filtered);
314         }
315         return out;
316     }
317 
318     /**
319      * Returns a copy of the trajectory accept list, with all assumed not accepted.
320      * @param trajectoryAcceptList trajectory accept list to copy
321      * @return copy of the trajectory accept list, with all assumed not accepted
322      */
323     private TrajectoryAcceptList copyTrajectoryAcceptList(final TrajectoryAcceptList trajectoryAcceptList)
324     {
325         TrajectoryAcceptList trajectoryAcceptListCopy = new TrajectoryAcceptList();
326         for (int i = 0; i < trajectoryAcceptList.size(); i++)
327         {
328             trajectoryAcceptListCopy.addTrajectory(trajectoryAcceptList.getTrajectory(i),
329                     trajectoryAcceptList.getTrajectoryGroup(i));
330         }
331         return trajectoryAcceptListCopy;
332     }
333 
334     /**
335      * Returns the sampler.
336      * @return sampler.
337      */
338     public Sampler<G, L> getSampler()
339     {
340         return this.sampler;
341     }
342 
343     @Override
344     public int hashCode()
345     {
346         final int prime = 31;
347         int result = 1;
348         result = prime * result + this.description.hashCode();
349         result = prime * result + ((this.interval == null) ? 0 : this.interval.hashCode());
350         result = prime * result + this.filterDataSet.hashCode();
351         result = prime * result + this.sampler.hashCode();
352         result = prime * result + this.spaceTimeRegions.hashCode();
353         result = prime * result + this.id.hashCode();
354         result = prime * result + ((this.updateFrequency == null) ? 0 : this.updateFrequency.hashCode());
355         return result;
356     }
357 
358     @Override
359     public boolean equals(final Object obj)
360     {
361         if (this == obj)
362         {
363             return true;
364         }
365         if (obj == null)
366         {
367             return false;
368         }
369         if (getClass() != obj.getClass())
370         {
371             return false;
372         }
373         Query<?, ?> other = (Query<?, ?>) obj;
374         if (!this.description.equals(other.description))
375         {
376             return false;
377         }
378         if (this.interval == null)
379         {
380             if (other.interval != null)
381             {
382                 return false;
383             }
384         }
385         else if (!this.interval.equals(other.interval))
386         {
387             return false;
388         }
389         if (!this.filterDataSet.equals(other.filterDataSet))
390         {
391             return false;
392         }
393         if (!this.sampler.equals(other.sampler))
394         {
395             return false;
396         }
397         if (!this.spaceTimeRegions.equals(other.spaceTimeRegions))
398         {
399             return false;
400         }
401         if (!this.id.equals(other.id))
402         {
403             return false;
404         }
405         if (this.updateFrequency == null)
406         {
407             if (other.updateFrequency != null)
408             {
409                 return false;
410             }
411         }
412         else if (!this.updateFrequency.equals(other.updateFrequency))
413         {
414             return false;
415         }
416         return true;
417     }
418 
419     @Override
420     public String toString()
421     {
422         return "Query (" + this.description + ")";
423     }
424 
425 }