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