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.unit.LengthUnit;
14  import org.djunits.value.vdouble.scalar.Duration;
15  import org.djunits.value.vdouble.scalar.Frequency;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Time;
18  import org.djutils.base.Identifiable;
19  import org.djutils.exceptions.Throw;
20  import org.djutils.immutablecollections.ImmutableIterator;
21  import org.opentrafficsim.kpi.interfaces.GtuData;
22  import org.opentrafficsim.kpi.interfaces.LaneData;
23  import org.opentrafficsim.kpi.interfaces.LinkData;
24  import org.opentrafficsim.kpi.sampling.meta.FilterDataSet;
25  import org.opentrafficsim.kpi.sampling.meta.FilterDataType;
26  
27  /**
28   * A query defines which subset of trajectory information should be included. This is in terms of space-time regions, and in
29   * terms of filter data of trajectories, e.g. only include trajectories of trucks.
30   * <p>
31   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
36   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
37   * @param <G> gtu data type
38   * @param <L> lane data type
39   */
40  public final class Query<G extends GtuData, L extends LaneData<L>> implements Identifiable
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&lt;G, L&gt;; sampler
66       * @param id String; id
67       * @param description String; description
68       * @param filterDataSet 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, description, filterDataSet, null, null);
74      }
75  
76      /**
77       * Constructor with time interval.
78       * @param sampler Sampler&lt;G, L&gt;; sampler
79       * @param id String; id
80       * @param description String; description
81       * @param filterDataSet FilterDataSet; filter data
82       * @param interval Duration; interval to gather statistics over
83       * @throws NullPointerException if sampling, description or filterDataSet is null
84       */
85      public Query(final Sampler<G, L> sampler, final String id, final String description, final FilterDataSet filterDataSet,
86              final Duration interval)
87      {
88          this(sampler, id, description, filterDataSet, null, interval);
89      }
90  
91      /**
92       * Constructor with update frequency.
93       * @param sampler Sampler&lt;G, L&gt;; sampler
94       * @param id String; id
95       * @param description String; description
96       * @param filterDataSet FilterDataSet; filter data
97       * @param updateFrequency Frequency; update frequency
98       * @throws NullPointerException if sampling, description or filterDataSet is null
99       */
100     public Query(final Sampler<G, L> sampler, final String id, final String description, final FilterDataSet filterDataSet,
101             final Frequency updateFrequency)
102     {
103         this(sampler, id, description, filterDataSet, updateFrequency, null);
104     }
105 
106     /**
107      * Constructor with time interval and update frequency.
108      * @param sampler Sampler&lt;G, L&gt;; sampler
109      * @param id String; id
110      * @param description String; description
111      * @param filterDataSet FilterDataSet; filter data
112      * @param updateFrequency Frequency; update frequency
113      * @param interval Duration; interval to gather statistics over
114      * @throws NullPointerException if sampling, description or filterDataSet is null
115      */
116     public Query(final Sampler<G, L> sampler, final String id, final String description, final FilterDataSet filterDataSet,
117             final Frequency updateFrequency, final Duration interval)
118     {
119         Throw.whenNull(sampler, "Sampling may not be null.");
120         Throw.whenNull(description, "Description may not be null.");
121         Throw.whenNull(filterDataSet, "Meta data may not be null.");
122         this.sampler = sampler;
123         this.filterDataSet = new FilterDataSet(filterDataSet);
124         this.id = id == null ? UUID.randomUUID().toString() : id;
125         this.description = description;
126         this.updateFrequency = updateFrequency;
127         this.interval = interval;
128     }
129 
130     /**
131      * Constructor without id.
132      * @param sampler Sampler&lt;G, L&gt;; sampler
133      * @param description String; description
134      * @param filterDataSet FilterDataSet; filter data
135      * @throws NullPointerException if sampling, description or filterDataSet is null
136      */
137     public Query(final Sampler<G, L> sampler, final String description, final FilterDataSet filterDataSet)
138     {
139         this(sampler, null, description, filterDataSet, null, null);
140     }
141 
142     /**
143      * Constructor without id, with time interval.
144      * @param sampler Sampler&lt;G, L&gt;; sampler
145      * @param description String; description
146      * @param filterDataSet filterDataSet; filter data
147      * @param interval Duration; interval to gather statistics over
148      * @throws NullPointerException if sampling, description or filterDataSet is null
149      */
150     public Query(final Sampler<G, L> sampler, final String description, final FilterDataSet filterDataSet,
151             final Duration interval)
152     {
153         this(sampler, null, description, filterDataSet, null, interval);
154     }
155 
156     /**
157      * Constructor without id, with time update frequency.
158      * @param sampler Sampler&lt;G, L&gt;; sampler
159      * @param description String; description
160      * @param filterDataSet filterDataSet; filter data
161      * @param updateFrequency Frequency; update frequency
162      * @throws NullPointerException if sampling, description or filterDataSet is null
163      */
164     public Query(final Sampler<G, L> sampler, final String description, final FilterDataSet filterDataSet,
165             final Frequency updateFrequency)
166     {
167         this(sampler, null, description, filterDataSet, updateFrequency, null);
168     }
169 
170     /**
171      * Constructor without id, with time interval and update frequency.
172      * @param sampler Sampler&lt;G, L&gt;; sampler
173      * @param description String; description
174      * @param filterDataSet filterDataSet; filter data
175      * @param updateFrequency Frequency; update frequency
176      * @param interval Duration; interval to gather statistics over
177      * @throws NullPointerException if sampling, description or filterDataSet is null
178      */
179     public Query(final Sampler<G, L> sampler, final String description, final FilterDataSet filterDataSet,
180             final Frequency updateFrequency, final Duration interval)
181     {
182         this(sampler, null, description, filterDataSet, updateFrequency, interval);
183     }
184 
185     /**
186      * Returns the unique id for the query.
187      * @return String; the unique id for the query
188      */
189     @Override
190     public String getId()
191     {
192         return this.id.toString();
193     }
194 
195     /**
196      * Returns the description.
197      * @return String; description
198      */
199     public String getDescription()
200     {
201         return this.description;
202     }
203 
204     /**
205      * Returns the update frequency.
206      * @return Frequency; updateFrequency.
207      */
208     public Frequency getUpdateFrequency()
209     {
210         return this.updateFrequency;
211     }
212 
213     /**
214      * Returns the time interval.
215      * @return Duration; interval.
216      */
217     public Duration getInterval()
218     {
219         return this.interval;
220     }
221 
222     /**
223      * Returns the number of filter datas.
224      * @return int; number of filter data entries
225      */
226     public int filterSize()
227     {
228         return this.filterDataSet.size();
229     }
230 
231     /**
232      * Returns an iterator over the filter datas and the related data sets.
233      * @return Iterator&lt;Entry&lt;FilterDataType&lt;?, ?&gt;, Set&lt;?&gt;&gt;&gt;; iterator over filter data entries, removal
234      *         is not allowed
235      */
236     public Iterator<Entry<FilterDataType<?, ?>, Set<?>>> getFilterDataSetIterator()
237     {
238         return this.filterDataSet.getFilterDataSetIterator();
239     }
240 
241     /**
242      * Defines a region in space and time for which this query is valid. All lanes in the link are included.
243      * @param link LinkData&lt;? extends L&gt;; link
244      * @param startPosition Length; start position
245      * @param endPosition Length; end position
246      * @param startTime Time; start time
247      * @param endTime Time; end time
248      */
249     public void addSpaceTimeRegionLink(final LinkData<? extends L> link, final Length startPosition, final Length endPosition,
250             final Time startTime, final Time endTime)
251     {
252         Throw.whenNull(link, "Link may not be null.");
253         Throw.whenNull(startPosition, "Start position may not be null.");
254         Throw.whenNull(endPosition, "End position may not be null.");
255         Throw.whenNull(startTime, "Start time may not be null.");
256         Throw.whenNull(endTime, "End time may not be null.");
257         Throw.when(endPosition.lt(startPosition), IllegalArgumentException.class,
258                 "End position should be greater than start position.");
259         Throw.when(endTime.lt(startTime), IllegalArgumentException.class, "End time should be greater than start time.");
260         for (L lane : link.getLaneDatas())
261         {
262             Length x0 = new Length(lane.getLength().si * startPosition.si / link.getLength().si, LengthUnit.SI);
263             Length x1 = new Length(lane.getLength().si * endPosition.si / link.getLength().si, LengthUnit.SI);
264             addSpaceTimeRegion(lane, x0, x1, startTime, endTime);
265         }
266     }
267 
268     /**
269      * Defines a region in space and time for which this query is valid.
270      * @param lane L; lane
271      * @param startPosition Length; start position
272      * @param endPosition Length; end position
273      * @param startTime Time; start time
274      * @param endTime Time; end time
275      */
276     public void addSpaceTimeRegion(final L lane, final Length startPosition, final Length endPosition, final Time startTime,
277             final Time endTime)
278     {
279         Throw.whenNull(lane, "Lane direction may not be null.");
280         Throw.whenNull(startPosition, "Start position may not be null.");
281         Throw.whenNull(endPosition, "End position may not be null.");
282         Throw.whenNull(startTime, "Start time may not be null.");
283         Throw.whenNull(endTime, "End time may not be null.");
284         Throw.when(endPosition.lt(startPosition), IllegalArgumentException.class,
285                 "End position should be greater than start position.");
286         Throw.when(endTime.lt(startTime), IllegalArgumentException.class, "End time should be greater than start time.");
287         SpaceTimeRegion<L> spaceTimeRegion = new SpaceTimeRegion<>(lane, startPosition, endPosition, startTime, endTime);
288         this.sampler.registerSpaceTimeRegion(spaceTimeRegion);
289         this.spaceTimeRegions.add(spaceTimeRegion);
290     }
291 
292     /**
293      * Returns the number of space-time regions.
294      * @return int; number of space-time regions
295      */
296     public int spaceTimeRegionSize()
297     {
298         return this.spaceTimeRegions.size();
299     }
300 
301     /**
302      * Returns an iterator over the space-time regions.
303      * @return Iterator&lt;SpaceTimeRegion&lt;? extends L&gt;&gt;; iterator over space-time regions, removal is not allowed
304      */
305     public Iterator<SpaceTimeRegion<? extends L>> getSpaceTimeIterator()
306     {
307         return new ImmutableIterator<>(this.spaceTimeRegions.iterator());
308     }
309 
310     /**
311      * Returns a list of TrajectoryGroups in accordance with the query. Each {@code TrajectoryGroup} contains {@code Trajectory}
312      * objects pertaining to a {@code SpaceTimeRegion} from the query. A {@code Trajectory} is only included if all the filter
313      * data of this query accepts the trajectory. This method uses {@code Time.ZERO} as start.
314      * @param endTime Time; end time of interval to get trajectory groups for
315      * @param <T> underlying class of filter data type and its value
316      * @return List&lt;TrajectoryGroup&lt;G&gt;&gt;; list of trajectory groups in accordance with the query
317      */
318     public <T> List<TrajectoryGroup<G>> getTrajectoryGroups(final Time endTime)
319     {
320         return getTrajectoryGroups(Time.ZERO, endTime);
321     }
322 
323     /**
324      * Returns a list of TrajectoryGroups in accordance with the query. Each {@code TrajectoryGroup} contains {@code Trajectory}
325      * objects pertaining to a {@code SpaceTimeRegion} from the query. A {@code Trajectory} is only included if all the filter
326      * data of this query accepts the trajectory.
327      * @param startTime Time; start time of interval to get trajectory groups for
328      * @param endTime Time; start time of interval to get trajectory groups for
329      * @param <T> underlying class of filter data type and its value
330      * @return List&lt;TrajectoryGroup&lt;G&gt;&gt;; list of trajectory groups in accordance with the query
331      */
332     @SuppressWarnings("unchecked")
333     public <T> List<TrajectoryGroup<G>> getTrajectoryGroups(final Time startTime, final Time endTime)
334     {
335         Throw.whenNull(startTime, "Start t may not be null.");
336         Throw.whenNull(endTime, "End t may not be null.");
337         // Step 1) gather trajectories per GTU, truncated over space and time
338         Map<String, TrajectoryAcceptList> trajectoryAcceptLists = new LinkedHashMap<>();
339         List<TrajectoryGroup<G>> trajectoryGroupList = new ArrayList<>();
340         for (SpaceTimeRegion<? extends L> spaceTimeRegion : this.spaceTimeRegions)
341         {
342             Time start = startTime.gt(spaceTimeRegion.startTime()) ? startTime : spaceTimeRegion.startTime();
343             Time end = endTime.lt(spaceTimeRegion.endTime()) ? endTime : spaceTimeRegion.endTime();
344             TrajectoryGroup<G> trajectoryGroup;
345             if (this.sampler.getSamplerData().getTrajectoryGroup(spaceTimeRegion.lane()) == null)
346             {
347                 trajectoryGroup = new TrajectoryGroup<>(start, spaceTimeRegion.lane());
348             }
349             else
350             {
351                 trajectoryGroup = this.sampler.getSamplerData().getTrajectoryGroup(spaceTimeRegion.lane())
352                         .getTrajectoryGroup(spaceTimeRegion.startPosition(), spaceTimeRegion.endPosition(), start, end);
353             }
354             for (Trajectory<G> trajectory : trajectoryGroup.getTrajectories())
355             {
356                 if (!trajectoryAcceptLists.containsKey(trajectory.getGtuId()))
357                 {
358                     trajectoryAcceptLists.put(trajectory.getGtuId(), new TrajectoryAcceptList());
359                 }
360                 trajectoryAcceptLists.get(trajectory.getGtuId()).addTrajectory(trajectory, trajectoryGroup);
361             }
362             trajectoryGroupList.add(trajectoryGroup);
363         }
364         // Step 2) accept per GTU
365         Iterator<String> iterator = trajectoryAcceptLists.keySet().iterator();
366         while (iterator.hasNext())
367         {
368             String gtuId = iterator.next();
369             TrajectoryAcceptList trajectoryAcceptListCombined = trajectoryAcceptLists.get(gtuId);
370             trajectoryAcceptListCombined.acceptAll(); // refuse only if any filter data type refuses
371             for (FilterDataType<?, ?> filterDataType : this.filterDataSet.getFilterDataTypes())
372             {
373                 // create safe copy per filter data type, with defaults accepts = false
374                 TrajectoryAcceptList trajectoryAcceptListCopy = copyTrajectoryAcceptList(trajectoryAcceptLists.get(gtuId));
375                 // request filter data type to accept or reject
376                 ((FilterDataType<T, ?>) filterDataType).accept(trajectoryAcceptListCopy,
377                         (Set<T>) new LinkedHashSet<>(this.filterDataSet.get(filterDataType)));
378                 // combine acceptance/rejection of filter data types so far
379                 for (int i = 0; i < trajectoryAcceptListCopy.size(); i++)
380                 {
381                     Trajectory<?> trajectory = trajectoryAcceptListCopy.getTrajectory(i);
382                     trajectoryAcceptListCombined.acceptTrajectory(trajectory,
383                             trajectoryAcceptListCombined.isAccepted(trajectory)
384                                     && trajectoryAcceptListCopy.isAccepted(trajectory));
385                 }
386             }
387         }
388         // Step 3) filter trajectories
389         List<TrajectoryGroup<G>> out = new ArrayList<>();
390         for (TrajectoryGroup<G> full : trajectoryGroupList)
391         {
392             TrajectoryGroup<G> filtered = new TrajectoryGroup<>(full.getStartTime(), full.getLane());
393             for (Trajectory<G> trajectory : full.getTrajectories())
394             {
395                 String gtuId = trajectory.getGtuId();
396                 if (trajectoryAcceptLists.get(gtuId).isAccepted(trajectory))
397                 {
398                     filtered.addTrajectory(trajectory);
399                 }
400             }
401             out.add(filtered);
402         }
403         return out;
404     }
405 
406     /**
407      * Returns a copy of the trajectory accept list, with all assumed not accepted.
408      * @param trajectoryAcceptList TrajectoryAcceptList; trajectory accept list to copy.
409      * @return TrajectoryAcceptList; copy of the trajectory accept list, with all assumed not accepted.
410      */
411     private TrajectoryAcceptList copyTrajectoryAcceptList(final TrajectoryAcceptList trajectoryAcceptList)
412     {
413         TrajectoryAcceptList trajectoryAcceptListCopy = new TrajectoryAcceptList();
414         for (int i = 0; i < trajectoryAcceptList.size(); i++)
415         {
416             trajectoryAcceptListCopy.addTrajectory(trajectoryAcceptList.getTrajectory(i),
417                     trajectoryAcceptList.getTrajectoryGroup(i));
418         }
419         return trajectoryAcceptListCopy;
420     }
421 
422     /**
423      * Returns the sampler.
424      * @return Sampler&lt;G, L&gt;; sampler.
425      */
426     public Sampler<G, L> getSampler()
427     {
428         return this.sampler;
429     }
430 
431     /** {@inheritDoc} */
432     @Override
433     public int hashCode()
434     {
435         final int prime = 31;
436         int result = 1;
437         result = prime * result + ((this.description == null) ? 0 : this.description.hashCode());
438         result = prime * result + ((this.interval == null) ? 0 : this.interval.hashCode());
439         result = prime * result + ((this.filterDataSet == null) ? 0 : this.filterDataSet.hashCode());
440         result = prime * result + ((this.sampler == null) ? 0 : this.sampler.hashCode());
441         result = prime * result + ((this.spaceTimeRegions == null) ? 0 : this.spaceTimeRegions.hashCode());
442         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
443         result = prime * result + ((this.updateFrequency == null) ? 0 : this.updateFrequency.hashCode());
444         return result;
445     }
446 
447     /** {@inheritDoc} */
448     @Override
449     public boolean equals(final Object obj)
450     {
451         if (this == obj)
452         {
453             return true;
454         }
455         if (obj == null)
456         {
457             return false;
458         }
459         if (getClass() != obj.getClass())
460         {
461             return false;
462         }
463         Query<?, ?> other = (Query<?, ?>) obj;
464         if (this.description == null)
465         {
466             if (other.description != null)
467             {
468                 return false;
469             }
470         }
471         else if (!this.description.equals(other.description))
472         {
473             return false;
474         }
475         if (this.interval == null)
476         {
477             if (other.interval != null)
478             {
479                 return false;
480             }
481         }
482         else if (!this.interval.equals(other.interval))
483         {
484             return false;
485         }
486         if (this.filterDataSet == null)
487         {
488             if (other.filterDataSet != null)
489             {
490                 return false;
491             }
492         }
493         else if (!this.filterDataSet.equals(other.filterDataSet))
494         {
495             return false;
496         }
497         if (this.sampler == null)
498         {
499             if (other.sampler != null)
500             {
501                 return false;
502             }
503         }
504         else if (!this.sampler.equals(other.sampler))
505         {
506             return false;
507         }
508         if (this.spaceTimeRegions == null)
509         {
510             if (other.spaceTimeRegions != null)
511             {
512                 return false;
513             }
514         }
515         else if (!this.spaceTimeRegions.equals(other.spaceTimeRegions))
516         {
517             return false;
518         }
519         if (this.id == null)
520         {
521             if (other.id != null)
522             {
523                 return false;
524             }
525         }
526         else if (!this.id.equals(other.id))
527         {
528             return false;
529         }
530         if (this.updateFrequency == null)
531         {
532             if (other.updateFrequency != null)
533             {
534                 return false;
535             }
536         }
537         else if (!this.updateFrequency.equals(other.updateFrequency))
538         {
539             return false;
540         }
541         return true;
542     }
543 
544     /** {@inheritDoc} */
545     @Override
546     public String toString()
547     {
548         return "Query (" + this.description + ")";
549     }
550 
551 }