altoslib: Don't record 'pad' state in FlightSeries
[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                 double ret;
63
64                 if (after == 0)
65                         ret = values.get(0).value;
66                 else if (after == values.size())
67                         ret = values.get(after - 1).value;
68                 else {
69                         AltosTimeValue b = values.get(after-1);
70                         AltosTimeValue a = values.get(after);
71                         ret = lerp(b, a, time);
72                 }
73                 return ret;
74         }
75
76         /* Find the value just before an arbitrary time */
77         public double value_before(double time) {
78                 int after = after_index(time);
79
80                 if (after == 0)
81                         return values.get(0).value;
82                 return values.get(after-1).value;
83         }
84
85         /* Find the value just after an arbitrary time */
86         public double value_after(double time) {
87                 int after = after_index(time);
88
89                 if (after == values.size())
90                         return values.get(after-1).value;
91                 return values.get(after).value;
92         }
93
94         public double time_of(double value) {
95                 double  last = AltosLib.MISSING;
96                 for (AltosTimeValue v : values) {
97                         if (v.value >= value)
98                                 return v.time;
99                         last = v.time;
100                 }
101                 return last;
102         }
103
104         public int size() {
105                 return values.size();
106         }
107
108         public Iterator<AltosTimeValue> iterator() {
109                 return values.iterator();
110         }
111
112         public AltosTimeValue max() {
113                 AltosTimeValue max = null;
114                 for (AltosTimeValue tv : values)
115                         if (max == null || tv.value > max.value)
116                                 max = tv;
117                 return max;
118         }
119
120         public AltosTimeValue max(double start_time, double end_time) {
121                 AltosTimeValue max = null;
122                 for (AltosTimeValue tv : values) {
123                         if (start_time <= tv.time && tv.time <= end_time)
124                                 if (max == null || tv.value > max.value)
125                                         max = tv;
126                 }
127                 return max;
128         }
129
130         public AltosTimeValue min() {
131                 AltosTimeValue min = null;
132                 for (AltosTimeValue tv : values) {
133                         if (min == null || tv.value < min.value)
134                                 min = tv;
135                 }
136                 return min;
137         }
138
139         public AltosTimeValue min(double start_time, double end_time) {
140                 AltosTimeValue min = null;
141                 for (AltosTimeValue tv : values) {
142                         if (start_time <= tv.time && tv.time <= end_time)
143                                 if (min == null || tv.value < min.value)
144                                         min = tv;
145                 }
146                 return min;
147         }
148
149         public double average() {
150                 double total = 0;
151                 int count = 0;
152                 for (AltosTimeValue tv : values) {
153                         total += tv.value;
154                         count++;
155                 }
156                 if (count == 0)
157                         return AltosLib.MISSING;
158                 return total / count;
159         }
160
161         public double average(double start_time, double end_time) {
162                 double total = 0;
163                 int count = 0;
164                 for (AltosTimeValue tv : values) {
165                         if (start_time <= tv.time && tv.time <= end_time) {
166                                 total += tv.value;
167                                 count++;
168                         }
169                 }
170                 if (count == 0)
171                         return AltosLib.MISSING;
172                 return total / count;
173         }
174
175         public AltosTimeSeries integrate(AltosTimeSeries integral) {
176                 double  value = 0.0;
177                 double  pvalue = 0.0;
178                 double  time = 0.0;
179                 boolean start = true;
180
181                 for (AltosTimeValue v : values) {
182                         if (start) {
183                                 value = 0.0;
184                                 start = false;
185                         } else {
186                                 value += (pvalue + v.value) / 2.0 * (v.time - time);
187                         }
188                         pvalue = v.value;
189                         time = v.time;
190                         integral.add(time, value);
191
192                 }
193                 return integral;
194         }
195
196         public AltosTimeSeries differentiate(AltosTimeSeries diff) {
197                 double value = 0.0;
198                 double time = 0.0;
199                 boolean start = true;
200
201                 for (AltosTimeValue v: values) {
202                         if (start) {
203                                 value = v.value;
204                                 time = v.time;
205                                 start = false;
206                         } else {
207                                 double  dx = v.time - time;
208                                 double  dy = v.value - value;
209
210                                 if (dx != 0)
211                                         diff.add(time, dy/dx);
212
213                                 time = v.time;
214                                 value = v.value;
215                         }
216                 }
217                 return diff;
218         }
219
220         private int find_left(int i, double dt) {
221                 int j;
222                 double t = values.get(i).time - dt;
223                 for (j = i; j >= 0; j--)        {
224                         if (values.get(j).time < t)
225                                 break;
226                 }
227                 return j + 1;
228
229         }
230
231         private int find_right(int i, double dt) {
232                 int j;
233                 double t = values.get(i).time + dt;
234                 for (j = i; j < values.size(); j++) {
235                         if (values.get(j).time > t)
236                                 break;
237                 }
238                 return j - 1;
239
240         }
241
242         private double filter_coeff(double dist, double width) {
243                 double ratio = dist / (width / 2);
244
245                 return Math.cos(ratio * Math.PI / 2);
246         }
247
248         public AltosTimeSeries filter(AltosTimeSeries f, double width) {
249                 double  half_width = width/2;
250                 for (int i = 0; i < values.size(); i++) {
251                         double  center_time = values.get(i).time;
252                         double  left_time = center_time - half_width;
253                         double  right_time = center_time + half_width;
254                         double  total_coeff = 0.0;
255                         double  total_value = 0.0;
256
257                         int     left = find_left(i, half_width);
258                         int     right = find_right(i, half_width);
259
260                         for (int j = left; j <= right; j++) {
261                                 double  j_time = values.get(j).time;
262
263                                 if (left_time <= j_time && j_time <= right_time) {
264                                         double  j_left = j == left ? left_time : values.get(j-1).time;
265                                         double  j_right = j == right ? right_time : values.get(j+1).time;
266                                         double  interval = (j_right - j_left) / 2.0;
267                                         double  coeff = filter_coeff(j_time - center_time, width) * interval;
268                                         double  value = values.get(j).value;
269                                         double  partial = value * coeff;
270
271                                         total_coeff += coeff;
272                                         total_value += partial;
273                                 }
274                         }
275                         if (total_coeff != 0.0)
276                                 f.add(center_time, total_value / total_coeff);
277                 }
278                 return f;
279         }
280
281         public AltosTimeSeries(String label, AltosUnits units) {
282                 this.label = label;
283                 this.units = units;
284                 this.values = new ArrayList<AltosTimeValue>();
285         }
286 }