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