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