1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.orekit.models.earth.ionosphere;
18
19 import java.io.BufferedInputStream;
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.regex.Pattern;
29
30 import org.hipparchus.CalculusFieldElement;
31 import org.hipparchus.analysis.interpolation.BilinearInterpolatingFunction;
32 import org.hipparchus.exception.DummyLocalizable;
33 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
34 import org.hipparchus.geometry.euclidean.threed.Vector3D;
35 import org.hipparchus.util.FastMath;
36 import org.orekit.annotation.DefaultDataContext;
37 import org.orekit.bodies.BodyShape;
38 import org.orekit.bodies.GeodeticPoint;
39 import org.orekit.data.DataContext;
40 import org.orekit.data.DataLoader;
41 import org.orekit.data.DataProvidersManager;
42 import org.orekit.data.DataSource;
43 import org.orekit.errors.OrekitException;
44 import org.orekit.errors.OrekitMessages;
45 import org.orekit.frames.Frame;
46 import org.orekit.frames.TopocentricFrame;
47 import org.orekit.propagation.FieldSpacecraftState;
48 import org.orekit.propagation.SpacecraftState;
49 import org.orekit.time.AbsoluteDate;
50 import org.orekit.time.FieldAbsoluteDate;
51 import org.orekit.time.TimeScale;
52 import org.orekit.utils.ParameterDriver;
53 import org.orekit.utils.TimeSpanMap;
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 public class GlobalIonosphereMapModel implements IonosphericModel {
123
124
125 private static final Pattern SEPARATOR = Pattern.compile("\\s+");
126
127
128 private TimeSpanMap<TECMapPair> tecMap;
129
130
131 private final TimeScale utc;
132
133
134
135
136 private String names;
137
138
139
140
141
142
143
144
145
146
147 @DefaultDataContext
148 public GlobalIonosphereMapModel(final String supportedNames) {
149 this(supportedNames,
150 DataContext.getDefault().getDataProvidersManager(),
151 DataContext.getDefault().getTimeScales().getUTC());
152 }
153
154
155
156
157
158
159
160
161
162
163
164 public GlobalIonosphereMapModel(final String supportedNames,
165 final DataProvidersManager dataProvidersManager,
166 final TimeScale utc) {
167 this.utc = utc;
168 this.tecMap = new TimeSpanMap<>(null);
169 this.names = "";
170
171
172 dataProvidersManager.feed(supportedNames, new Parser());
173
174 }
175
176
177
178
179
180
181
182
183 public GlobalIonosphereMapModel(final TimeScale utc,
184 final DataSource... ionex) {
185 try {
186 this.utc = utc;
187 this.tecMap = new TimeSpanMap<>(null);
188 this.names = "";
189 final Parser parser = new Parser();
190 for (final DataSource source : ionex) {
191 try (InputStream is = source.getOpener().openStreamOnce();
192 BufferedInputStream bis = new BufferedInputStream(is)) {
193 parser.loadData(bis, source.getName());
194 }
195 }
196 } catch (IOException ioe) {
197 throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
198 }
199 }
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private double pathDelayAtIPP(final AbsoluteDate date, final GeodeticPoint piercePoint,
214 final double elevation, final double frequency) {
215
216 final TECMapPair pair = getPairAtDate(date);
217 final double tec = pair.getTEC(date, piercePoint);
218
219 final double freq2 = frequency * frequency;
220
221 final double stec;
222
223 if (pair.mapping) {
224 stec = tec;
225 } else {
226
227 final double fz = mappingFunction(elevation, pair.r0, pair.h);
228 stec = tec * fz;
229 }
230
231 final double alpha = 40.3e16 / freq2;
232 return alpha * stec;
233 }
234
235 @Override
236 public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
237 final double frequency, final double[] parameters) {
238
239
240 final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
241 final Vector3D satPoint = state.getPosition(bodyFrame);
242
243
244 final double elevation = bodyFrame.
245 getStaticTransformTo(baseFrame, state.getDate()).
246 transformPosition(satPoint).
247 getDelta();
248
249
250 if (elevation > 0.0) {
251
252 final Vector3D los = satPoint.subtract(baseFrame.getCartesianPoint()).normalize();
253
254 final GeodeticPoint ipp = piercePoint(state.getDate(), baseFrame.getCartesianPoint(), los, baseFrame.getParentShape());
255 if (ipp != null) {
256
257 return pathDelayAtIPP(state.getDate(), ipp, elevation, frequency);
258 }
259 }
260
261 return 0.0;
262
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278 private <T extends CalculusFieldElement<T>> T pathDelayAtIPP(final FieldAbsoluteDate<T> date,
279 final GeodeticPoint piercePoint,
280 final T elevation, final double frequency) {
281
282 final TECMapPair pair = getPairAtDate(date.toAbsoluteDate());
283 final T tec = pair.getTEC(date, piercePoint);
284
285 final double freq2 = frequency * frequency;
286
287 final T stec;
288
289 if (pair.mapping) {
290 stec = tec;
291 } else {
292
293 final T fz = mappingFunction(elevation, pair.r0, pair.h);
294 stec = tec.multiply(fz);
295 }
296
297 final double alpha = 40.3e16 / freq2;
298 return stec.multiply(alpha);
299 }
300
301 @Override
302 public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state, final TopocentricFrame baseFrame,
303 final double frequency, final T[] parameters) {
304
305
306 final Frame bodyFrame = baseFrame.getParentShape().getBodyFrame();
307 final FieldVector3D<T> satPoint = state.getPosition(bodyFrame);
308
309
310 final T elevation = bodyFrame.
311 getStaticTransformTo(baseFrame, state.getDate()).
312 transformPosition(satPoint).
313 getDelta();
314
315
316 if (elevation.getReal() > 0.0) {
317
318 final Vector3D los = satPoint.toVector3D().subtract(baseFrame.getCartesianPoint()).normalize();
319
320 final GeodeticPoint ipp = piercePoint(state.getDate().toAbsoluteDate(), baseFrame.getCartesianPoint(), los, baseFrame.getParentShape());
321 if (ipp != null) {
322
323 return pathDelayAtIPP(state.getDate(), ipp, elevation, frequency);
324 }
325 }
326
327 return elevation.getField().getZero();
328
329 }
330
331
332
333
334
335
336 private TECMapPair getPairAtDate(final AbsoluteDate date) {
337 final TECMapPair pair = tecMap.get(date);
338 if (pair == null) {
339 throw new OrekitException(OrekitMessages.NO_TEC_DATA_IN_FILES_FOR_DATE,
340 names, date);
341 }
342 return pair;
343 }
344
345 @Override
346 public List<ParameterDriver> getParametersDrivers() {
347 return Collections.emptyList();
348 }
349
350
351
352
353
354
355
356
357 private double mappingFunction(final double elevation,
358 final double r0, final double h) {
359
360 final double z = FastMath.abs(0.5 * FastMath.PI - elevation);
361
362 final double ratio = r0 / (r0 + h);
363
364 final double coef = FastMath.sin(z) * ratio;
365 final double fz = 1.0 / FastMath.sqrt(1.0 - coef * coef);
366 return fz;
367 }
368
369
370
371
372
373
374
375
376
377 private <T extends CalculusFieldElement<T>> T mappingFunction(final T elevation,
378 final double r0, final double h) {
379
380 final T z = FastMath.abs(elevation.getPi().multiply(0.5).subtract(elevation));
381
382 final double ratio = r0 / (r0 + h);
383
384 final T coef = FastMath.sin(z).multiply(ratio);
385 final T fz = FastMath.sqrt(coef.multiply(coef).negate().add(1.0)).reciprocal();
386 return fz;
387 }
388
389
390
391
392
393
394
395
396
397
398
399
400 private GeodeticPoint piercePoint(final AbsoluteDate date,
401 final Vector3D recPoint, final Vector3D los,
402 final BodyShape bodyShape) {
403
404 final TECMapPair pair = getPairAtDate(date);
405 final double r = pair.r0 + pair.h;
406 final double r2 = r * r;
407 final double p2 = recPoint.getNormSq();
408 if (p2 >= r2) {
409
410 return null;
411 }
412
413
414 final double dot = Vector3D.dotProduct(recPoint, los);
415 final double k = FastMath.sqrt(dot * dot + r2 - p2) - dot;
416
417
418 final Vector3D ipp = new Vector3D(1, recPoint, k, los);
419
420 return bodyShape.transform(ipp, bodyShape.getBodyFrame(), null);
421
422 }
423
424
425 private class Parser implements DataLoader {
426
427
428 private static final String END = "END OF TEC MAP";
429
430
431 private static final String EPOCH = "EPOCH OF CURRENT MAP";
432
433
434 private static final int LABEL_START = 60;
435
436
437 private static final double KM_TO_M = 1000.0;
438
439
440 private IONEXHeader header;
441
442
443 private List<TECMap> maps;
444
445 @Override
446 public boolean stillAcceptsData() {
447 return true;
448 }
449
450 @Override
451 public void loadData(final InputStream input, final String name)
452 throws IOException {
453
454 maps = new ArrayList<>();
455
456
457 int lineNumber = 0;
458 String line = null;
459 try (InputStreamReader isr = new InputStreamReader(input, StandardCharsets.UTF_8);
460 BufferedReader br = new BufferedReader(isr)) {
461
462
463 int nbOfMaps = 1;
464 int exponent = -1;
465 double baseRadius = Double.NaN;
466 double hIon = Double.NaN;
467 boolean mappingF = false;
468 boolean inTEC = false;
469 double[] latitudes = null;
470 double[] longitudes = null;
471 AbsoluteDate firstEpoch = null;
472 AbsoluteDate lastEpoch = null;
473 AbsoluteDate epoch = firstEpoch;
474 ArrayList<Double> values = new ArrayList<>();
475
476 for (line = br.readLine(); line != null; line = br.readLine()) {
477 ++lineNumber;
478 if (line.length() > LABEL_START) {
479 switch (line.substring(LABEL_START).trim()) {
480 case "EPOCH OF FIRST MAP" :
481 firstEpoch = parseDate(line);
482 break;
483 case "EPOCH OF LAST MAP" :
484 lastEpoch = parseDate(line);
485 break;
486 case "INTERVAL" :
487
488 break;
489 case "# OF MAPS IN FILE" :
490 nbOfMaps = parseInt(line, 2, 4);
491 break;
492 case "BASE RADIUS" :
493
494 baseRadius = parseDouble(line, 2, 6) * KM_TO_M;
495 break;
496 case "MAPPING FUNCTION" :
497 mappingF = !parseString(line, 2, 4).equals("NONE");
498 break;
499 case "EXPONENT" :
500 exponent = parseInt(line, 4, 2);
501 break;
502 case "HGT1 / HGT2 / DHGT" :
503 if (parseDouble(line, 17, 3) == 0.0) {
504
505 hIon = parseDouble(line, 3, 5) * KM_TO_M;
506 }
507 break;
508 case "LAT1 / LAT2 / DLAT" :
509 latitudes = parseCoordinate(line);
510 break;
511 case "LON1 / LON2 / DLON" :
512 longitudes = parseCoordinate(line);
513 break;
514 case "END OF HEADER" :
515
516 if (latitudes == null || longitudes == null) {
517 throw new OrekitException(OrekitMessages.NO_LATITUDE_LONGITUDE_BONDARIES_IN_IONEX_HEADER, name);
518 }
519
520 if (firstEpoch == null || lastEpoch == null) {
521 throw new OrekitException(OrekitMessages.NO_EPOCH_IN_IONEX_HEADER, name);
522 }
523
524 header = new IONEXHeader(nbOfMaps, baseRadius, hIon, mappingF);
525 break;
526 case "START OF TEC MAP" :
527 inTEC = true;
528 break;
529 case END :
530 final BilinearInterpolatingFunction tec = interpolateTEC(values, exponent, latitudes, longitudes);
531 final TECMap map = new TECMap(epoch, tec);
532 maps.add(map);
533
534 inTEC = false;
535 values = new ArrayList<>();
536 epoch = null;
537 break;
538 default :
539 if (inTEC) {
540
541 if (line.endsWith(EPOCH)) {
542 epoch = parseDate(line);
543 }
544
545 if (!line.endsWith("LAT/LON1/LON2/DLON/H") &&
546 !line.endsWith(END) &&
547 !line.endsWith(EPOCH)) {
548 line = line.trim();
549 final String[] readLine = SEPARATOR.split(line);
550 for (final String s : readLine) {
551 values.add(Double.parseDouble(s));
552 }
553 }
554 }
555 break;
556 }
557 } else {
558 if (inTEC) {
559
560
561 line = line.trim();
562 final String[] readLine = SEPARATOR.split(line);
563 for (final String s : readLine) {
564 values.add(Double.parseDouble(s));
565 }
566 }
567 }
568
569 }
570
571
572 input.close();
573
574 } catch (NumberFormatException nfe) {
575 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
576 lineNumber, name, line);
577 }
578
579
580 if (maps.size() != header.getTECMapsNumer()) {
581 throw new OrekitException(OrekitMessages.INCONSISTENT_NUMBER_OF_TEC_MAPS_IN_FILE,
582 maps.size(), header.getTECMapsNumer());
583 }
584 TECMap previous = null;
585 for (TECMap current : maps) {
586 if (previous != null) {
587 tecMap.addValidBetween(new TECMapPair(previous, current,
588 header.getEarthRadius(), header.getHIon(), header.isMappingFunction()),
589 previous.date, current.date);
590 }
591 previous = current;
592 }
593
594 names = names.isEmpty() ? name : (names + ", " + name);
595
596 }
597
598
599
600
601
602
603
604 private String parseString(final String line, final int start, final int length) {
605 return line.substring(start, FastMath.min(line.length(), start + length)).trim();
606 }
607
608
609
610
611
612
613
614 private int parseInt(final String line, final int start, final int length) {
615 return Integer.parseInt(parseString(line, start, length));
616 }
617
618
619
620
621
622
623
624 private double parseDouble(final String line, final int start, final int length) {
625 return Double.parseDouble(parseString(line, start, length));
626 }
627
628
629
630
631
632 private AbsoluteDate parseDate(final String line) {
633 return new AbsoluteDate(parseInt(line, 0, 6),
634 parseInt(line, 6, 6),
635 parseInt(line, 12, 6),
636 parseInt(line, 18, 6),
637 parseInt(line, 24, 6),
638 parseDouble(line, 30, 13),
639 utc);
640 }
641
642
643
644
645
646 private double[] parseCoordinate(final String line) {
647 final double a = parseDouble(line, 2, 6);
648 final double b = parseDouble(line, 8, 6);
649 final double c = parseDouble(line, 14, 6);
650 final double[] coordinate = new double[((int) FastMath.abs((a - b) / c)) + 1];
651 int i = 0;
652 for (double cor = FastMath.min(a, b); cor <= FastMath.max(a, b); cor += FastMath.abs(c)) {
653 coordinate[i] = FastMath.toRadians(cor);
654 i++;
655 }
656 return coordinate;
657 }
658
659
660
661
662
663
664
665
666 private BilinearInterpolatingFunction interpolateTEC(final ArrayList<Double> values, final double exponent,
667 final double[] latitudes, final double[] longitudes) {
668
669 final int dimLat = latitudes.length;
670 final int dimLon = longitudes.length;
671
672
673 final double factor = FastMath.pow(10.0, exponent);
674 final double[][] fvalTEC = new double[dimLat][dimLon];
675 int index = dimLon * dimLat;
676 for (int x = 0; x < dimLat; x++) {
677 for (int y = dimLon - 1; y >= 0; y--) {
678 index = index - 1;
679 fvalTEC[x][y] = values.get(index) * factor;
680 }
681 }
682
683
684 return new BilinearInterpolatingFunction(latitudes, longitudes, fvalTEC);
685
686 }
687
688 }
689
690
691
692
693
694
695
696
697 private static class TECMap {
698
699
700 private AbsoluteDate date;
701
702
703 private BilinearInterpolatingFunction tec;
704
705
706
707
708
709
710 TECMap(final AbsoluteDate date, final BilinearInterpolatingFunction tec) {
711 this.date = date;
712 this.tec = tec;
713 }
714
715 }
716
717
718
719
720 private static class TECMapPair {
721
722
723 private final TECMap first;
724
725
726 private final TECMap second;
727
728
729 private double r0;
730
731
732 private double h;
733
734
735 private boolean mapping;
736
737
738
739
740
741
742
743
744 TECMapPair(final TECMap first, final TECMap second,
745 final double r0, final double h, final boolean mapping) {
746 this.first = first;
747 this.second = second;
748 this.r0 = r0;
749 this.h = h;
750 this.mapping = mapping;
751 }
752
753
754
755
756
757
758 public double getTEC(final AbsoluteDate date, final GeodeticPoint ipp) {
759
760 final AbsoluteDate t1 = first.date;
761 final double tec1 = first.tec.value(ipp.getLatitude(), ipp.getLongitude());
762 final AbsoluteDate t2 = second.date;
763 final double tec2 = second.tec.value(ipp.getLatitude(), ipp.getLongitude());
764 final double dt = t2.durationFrom(t1);
765
766
767 return (t2.durationFrom(date) / dt) * tec1 + (date.durationFrom(t1) / dt) * tec2;
768
769 }
770
771
772
773
774
775
776
777 public <T extends CalculusFieldElement<T>> T getTEC(final FieldAbsoluteDate<T> date, final GeodeticPoint ipp) {
778
779
780 final AbsoluteDate t1 = first.date;
781 final double tec1 = first.tec.value(ipp.getLatitude(), ipp.getLongitude());
782 final AbsoluteDate t2 = second.date;
783 final double tec2 = second.tec.value(ipp.getLatitude(), ipp.getLongitude());
784 final double dt = t2.durationFrom(t1);
785
786
787 return date.durationFrom(t2).negate().divide(dt).multiply(tec1).add(date.durationFrom(t1).divide(dt).multiply(tec2));
788
789 }
790
791 }
792
793
794 private static class IONEXHeader {
795
796
797 private int nbOfMaps;
798
799
800 private double baseRadius;
801
802
803 private double hIon;
804
805
806 private boolean isMappingFunction;
807
808
809
810
811
812
813
814
815 IONEXHeader(final int nbOfMaps,
816 final double baseRadius, final double hIon,
817 final boolean mappingFunction) {
818 this.nbOfMaps = nbOfMaps;
819 this.baseRadius = baseRadius;
820 this.hIon = hIon;
821 this.isMappingFunction = mappingFunction;
822 }
823
824
825
826
827
828 public int getTECMapsNumer() {
829 return nbOfMaps;
830 }
831
832
833
834
835
836 public double getEarthRadius() {
837 return baseRadius;
838 }
839
840
841
842
843
844 public double getHIon() {
845 return hIon;
846 }
847
848
849
850
851
852 public boolean isMappingFunction() {
853 return isMappingFunction;
854 }
855
856 }
857
858 }