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