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