View Javadoc
1   package org.opentrafficsim.animation.graphs;
2   
3   import java.util.List;
4   import java.util.function.Function;
5   
6   import org.djunits.unit.Unit;
7   import org.djunits.value.vdouble.scalar.Duration;
8   import org.djunits.value.vdouble.scalar.Length;
9   import org.djunits.value.vdouble.scalar.base.DoubleScalarRel;
10  import org.djutils.exceptions.Throw;
11  import org.djutils.math.means.ArithmeticMean;
12  import org.opentrafficsim.animation.BoundsPaintScale;
13  import org.opentrafficsim.animation.egtf.Quantity;
14  import org.opentrafficsim.animation.graphs.ContourDataSource.ContourAdditionalDataType;
15  import org.opentrafficsim.animation.graphs.ContourDataSource.ContourDataType;
16  import org.opentrafficsim.kpi.sampling.SamplingException;
17  import org.opentrafficsim.kpi.sampling.Trajectory;
18  import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
19  import org.opentrafficsim.kpi.sampling.data.ExtendedDataNumber;
20  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
21  
22  /**
23   * Contour data plot for any numerical extended trajectory data. For extended trajectory data valued with a DJUNITS type,
24   * sub-class {@link UnitPlot} is available.
25   * <p>
26   * Copyright (c) 2026-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
27   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
28   * </p>
29   * @author Wouter Schakel
30   * @param <Z> value type
31   */
32  public class ContourPlotExtendedData<Z extends Number> extends AbstractContourPlot<Z>
33  {
34  
35      /**
36       * Constructor with default paint scale (red at minimum, yellow at mid-point, green at maximum).
37       * @param caption caption
38       * @param source data source
39       * @param extendedDataType extended data type
40       * @param valueConverter value converter
41       * @param bounds paint scale bounds
42       * @param labelData label data
43       */
44      public ContourPlotExtendedData(final String caption, final ContourDataSource source,
45              final ExtendedDataNumber<?> extendedDataType, final Function<Double, Z> valueConverter, final Bounds<Z> bounds,
46              final LabelData<Z> labelData)
47      {
48          super(caption, source, constructDataType(extendedDataType, valueConverter), bounds, labelData);
49      }
50  
51      /**
52       * Constructor with specified paint scale.
53       * @param caption caption
54       * @param source data source
55       * @param extendedDataType extended data type
56       * @param valueConverter value converter
57       * @param paintScale paint scale
58       * @param labelData label data
59       */
60      public ContourPlotExtendedData(final String caption, final ContourDataSource source,
61              final ExtendedDataNumber<?> extendedDataType, final Function<Double, Z> valueConverter,
62              final BoundsPaintScale paintScale, final LabelData<Z> labelData)
63      {
64          super(caption, source, constructDataType(extendedDataType, valueConverter), paintScale, labelData);
65      }
66  
67      /**
68       * This method is a total hack, as super will call getContourDataType() which will return this.contourDataType, which at
69       * that point has not been set. So we set one statically before it is called, by forwarding the caption argument through
70       * this method.
71       * @param <Z> value type
72       * @param extendedDataType extended data type
73       * @param valueConverter value converter
74       * @return contour data type
75       */
76      private static <Z extends Number> ExtendedContourDataType<Z> constructDataType(final ExtendedDataNumber<?> extendedDataType,
77              final Function<Double, Z> valueConverter)
78      {
79          Quantity<Z, double[][]> quantity = Quantity.si("extended_data_" + extendedDataType.getId());
80          return new ExtendedContourDataType<>(extendedDataType, quantity, valueConverter);
81      }
82  
83      @Override
84      public GraphType getGraphType()
85      {
86          return GraphType.OTHER;
87      }
88  
89      @Override
90      protected double scale(final double si)
91      {
92          return si;
93      }
94  
95      @Override
96      public String toString()
97      {
98          return "ContourPlotExtendedData [" + getCaption() + "]";
99      }
100 
101     /**
102      * Attention contour data type.
103      * @param <Z> value type
104      */
105     private static class ExtendedContourDataType<Z extends Number>
106             implements ContourAdditionalDataType<Z, ArithmeticMean<Double, Double>>
107     {
108 
109         /** Extended data type. */
110         private final ExtendedDataType<?, ? extends float[], ?, ?> dataType;
111 
112         /** Quantity. */
113         private final Quantity<Z, ?> quantity;
114 
115         /** Value converter. */
116         private final Function<Double, Z> valueConverter;
117 
118         /**
119          * Constructor.
120          * @param dataType extended data type
121          * @param quantity quantity
122          * @param valueConverter value converter
123          */
124         ExtendedContourDataType(final ExtendedDataType<?, ? extends float[], ?, ?> dataType, final Quantity<Z, ?> quantity,
125                 final Function<Double, Z> valueConverter)
126         {
127             this.dataType = dataType;
128             this.quantity = quantity;
129             this.valueConverter = valueConverter;
130         }
131 
132         @Override
133         public ArithmeticMean<Double, Double> identity()
134         {
135             return new ArithmeticMean<>();
136         }
137 
138         @Override
139         public ArithmeticMean<Double, Double> processSeries(final ArithmeticMean<Double, Double> intermediate,
140                 final List<TrajectoryGroup<?>> trajectories, final List<Length> xFrom, final List<Length> xTo,
141                 final Duration tFrom, final Duration tTo)
142         {
143             for (int i = 0; i < trajectories.size(); i++)
144             {
145                 TrajectoryGroup<?> trajectoryGroup = trajectories.get(i);
146                 for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
147                 {
148                     if (GraphUtil.considerTrajectory(trajectory, tFrom, tTo))
149                     {
150                         trajectory = trajectory.subSet(xFrom.get(i), xTo.get(i), tFrom, tTo);
151                         try
152                         {
153                             ContourDataType.weightedNaN(trajectory.getExtendedData(this.dataType), trajectory.getX(),
154                                     intermediate);
155                         }
156                         catch (SamplingException ex)
157                         {
158                             throw new RuntimeException(ex);
159                         }
160                     }
161                 }
162             }
163             return intermediate;
164         }
165 
166         @Override
167         public Z finalize(final ArithmeticMean<Double, Double> intermediate)
168         {
169             return this.valueConverter.apply(intermediate.getMean());
170         }
171 
172         @Override
173         public Quantity<Z, ?> getQuantity()
174         {
175             return this.quantity;
176         }
177 
178         @Override
179         public boolean normalize()
180         {
181             return false;
182         }
183 
184     };
185 
186     /**
187      * Extension of {@link ContourPlotExtendedData} for DJUNITS data types. The legend format will be <code>"%.1f{unit}"</code>.
188      * The label format will be <code>"{quantity} %.2f {unit}"</code>. A last digit in the unit will be made in to a
189      * superscript.
190      * @param <U> unit type
191      * @param <Z> unit value type
192      */
193     public static class UnitPlot<U extends Unit<U>, Z extends DoubleScalarRel<U, Z>> extends ContourPlotExtendedData<Z>
194     {
195 
196         /** Character codes for superscripts 0-9. */
197         private static final char[] SUPER =
198                 {'\u2070', '\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'};
199 
200         /** One value. */
201         private final Z one;
202 
203         /** Unit. */
204         private final U unit;
205 
206         /**
207          * Constructor with default paint scale (red at minimum, yellow at mid-point, green at maximum).
208          * @param caption caption
209          * @param source data source
210          * @param extendedDataType extended data type
211          * @param bounds paint scale bounds
212          * @param legendStep legend step
213          * @param unit unit to display values in
214          */
215         public UnitPlot(final String caption, final ContourDataSource source, final ExtendedDataNumber<?> extendedDataType,
216                 final Bounds<Z> bounds, final Z legendStep, final U unit)
217         {
218             super(caption, source, extendedDataType, getConverter(legendStep), bounds, constructLabelData(legendStep, unit));
219             this.one = legendStep.divide(legendStep.si);
220             this.unit = unit;
221         }
222 
223         /**
224          * Constructor with specified paint scale.
225          * @param caption caption
226          * @param source data source
227          * @param extendedDataType extended data type
228          * @param paintScale paint scale
229          * @param legendStep legend step
230          * @param unit unit to display values in
231          */
232         public UnitPlot(final String caption, final ContourDataSource source, final ExtendedDataNumber<?> extendedDataType,
233                 final BoundsPaintScale paintScale, final Z legendStep, final U unit)
234         {
235             super(caption, source, extendedDataType, getConverter(legendStep), paintScale,
236                     constructLabelData(legendStep, unit));
237             this.one = legendStep.divide(legendStep.si);
238             this.unit = unit;
239         }
240 
241         /**
242          * Constructs value converter.
243          * @param <U> unit type
244          * @param <Z> unit value type
245          * @param legendStep legend step
246          * @return value converter
247          */
248         private static <U extends Unit<U>, Z extends DoubleScalarRel<U, Z>> Function<Double, Z> getConverter(final Z legendStep)
249         {
250             // TODO: use unit to create value with new DJUNITS version
251             Throw.whenNull(legendStep, "legendStep");
252             Z one = legendStep.divide(legendStep.si);
253             return (v) -> one.times(v);
254         }
255 
256         /**
257          * Constructs label data. The legend format will be <code>"%.1f{unit}"</code>. The label format will be
258          * <code>"{quantity} %.2f {unit}"</code>. A last digit in the unit will be made in to a superscript.
259          * @param <U> unit type
260          * @param <Z> unit value type
261          * @param legendStep legend step
262          * @param unit unit
263          * @return label data
264          */
265         private static <U extends Unit<U>, Z extends DoubleScalarRel<U, Z>> LabelData<Z> constructLabelData(final Z legendStep,
266                 final U unit)
267         {
268             String unitString = unit.getId();
269             // superscript last digit
270             if (unitString != null && !unitString.isEmpty())
271             {
272                 int last = unitString.length() - 1;
273                 char c = unitString.charAt(last);
274                 if (Character.isDigit(c))
275                 {
276                     unitString = unitString.substring(0, last) + SUPER[c - '0'];
277                 }
278             }
279             return new LabelData<>(legendStep, "%.1f" + unitString,
280                     "%.2f " + unit.getQuantity().getName().toLowerCase() + " " + unitString);
281         }
282 
283         @Override
284         protected double scale(final double si)
285         {
286             // TODO: use unit to create value with new DJUNITS version
287             return this.one.times(si).getInUnit(this.unit);
288         }
289 
290     }
291 }