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