altoslib: Make sure AltosFlightSeries is filled in before use
[fw/altos] / altoslib / AltosTimeSeries.java
1 /*
2  * Copyright © 2017 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  */
14
15 package org.altusmetrum.altoslib_11;
16
17 import java.util.*;
18
19 public class AltosTimeSeries implements Iterable<AltosTimeValue> {
20         public String                   label;
21         public AltosUnits               units;
22         ArrayList<AltosTimeValue>       values;
23
24         public void add(AltosTimeValue tv) {
25                 values.add(tv);
26         }
27
28         public void add(double time, double value) {
29                 add(new AltosTimeValue(time, value));
30         }
31
32         public AltosTimeValue get(int i) {
33                 return values.get(i);
34         }
35
36         private double lerp(AltosTimeValue v0, AltosTimeValue v1, double t) {
37                 /* degenerate case */
38                 if (v0.time == v1.time)
39                         return (v0.value + v1.value) / 2;
40
41                 return (v0.value * (v1.time - t) + v1.value * (t - v0.time)) / v1.time - v0.time;
42         }
43
44         private int after_index(double time) {
45                 int     lo = 0;
46                 int     hi = values.size() - 1;
47
48                 while (lo <= hi) {
49                         int mid = (lo + hi) / 2;
50
51                         if (values.get(mid).time < time)
52                                 lo = mid + 1;
53                         else
54                                 hi = mid - 1;
55                 }
56                 return lo;
57         }
58
59         /* Compute a value for an arbitrary time */
60         public double value(double time) {
61                 int after = after_index(time);
62                 if (after == 0)
63                         return values.get(0).value;
64                 if (after == values.size())
65                         return values.get(after - 1).value;
66
67                 return lerp(values.get(after-1), values.get(after), time);
68         }
69
70         /* Find the value just before an arbitrary time */
71         public double value_before(double time) {
72                 int after = after_index(time);
73
74                 if (after == 0)
75                         return values.get(0).value;
76                 return values.get(after-1).value;
77         }
78
79         /* Find the value just after an arbitrary time */
80         public double value_after(double time) {
81                 int after = after_index(time);
82
83                 if (after == values.size())
84                         return values.get(after-1).value;
85                 return values.get(after).value;
86         }
87
88         public double time_of(double value) {
89                 double  last = AltosLib.MISSING;
90                 for (AltosTimeValue v : values) {
91                         if (v.value >= value)
92                                 return v.time;
93                         last = v.time;
94                 }
95                 return last;
96         }
97
98         public int size() {
99                 return values.size();
100         }
101
102         public Iterator<AltosTimeValue> iterator() {
103                 return values.iterator();
104         }
105
106         public double max() {
107                 double max = AltosLib.MISSING;
108                 for (AltosTimeValue tv : values) {
109                         if (max == AltosLib.MISSING || tv.value > max)
110                                 max = tv.value;
111                 }
112                 return max;
113         }
114
115         public double max(double start_time, double end_time) {
116                 double max = AltosLib.MISSING;
117                 for (AltosTimeValue tv : values) {
118                         if (start_time <= tv.time && tv.time <= end_time)
119                                 if (max == AltosLib.MISSING || tv.value > max)
120                                         max = tv.value;
121                 }
122                 return max;
123         }
124
125         public double min() {
126                 double min = AltosLib.MISSING;
127                 for (AltosTimeValue tv : values) {
128                         if (min == AltosLib.MISSING || tv.value < min)
129                                 min = tv.value;
130                 }
131                 return min;
132         }
133
134         public double min(double start_time, double end_time) {
135                 double min = AltosLib.MISSING;
136                 for (AltosTimeValue tv : values) {
137                         if (start_time <= tv.time && tv.time <= end_time)
138                                 if (min == AltosLib.MISSING || tv.value < min)
139                                         min = tv.value;
140                 }
141                 return min;
142         }
143
144         public double average() {
145                 double total = 0;
146                 int count = 0;
147                 for (AltosTimeValue tv : values) {
148                         total += tv.value;
149                         count++;
150                 }
151                 if (count == 0)
152                         return AltosLib.MISSING;
153                 return total / count;
154         }
155
156         public double average(double start_time, double end_time) {
157                 double total = 0;
158                 int count = 0;
159                 for (AltosTimeValue tv : values) {
160                         if (start_time <= tv.time && tv.time <= end_time) {
161                                 total += tv.value;
162                                 count++;
163                         }
164                 }
165                 if (count == 0)
166                         return AltosLib.MISSING;
167                 return total / count;
168         }
169
170         public AltosTimeSeries integrate(AltosTimeSeries integral) {
171                 double  value = 0.0;
172                 double  pvalue = 0.0;
173                 double  time = 0.0;
174                 boolean start = true;
175
176                 for (AltosTimeValue v : values) {
177                         if (start) {
178                                 value = 0.0;
179                                 start = false;
180                         } else {
181                                 value += (pvalue + v.value) / 2.0 * (v.time - time);
182                         }
183                         pvalue = v.value;
184                         time = v.time;
185 //                      System.out.printf("%g %g %g\n", time, v.value, value);
186                         integral.add(time, value);
187
188                 }
189                 return integral;
190         }
191
192         public AltosTimeSeries differentiate(AltosTimeSeries diff) {
193                 double value = 0.0;
194                 double time = 0.0;
195                 boolean start = true;
196
197                 for (AltosTimeValue v: values) {
198                         if (start) {
199                                 value = v.value;
200                                 time = v.time;
201                                 start = false;
202                         } else {
203                                 double  dx = v.time - time;
204                                 double  dy = v.value - value;
205
206                                 if (dx != 0)
207                                         diff.add(time, dy/dx);
208
209                                 time = v.time;
210                                 value = v.value;
211                         }
212                 }
213                 return diff;
214         }
215
216         private int find_left(int i, double dt) {
217                 int j;
218                 double t = values.get(i).time - dt;
219                 for (j = i; j >= 0; j--)        {
220                         if (values.get(j).time < t)
221                                 break;
222                 }
223                 return j + 1;
224
225         }
226
227         private int find_right(int i, double dt) {
228                 int j;
229                 double t = values.get(i).time + dt;
230                 for (j = i; j < values.size(); j++) {
231                         if (values.get(j).time > t)
232                                 break;
233                 }
234                 return j - 1;
235
236         }
237
238         private double filter_coeff(double dist, double width) {
239                 double ratio = dist / (width / 2);
240
241                 return Math.cos(ratio * Math.PI / 2);
242         }
243
244         public AltosTimeSeries filter(AltosTimeSeries f, double width) {
245                 double  half_width = width/2;
246                 for (int i = 0; i < values.size(); i++) {
247                         double  center_time = values.get(i).time;
248                         double  left_time = center_time - half_width;
249                         double  right_time = center_time + half_width;
250                         double  total_coeff = 0.0;
251                         double  total_value = 0.0;
252
253                         int     left = find_left(i, half_width);
254                         int     right = find_right(i, half_width);
255
256                         for (int j = left; j <= right; j++) {
257                                 double  j_time = values.get(j).time;
258
259                                 if (left_time <= j_time && j_time <= right_time) {
260                                         double  j_left = j == left ? left_time : values.get(j-1).time;
261                                         double  j_right = j == right ? right_time : values.get(j+1).time;
262                                         double  interval = (j_right - j_left) / 2.0;
263                                         double  coeff = filter_coeff(j_time - center_time, width) * interval;
264
265                                         total_coeff += coeff;
266                                         total_value += coeff * values.get(j).value;
267                                 }
268                         }
269                         if (total_coeff != 0.0)
270                                 f.add(center_time, total_value / total_coeff);
271                 }
272                 return f;
273         }
274
275         public AltosTimeSeries(String label, AltosUnits units) {
276                 this.label = label;
277                 this.units = units;
278                 this.values = new ArrayList<AltosTimeValue>();
279         }
280 }