1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.models.earth.weather;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.nio.charset.StandardCharsets;
24 import java.text.ParseException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.concurrent.atomic.AtomicReference;
30 import java.util.function.ToDoubleFunction;
31 import java.util.regex.Pattern;
32
33 import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
34 import org.hipparchus.util.FastMath;
35 import org.hipparchus.util.MathUtils;
36 import org.hipparchus.util.SinCos;
37 import org.orekit.annotation.DefaultDataContext;
38 import org.orekit.data.DataContext;
39 import org.orekit.data.DataLoader;
40 import org.orekit.data.DataProvidersManager;
41 import org.orekit.errors.OrekitException;
42 import org.orekit.errors.OrekitMessages;
43 import org.orekit.models.earth.Geoid;
44 import org.orekit.models.earth.troposphere.ViennaOneModel;
45 import org.orekit.time.AbsoluteDate;
46 import org.orekit.time.TimeScale;
47 import org.orekit.utils.Constants;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class GlobalPressureTemperature2Model implements WeatherModel {
84
85
86 public static final String DEFAULT_SUPPORTED_NAMES = "gpt2_\\d+.grd";
87
88
89 private static final Pattern SEPARATOR = Pattern.compile("\\s+");
90
91
92 private static final double G = Constants.G0_STANDARD_GRAVITY;
93
94
95 private static final double R = 287.0;
96
97
98 private static final int DEG_TO_MAS = 3600000;
99
100
101 private static final AtomicReference<Grid> SHARED_GRID = new AtomicReference<>(null);
102
103
104 private final GridEntry southWest;
105
106
107 private final GridEntry southEast;
108
109
110 private final GridEntry northWest;
111
112
113 private final GridEntry northEast;
114
115
116 private double[] coefficientsA;
117
118
119 private double latitude;
120
121
122 private double longitude;
123
124
125 private double temperature;
126
127
128 private double pressure;
129
130
131 private double e0;
132
133
134 private final Geoid geoid;
135
136
137 private final TimeScale utc;
138
139
140
141
142
143
144
145
146
147
148
149
150 @DefaultDataContext
151 public GlobalPressureTemperature2Model(final String supportedNames, final double latitude,
152 final double longitude, final Geoid geoid) {
153 this(supportedNames, latitude, longitude, geoid,
154 DataContext.getDefault().getDataProvidersManager(),
155 DataContext.getDefault().getTimeScales().getUTC());
156 }
157
158
159
160
161
162
163
164
165
166
167
168
169 public GlobalPressureTemperature2Model(final String supportedNames,
170 final double latitude,
171 final double longitude,
172 final Geoid geoid,
173 final DataProvidersManager dataProvidersManager,
174 final TimeScale utc) {
175 this.coefficientsA = null;
176 this.temperature = Double.NaN;
177 this.pressure = Double.NaN;
178 this.e0 = Double.NaN;
179 this.geoid = geoid;
180 this.latitude = latitude;
181 this.utc = utc;
182
183
184 Grid grid = SHARED_GRID.get();
185 if (grid == null) {
186
187 final Parser parser = new Parser();
188 dataProvidersManager.feed(supportedNames, parser);
189 SHARED_GRID.compareAndSet(null, parser.grid);
190 grid = parser.grid;
191 }
192
193
194 this.longitude = MathUtils.normalizeAngle(longitude, grid.entries[0][0].longitude + FastMath.PI);
195
196 final int southIndex = grid.getSouthIndex(this.latitude);
197 final int westIndex = grid.getWestIndex(this.longitude);
198 this.southWest = grid.entries[southIndex ][westIndex ];
199 this.southEast = grid.entries[southIndex ][westIndex + 1];
200 this.northWest = grid.entries[southIndex + 1][westIndex ];
201 this.northEast = grid.entries[southIndex + 1][westIndex + 1];
202
203 }
204
205
206
207
208
209
210
211
212
213
214
215 @DefaultDataContext
216 public GlobalPressureTemperature2Model(final double latitude, final double longitude, final Geoid geoid) {
217 this(DEFAULT_SUPPORTED_NAMES, latitude, longitude, geoid);
218 }
219
220
221
222
223
224
225
226
227 public double[] getA() {
228 return coefficientsA.clone();
229 }
230
231
232
233
234 public double getTemperature() {
235 return temperature;
236 }
237
238
239
240
241 public double getPressure() {
242 return pressure;
243 }
244
245
246
247
248 public double getWaterVaporPressure() {
249 return e0;
250 }
251
252 @Override
253 public void weatherParameters(final double stationHeight, final AbsoluteDate currentDate) {
254
255 final int dayOfYear = currentDate.getComponents(utc).getDate().getDayOfYear();
256
257
258 coefficientsA = new double[] {
259 interpolate(e -> evaluate(dayOfYear, e.ah)) * 0.001,
260 interpolate(e -> evaluate(dayOfYear, e.aw)) * 0.001
261 };
262
263
264 final double undu = geoid.getUndulation(latitude, longitude, currentDate);
265 final double correctedheight = stationHeight - undu - interpolate(e -> e.hS);
266
267
268 final double dTdH = interpolate(e -> evaluate(dayOfYear, e.dT)) * 0.001;
269
270
271 final double qv = interpolate(e -> evaluate(dayOfYear, e.qv0)) * 0.001;
272
273
274
275
276
277 final double t0 = interpolate(e -> evaluate(dayOfYear, e.temperature0));
278 this.temperature = t0 + dTdH * correctedheight;
279
280
281 final double p0 = interpolate(e -> evaluate(dayOfYear, e.pressure0));
282 final double exponent = G / (dTdH * R);
283 this.pressure = p0 * FastMath.pow(1 - (dTdH / t0) * correctedheight, exponent) * 0.01;
284
285
286 this.e0 = qv * pressure / (0.622 + 0.378 * qv);
287
288 }
289
290
291
292
293
294 private double interpolate(final ToDoubleFunction<GridEntry> gridGetter) {
295
296
297 final double[] xVal = new double[] {
298 southWest.longitude, southEast.longitude
299 };
300 final double[] yVal = new double[] {
301 southWest.latitude, northWest.latitude
302 };
303
304
305 final double[][] fval = new double[][] {
306 {
307 gridGetter.applyAsDouble(southWest),
308 gridGetter.applyAsDouble(northWest)
309 }, {
310 gridGetter.applyAsDouble(southEast),
311 gridGetter.applyAsDouble(northEast)
312 }
313 };
314
315
316 return new BilinearInterpolatingFunction(xVal, yVal, fval).value(longitude, latitude);
317
318 }
319
320
321
322
323
324
325 private double evaluate(final int dayOfYear, final double[] model) {
326
327 final double coef = (dayOfYear / 365.25) * 2 * FastMath.PI;
328 final SinCos sc1 = FastMath.sinCos(coef);
329 final SinCos sc2 = FastMath.sinCos(2.0 * coef);
330
331 return model[0] +
332 model[1] * sc1.cos() + model[2] * sc1.sin() +
333 model[3] * sc2.cos() + model[4] * sc2.sin();
334
335 }
336
337
338 private static class Parser implements DataLoader {
339
340
341 private Grid grid;
342
343 @Override
344 public boolean stillAcceptsData() {
345 return grid == null;
346 }
347
348 @Override
349 public void loadData(final InputStream input, final String name)
350 throws IOException, ParseException {
351
352 final SortedSet<Integer> latSample = new TreeSet<>();
353 final SortedSet<Integer> lonSample = new TreeSet<>();
354 final List<GridEntry> entries = new ArrayList<>();
355
356
357 int lineNumber = 0;
358 String line = null;
359 try (InputStreamReader isr = new InputStreamReader(input, StandardCharsets.UTF_8);
360 BufferedReader br = new BufferedReader(isr)) {
361
362 for (line = br.readLine(); line != null; line = br.readLine()) {
363 ++lineNumber;
364 line = line.trim();
365
366
367 if (line.length() > 0 && !line.startsWith("%")) {
368 final GridEntry entry = new GridEntry(SEPARATOR.split(line));
369 latSample.add(entry.latKey);
370 lonSample.add(entry.lonKey);
371 entries.add(entry);
372 }
373
374 }
375 } catch (NumberFormatException nfe) {
376 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
377 lineNumber, name, line);
378 }
379
380
381 grid = new Grid(latSample, lonSample, entries, name);
382
383 }
384
385 }
386
387
388 private static class Grid {
389
390
391 private final SortedSet<Integer> latitudeSample;
392
393
394 private final SortedSet<Integer> longitudeSample;
395
396
397 private final GridEntry[][] entries;
398
399
400
401
402
403
404
405 Grid(final SortedSet<Integer> latitudeSample, final SortedSet<Integer> longitudeSample,
406 final List<GridEntry> loadedEntries, final String name) {
407
408 final int nA = latitudeSample.size();
409 final int nO = longitudeSample.size() + 1;
410 this.entries = new GridEntry[nA][nO];
411 this.latitudeSample = latitudeSample;
412 this.longitudeSample = longitudeSample;
413
414
415 for (final GridEntry entry : loadedEntries) {
416 final int latitudeIndex = latitudeSample.headSet(entry.latKey + 1).size() - 1;
417 final int longitudeIndex = longitudeSample.headSet(entry.lonKey + 1).size() - 1;
418 entries[latitudeIndex][longitudeIndex] = entry;
419 }
420
421
422 for (final GridEntry[] row : entries) {
423
424
425 for (int longitudeIndex = 0; longitudeIndex < nO - 1; ++longitudeIndex) {
426 if (row[longitudeIndex] == null) {
427 throw new OrekitException(OrekitMessages.IRREGULAR_OR_INCOMPLETE_GRID, name);
428 }
429 }
430
431
432 row[nO - 1] = new GridEntry(row[0].latitude, row[0].latKey,
433 row[0].longitude + 2 * FastMath.PI,
434 row[0].lonKey + DEG_TO_MAS * 360,
435 row[0].hS, row[0].pressure0, row[0].temperature0,
436 row[0].qv0, row[0].dT, row[0].ah, row[0].aw);
437
438 }
439
440 }
441
442
443
444
445
446 public int getSouthIndex(final double latitude) {
447
448 final int latKey = (int) FastMath.rint(FastMath.toDegrees(latitude) * DEG_TO_MAS);
449 final int index = latitudeSample.headSet(latKey + 1).size() - 1;
450
451
452 return FastMath.min(index, latitudeSample.size() - 2);
453
454 }
455
456
457
458
459
460 public int getWestIndex(final double longitude) {
461
462 final int lonKey = (int) FastMath.rint(FastMath.toDegrees(longitude) * DEG_TO_MAS);
463 final int index = longitudeSample.headSet(lonKey + 1).size() - 1;
464
465
466 return index;
467
468 }
469
470 }
471
472
473 private static class GridEntry {
474
475
476 private final double latitude;
477
478
479 private final int latKey;
480
481
482 private final double longitude;
483
484
485 private final int lonKey;
486
487
488 private final double hS;
489
490
491 private final double[] pressure0;
492
493
494 private final double[] temperature0;
495
496
497 private final double[] qv0;
498
499
500 private final double[] dT;
501
502
503 private final double[] ah;
504
505
506 private final double[] aw;
507
508
509
510
511 GridEntry(final String[] fields) {
512
513 final double latDegree = Double.parseDouble(fields[0]);
514 final double lonDegree = Double.parseDouble(fields[1]);
515 latitude = FastMath.toRadians(latDegree);
516 longitude = FastMath.toRadians(lonDegree);
517 latKey = (int) FastMath.rint(latDegree * DEG_TO_MAS);
518 lonKey = (int) FastMath.rint(lonDegree * DEG_TO_MAS);
519
520 hS = Double.parseDouble(fields[23]);
521
522 pressure0 = createModel(fields, 2);
523 temperature0 = createModel(fields, 7);
524 qv0 = createModel(fields, 12);
525 dT = createModel(fields, 17);
526 ah = createModel(fields, 24);
527 aw = createModel(fields, 29);
528
529 }
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544 GridEntry(final double latitude, final int latKey, final double longitude, final int lonKey,
545 final double hS, final double[] pressure0, final double[] temperature0,
546 final double[] qv0, final double[] dT, final double[] ah, final double[] aw) {
547
548 this.latitude = latitude;
549 this.latKey = latKey;
550 this.longitude = longitude;
551 this.lonKey = lonKey;
552 this.hS = hS;
553 this.pressure0 = pressure0.clone();
554 this.temperature0 = temperature0.clone();
555 this.qv0 = qv0.clone();
556 this.dT = dT.clone();
557 this.ah = ah.clone();
558 this.aw = aw.clone();
559 }
560
561
562
563
564
565
566 private double[] createModel(final String[] fields, final int first) {
567 return new double[] {
568 Double.parseDouble(fields[first ]),
569 Double.parseDouble(fields[first + 1]),
570 Double.parseDouble(fields[first + 2]),
571 Double.parseDouble(fields[first + 3]),
572 Double.parseDouble(fields[first + 4])
573 };
574 }
575
576 }
577
578 }