1 package org.opentrafficsim.core.geometry;
2
3 import java.util.Map;
4 import java.util.Map.Entry;
5 import java.util.NavigableMap;
6 import java.util.TreeMap;
7
8 import org.djutils.exceptions.Throw;
9 import org.djutils.immutablecollections.ImmutableLinkedHashSet;
10 import org.djutils.immutablecollections.ImmutableNavigableSet;
11 import org.djutils.immutablecollections.ImmutableSet;
12 import org.djutils.immutablecollections.ImmutableTreeSet;
13 import org.opentrafficsim.core.geometry.ContinuousLine.ContinuousDoubleFunction;
14
15
16
17
18
19
20
21
22
23 public class FractionalLengthData implements ContinuousDoubleFunction
24 {
25
26
27 private final NavigableMap<Double, Double> data = new TreeMap<>();
28
29
30
31
32
33
34
35 public FractionalLengthData(final double... data) throws IllegalArgumentException
36 {
37 Throw.when(data.length < 2 || data.length % 2 > 0, IllegalArgumentException.class,
38 "Number of input values must be even and at least 2.");
39 for (int i = 0; i < data.length; i = i + 2)
40 {
41 Throw.when(data[i] < 0.0 || data[i] > 1.0, IllegalArgumentException.class,
42 "Fractional length %s is outside of range [0 ... 1].", data[i]);
43 this.data.put(data[i], data[i + 1]);
44 }
45 }
46
47
48
49
50
51
52
53 public FractionalLengthData(final Map<Double, Double> data) throws IllegalArgumentException
54 {
55 Throw.when(data == null || data.isEmpty(), IllegalArgumentException.class, "Input data is empty or null.");
56 for (Entry<Double, Double> entry : data.entrySet())
57 {
58 Throw.when(entry.getKey() < 0.0 || entry.getKey() > 1.0, IllegalArgumentException.class,
59 "Fractional length %s is outside of range [0 ... 1].", entry.getKey());
60 this.data.put(entry.getKey(), entry.getValue());
61 }
62 }
63
64
65
66
67
68
69
70
71 @Override
72 public Double apply(final Double fractionalLength)
73 {
74 Double exact = this.data.get(fractionalLength);
75 if (exact != null)
76 {
77 return exact;
78 }
79 Entry<Double, Double> ceiling = this.data.ceilingEntry(fractionalLength);
80 if (ceiling == null)
81 {
82 return this.data.lastEntry().getValue();
83 }
84 Entry<Double, Double> floor = this.data.floorEntry(fractionalLength);
85 if (floor == null)
86 {
87 return this.data.firstEntry().getValue();
88 }
89 double w = (fractionalLength - floor.getKey()) / (ceiling.getKey() - floor.getKey());
90 return (1.0 - w) * floor.getValue() + w * ceiling.getValue();
91 }
92
93
94
95
96
97
98 @Override
99 public double getDerivative(final double fractionalLength)
100 {
101 Entry<Double, Double> ceiling, floor;
102 if (fractionalLength == 0.0)
103 {
104 ceiling = this.data.higherEntry(fractionalLength);
105 floor = this.data.floorEntry(fractionalLength);
106 }
107 else
108 {
109 ceiling = this.data.ceilingEntry(fractionalLength);
110 floor = this.data.lowerEntry(fractionalLength);
111 }
112 if (ceiling == null || floor == null)
113 {
114 return 0.0;
115 }
116 return (ceiling.getValue() - floor.getValue()) / (ceiling.getKey() - floor.getKey());
117 }
118
119
120
121
122
123 public ImmutableNavigableSet<Double> getFractionalLengths()
124 {
125 return new ImmutableTreeSet<>(this.data.keySet());
126 }
127
128
129
130
131
132 public ImmutableSet<Double> getValues()
133 {
134 return new ImmutableLinkedHashSet<>(this.data.values());
135 }
136
137
138
139
140
141 @Override
142 public double[] getKnots()
143 {
144 NavigableMap<Double, Double> full = fullRange();
145 double[] fractionalLengths = new double[full.size()];
146 int i = 0;
147 for (double f : full.navigableKeySet())
148 {
149 fractionalLengths[i++] = f;
150 }
151 return fractionalLengths;
152 }
153
154
155
156
157
158 public double[] getValuesAsArray()
159 {
160 NavigableMap<Double, Double> full = fullRange();
161 double[] values = new double[full.size()];
162 int i = 0;
163 for (double f : full.navigableKeySet())
164 {
165 values[i++] = full.get(f);
166 }
167 return values;
168 }
169
170
171
172
173
174 private NavigableMap<Double, Double> fullRange()
175 {
176 NavigableMap<Double, Double> full = new TreeMap<>(this.data);
177 full.put(0.0, full.firstEntry().getValue());
178 full.put(1.0, full.lastEntry().getValue());
179 return full;
180 }
181
182
183
184
185
186 public int size()
187 {
188 return this.data.size();
189 }
190
191
192
193
194
195
196
197
198 public static FractionalLengthData of(final double... data) throws IllegalArgumentException
199 {
200 return new FractionalLengthData(data);
201 }
202
203 }