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