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.immutablecollections.ImmutableIterator;
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.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 sampling;
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 sampling sampling
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 sampling, final String id, final String description, final MetaDataSet metaDataSet)
70      {
71          this(sampling, description, metaDataSet, null, null);
72      }
73  
74      /**
75       * @param sampling sampling
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 sampling, final String id, final String description, final MetaDataSet metaDataSet, final Duration interval)
83      {
84          this(sampling, description, metaDataSet, null, interval);
85      }
86  
87      /**
88       * @param sampling sampling
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 sampling, final String id, final String description, final MetaDataSet metaDataSet,
96              final Frequency updateFrequency)
97      {
98          this(sampling, description, metaDataSet, updateFrequency, null);
99      }
100 
101     /**
102      * @param sampling sampling
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 sampling, final String id, final String description, final MetaDataSet metaDataSet,
111             final Frequency updateFrequency, final Duration interval)
112     {
113         Throw.whenNull(sampling, "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.sampling = sampling;
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         sampling.registerMetaDataTypes(metaDataSet.getMetaDataTypes());
123     }
124     
125     /**
126      * @param sampling sampling
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 sampling, final String description, final MetaDataSet metaDataSet)
132     {
133         this(sampling, null, description, metaDataSet, null, null);
134     }
135 
136     /**
137      * @param sampling sampling
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 sampling, final String description, final MetaDataSet metaDataSet, final Duration interval)
144     {
145         this(sampling, null, description, metaDataSet, null, interval);
146     }
147 
148     /**
149      * @param sampling sampling
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 sampling, final String description, final MetaDataSet metaDataSet,
156             final Frequency updateFrequency)
157     {
158         this(sampling, null, description, metaDataSet, updateFrequency, null);
159     }
160 
161     /**
162      * @param sampling sampling
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 sampling, final String description, final MetaDataSet metaDataSet,
170             final Frequency updateFrequency, final Duration interval)
171     {
172         this(sampling, 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.sampling.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.
297      * @param startTime start time of interval to get trajectory groups for
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     @SuppressWarnings("unchecked")
303     public <T> List<TrajectoryGroup> getTrajectoryGroups(final Time startTime, final Time endTime)
304     {
305         Throw.whenNull(startTime, "Start t may not be null.");
306         Throw.whenNull(endTime, "End t may not be null.");
307         // Step 1) gather trajectories per GTU, truncated over space and time
308         Map<String, TrajectoryAcceptList> trajectoryAcceptLists = new HashMap<>();
309         List<TrajectoryGroup> trajectoryGroupList = new ArrayList<>();
310         for (SpaceTimeRegion spaceTimeRegion : this.spaceTimeRegions)
311         {
312             Time start = startTime.gt(spaceTimeRegion.getStartTime()) ? startTime : spaceTimeRegion.getStartTime();
313             Time end = endTime.lt(spaceTimeRegion.getEndTime()) ? endTime : spaceTimeRegion.getEndTime();
314             TrajectoryGroup trajectoryGroup;
315             if (this.sampling.getTrajectoryGroup(spaceTimeRegion.getLaneDirection()) == null)
316             {
317                 trajectoryGroup = new TrajectoryGroup(start, spaceTimeRegion.getLaneDirection());
318             }
319             else
320             {
321                 trajectoryGroup = this.sampling.getTrajectoryGroup(spaceTimeRegion.getLaneDirection())
322                         .getTrajectoryGroup(spaceTimeRegion.getStartPosition(), spaceTimeRegion.getEndPosition(), start, end);
323             }
324             for (Trajectory trajectory : trajectoryGroup.getTrajectories())
325             {
326                 if (!trajectoryAcceptLists.containsKey(trajectory.getGtuId()))
327                 {
328                     trajectoryAcceptLists.put(trajectory.getGtuId(), new TrajectoryAcceptList());
329                 }
330                 trajectoryAcceptLists.get(trajectory.getGtuId()).addTrajectory(trajectory, trajectoryGroup);
331             }
332             trajectoryGroupList.add(trajectoryGroup);
333         }
334         // Step 2) accept per GTU
335         Iterator<String> iterator = trajectoryAcceptLists.keySet().iterator();
336         while (iterator.hasNext())
337         {
338             String gtuId = iterator.next();
339             TrajectoryAcceptList trajectoryAcceptListCombined = trajectoryAcceptLists.get(gtuId);
340             trajectoryAcceptListCombined.acceptAll(); // refuse only if any meta data type refuses
341             for (MetaDataType<?> metaDataType : this.metaDataSet.getMetaDataTypes())
342             {
343                 // create safe copy per meta data type, with defaults accepts = false
344                 TrajectoryAcceptList trajectoryAcceptList = trajectoryAcceptLists.get(gtuId);
345                 TrajectoryAcceptList trajectoryAcceptListCopy = new TrajectoryAcceptList();
346                 for (int i = 0; i < trajectoryAcceptList.size(); i++)
347                 {
348                     trajectoryAcceptListCopy.addTrajectory(trajectoryAcceptList.getTrajectory(i),
349                             trajectoryAcceptList.getTrajectoryGroup(i));
350                 }
351                 // request meta data type to accept or reject
352                 ((MetaDataType<T>) metaDataType).accept(trajectoryAcceptListCopy,
353                         (Set<T>) new HashSet<>(this.metaDataSet.get(metaDataType)));
354                 // combine acceptance/rejection of meta data type so far
355                 for (int i = 0; i < trajectoryAcceptListCopy.size(); i++)
356                 {
357                     Trajectory trajectory = trajectoryAcceptListCopy.getTrajectory(i);
358                     trajectoryAcceptListCombined.acceptTrajectory(trajectory,
359                             trajectoryAcceptListCombined.isAccepted(trajectory)
360                                     && trajectoryAcceptListCopy.isAccepted(trajectory));
361                 }
362             }
363         }
364         // Step 3) filter trajectories
365         List<TrajectoryGroup> out = new ArrayList<>();
366         for (TrajectoryGroup full : trajectoryGroupList)
367         {
368             TrajectoryGroup filtered = new TrajectoryGroup(full.getStartTime(), full.getLaneDirection());
369             for (Trajectory trajectory : full.getTrajectories())
370             {
371                 String gtuId = trajectory.getGtuId();
372                 if (trajectoryAcceptLists.get(gtuId).isAccepted(trajectory))
373                 {
374                     filtered.addTrajectory(trajectory);
375                 }
376             }
377             out.add(filtered);
378         }
379         return out;
380     }
381 
382     /** {@inheritDoc} */
383     @Override
384     public int hashCode()
385     {
386         final int prime = 31;
387         int result = 1;
388         result = prime * result + ((this.description == null) ? 0 : this.description.hashCode());
389         result = prime * result + ((this.interval == null) ? 0 : this.interval.hashCode());
390         result = prime * result + ((this.metaDataSet == null) ? 0 : this.metaDataSet.hashCode());
391         result = prime * result + ((this.sampling == null) ? 0 : this.sampling.hashCode());
392         result = prime * result + ((this.spaceTimeRegions == null) ? 0 : this.spaceTimeRegions.hashCode());
393         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
394         result = prime * result + ((this.updateFrequency == null) ? 0 : this.updateFrequency.hashCode());
395         return result;
396     }
397 
398     /** {@inheritDoc} */
399     @Override
400     public boolean equals(final Object obj)
401     {
402         if (this == obj)
403         {
404             return true;
405         }
406         if (obj == null)
407         {
408             return false;
409         }
410         if (getClass() != obj.getClass())
411         {
412             return false;
413         }
414         Query other = (Query) obj;
415         if (this.description == null)
416         {
417             if (other.description != null)
418             {
419                 return false;
420             }
421         }
422         else if (!this.description.equals(other.description))
423         {
424             return false;
425         }
426         if (this.interval == null)
427         {
428             if (other.interval != null)
429             {
430                 return false;
431             }
432         }
433         else if (!this.interval.equals(other.interval))
434         {
435             return false;
436         }
437         if (this.metaDataSet == null)
438         {
439             if (other.metaDataSet != null)
440             {
441                 return false;
442             }
443         }
444         else if (!this.metaDataSet.equals(other.metaDataSet))
445         {
446             return false;
447         }
448         if (this.sampling == null)
449         {
450             if (other.sampling != null)
451             {
452                 return false;
453             }
454         }
455         else if (!this.sampling.equals(other.sampling))
456         {
457             return false;
458         }
459         if (this.spaceTimeRegions == null)
460         {
461             if (other.spaceTimeRegions != null)
462             {
463                 return false;
464             }
465         }
466         else if (!this.spaceTimeRegions.equals(other.spaceTimeRegions))
467         {
468             return false;
469         }
470         if (this.id == null)
471         {
472             if (other.id != null)
473             {
474                 return false;
475             }
476         }
477         else if (!this.id.equals(other.id))
478         {
479             return false;
480         }
481         if (this.updateFrequency == null)
482         {
483             if (other.updateFrequency != null)
484             {
485                 return false;
486             }
487         }
488         else if (!this.updateFrequency.equals(other.updateFrequency))
489         {
490             return false;
491         }
492         return true;
493     }
494 
495     /** {@inheritDoc} */
496     @Override
497     public String toString()
498     {
499         return "Query (" + this.description + ")";
500     }
501 
502 }