1 /* Copyright 2002-2012 Space Applications Services
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.files.sp3;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.util.Locale;
23 import java.util.Optional;
24 import java.util.Scanner;
25 import java.util.function.Function;
26 import java.util.regex.Pattern;
27 import java.util.stream.Stream;
28
29 import org.hipparchus.exception.LocalizedCoreFormats;
30 import org.hipparchus.geometry.euclidean.threed.Vector3D;
31 import org.orekit.annotation.DefaultDataContext;
32 import org.orekit.data.DataContext;
33 import org.orekit.data.DataSource;
34 import org.orekit.errors.OrekitException;
35 import org.orekit.errors.OrekitMessages;
36 import org.orekit.files.general.EphemerisFileParser;
37 import org.orekit.files.sp3.SP3.SP3Coordinate;
38 import org.orekit.files.sp3.SP3.SP3FileType;
39 import org.orekit.frames.Frame;
40 import org.orekit.gnss.TimeSystem;
41 import org.orekit.time.AbsoluteDate;
42 import org.orekit.time.DateTimeComponents;
43 import org.orekit.time.TimeScale;
44 import org.orekit.time.TimeScales;
45 import org.orekit.utils.CartesianDerivativesFilter;
46 import org.orekit.utils.Constants;
47 import org.orekit.utils.IERSConventions;
48
49 /** A parser for the SP3 orbit file format. It supports all formats from sp3-a
50 * to sp3-d.
51 * <p>
52 * <b>Note:</b> this parser is thread-safe, so calling {@link #parse} from
53 * different threads is allowed.
54 * </p>
55 * @see <a href="ftp://igs.org/pub/data/format/sp3_docu.txt">SP3-a file format</a>
56 * @see <a href="ftp://igs.org/pub/data/format/sp3c.txt">SP3-c file format</a>
57 * @see <a href="ftp://igs.org/pub/data/format/sp3d.pdf">SP3-d file format</a>
58 * @author Thomas Neidhart
59 * @author Luc Maisonobe
60 */
61 public class SP3Parser implements EphemerisFileParser<SP3> {
62
63 /** Bad or absent clock values are to be set to 999999.999999. */
64 public static final double DEFAULT_CLOCK_VALUE = 999999.999999;
65
66 /** Spaces delimiters. */
67 private static final String SPACES = "\\s+";
68
69 /** One millimeter, in meters. */
70 private static final double MILLIMETER = 1.0e-3;
71
72 /** Standard gravitational parameter in m^3 / s^2. */
73 private final double mu;
74 /** Number of data points to use in interpolation. */
75 private final int interpolationSamples;
76 /** Mapping from frame identifier in the file to a {@link Frame}. */
77 private final Function<? super String, ? extends Frame> frameBuilder;
78 /** Set of time scales. */
79 private final TimeScales timeScales;
80
81 /**
82 * Create an SP3 parser using default values.
83 *
84 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
85 *
86 * @see #SP3Parser(double, int, Function)
87 */
88 @DefaultDataContext
89 public SP3Parser() {
90 this(Constants.EIGEN5C_EARTH_MU, 7, SP3Parser::guessFrame);
91 }
92
93 /**
94 * Create an SP3 parser and specify the extra information needed to create a {@link
95 * org.orekit.propagation.Propagator Propagator} from the ephemeris data.
96 *
97 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
98 *
99 * @param mu is the standard gravitational parameter to use for
100 * creating {@link org.orekit.orbits.Orbit Orbits} from
101 * the ephemeris data. See {@link Constants}.
102 * @param interpolationSamples is the number of samples to use when interpolating.
103 * @param frameBuilder is a function that can construct a frame from an SP3
104 * coordinate system string. The coordinate system can be
105 * any 5 character string e.g. ITR92, IGb08.
106 * @see #SP3Parser(double, int, Function, TimeScales)
107 */
108 @DefaultDataContext
109 public SP3Parser(final double mu,
110 final int interpolationSamples,
111 final Function<? super String, ? extends Frame> frameBuilder) {
112 this(mu, interpolationSamples, frameBuilder,
113 DataContext.getDefault().getTimeScales());
114 }
115
116 /**
117 * Create an SP3 parser and specify the extra information needed to create a {@link
118 * org.orekit.propagation.Propagator Propagator} from the ephemeris data.
119 *
120 * @param mu is the standard gravitational parameter to use for
121 * creating {@link org.orekit.orbits.Orbit Orbits} from
122 * the ephemeris data. See {@link Constants}.
123 * @param interpolationSamples is the number of samples to use when interpolating.
124 * @param frameBuilder is a function that can construct a frame from an SP3
125 * coordinate system string. The coordinate system can be
126 * @param timeScales the set of time scales used for parsing dates.
127 * @since 10.1
128 */
129 public SP3Parser(final double mu,
130 final int interpolationSamples,
131 final Function<? super String, ? extends Frame> frameBuilder,
132 final TimeScales timeScales) {
133 this.mu = mu;
134 this.interpolationSamples = interpolationSamples;
135 this.frameBuilder = frameBuilder;
136 this.timeScales = timeScales;
137 }
138
139 /**
140 * Default string to {@link Frame} conversion for {@link #SP3Parser()}.
141 *
142 * <p>This method uses the {@link DataContext#getDefault() default data context}.
143 *
144 * @param name of the frame.
145 * @return ITRF based on 2010 conventions,
146 * with tidal effects considered during EOP interpolation.
147 */
148 @DefaultDataContext
149 private static Frame guessFrame(final String name) {
150 return DataContext.getDefault().getFrames()
151 .getITRF(IERSConventions.IERS_2010, false);
152 }
153
154 @Override
155 public SP3 parse(final DataSource source) {
156
157 try (Reader reader = source.getOpener().openReaderOnce();
158 BufferedReader br = (reader == null) ? null : new BufferedReader(reader)) {
159
160 if (br == null) {
161 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
162 }
163
164 // initialize internal data structures
165 final ParseInfo pi = new ParseInfo();
166
167 int lineNumber = 0;
168 Stream<LineParser> candidateParsers = Stream.of(LineParser.HEADER_VERSION);
169 for (String line = br.readLine(); line != null; line = br.readLine()) {
170 ++lineNumber;
171 final String l = line;
172 final Optional<LineParser> selected = candidateParsers.filter(p -> p.canHandle(l)).findFirst();
173 if (selected.isPresent()) {
174 try {
175 selected.get().parse(line, pi);
176 } catch (StringIndexOutOfBoundsException | NumberFormatException e) {
177 throw new OrekitException(e,
178 OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
179 lineNumber, source.getName(), line);
180 }
181 candidateParsers = selected.get().allowedNext();
182 } else {
183 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
184 lineNumber, source.getName(), line);
185 }
186 if (pi.done) {
187 if (pi.nbEpochs != pi.file.getNumberOfEpochs()) {
188 throw new OrekitException(OrekitMessages.SP3_NUMBER_OF_EPOCH_MISMATCH,
189 pi.nbEpochs, source.getName(), pi.file.getNumberOfEpochs());
190 }
191 return pi.file;
192 }
193 }
194
195 // we never reached the EOF marker
196 throw new OrekitException(OrekitMessages.SP3_UNEXPECTED_END_OF_FILE, lineNumber);
197
198 } catch (IOException ioe) {
199 throw new OrekitException(ioe, LocalizedCoreFormats.SIMPLE_MESSAGE, ioe.getLocalizedMessage());
200 }
201
202 }
203
204 /** Returns the {@link SP3FileType} that corresponds to a given string in a SP3 file.
205 * @param fileType file type as string
206 * @return file type as enum
207 */
208 private static SP3FileType getFileType(final String fileType) {
209 SP3FileType type = SP3FileType.UNDEFINED;
210 if ("G".equalsIgnoreCase(fileType)) {
211 type = SP3FileType.GPS;
212 } else if ("M".equalsIgnoreCase(fileType)) {
213 type = SP3FileType.MIXED;
214 } else if ("R".equalsIgnoreCase(fileType)) {
215 type = SP3FileType.GLONASS;
216 } else if ("L".equalsIgnoreCase(fileType)) {
217 type = SP3FileType.LEO;
218 } else if ("S".equalsIgnoreCase(fileType)) {
219 type = SP3FileType.SBAS;
220 } else if ("I".equalsIgnoreCase(fileType)) {
221 type = SP3FileType.IRNSS;
222 } else if ("E".equalsIgnoreCase(fileType)) {
223 type = SP3FileType.GALILEO;
224 } else if ("C".equalsIgnoreCase(fileType)) {
225 type = SP3FileType.COMPASS;
226 } else if ("J".equalsIgnoreCase(fileType)) {
227 type = SP3FileType.QZSS;
228 }
229 return type;
230 }
231
232 /** Transient data used for parsing a sp3 file. The data is kept in a
233 * separate data structure to make the parser thread-safe.
234 * <p><b>Note</b>: The class intentionally does not provide accessor
235 * methods, as it is only used internally for parsing a SP3 file.</p>
236 */
237 private class ParseInfo {
238
239 /** Set of time scales for parsing dates. */
240 private final TimeScales timeScales;
241
242 /** The corresponding SP3File object. */
243 private SP3 file;
244
245 /** The latest epoch as read from the SP3 file. */
246 private AbsoluteDate latestEpoch;
247
248 /** The latest position as read from the SP3 file. */
249 private Vector3D latestPosition;
250
251 /** The latest clock value as read from the SP3 file. */
252 private double latestClock;
253
254 /** Indicates if the SP3 file has velocity entries. */
255 private boolean hasVelocityEntries;
256
257 /** The timescale used in the SP3 file. */
258 private TimeScale timeScale;
259
260 /** Date and time of the file. */
261 private DateTimeComponents epoch;
262
263 /** The number of satellites as contained in the SP3 file. */
264 private int maxSatellites;
265
266 /** The number of satellites accuracies already seen. */
267 private int nbAccuracies;
268
269 /** The number of epochs already seen. */
270 private int nbEpochs;
271
272 /** End Of File reached indicator. */
273 private boolean done;
274
275 /** The base for pos/vel. */
276 //private double posVelBase;
277
278 /** The base for clock/rate. */
279 //private double clockBase;
280
281 /** Create a new {@link ParseInfo} object. */
282 protected ParseInfo() {
283 this.timeScales = SP3Parser.this.timeScales;
284 file = new SP3(mu, interpolationSamples, frameBuilder);
285 latestEpoch = null;
286 latestPosition = null;
287 latestClock = 0.0;
288 hasVelocityEntries = false;
289 epoch = DateTimeComponents.JULIAN_EPOCH;
290 timeScale = timeScales.getGPS();
291 maxSatellites = 0;
292 nbAccuracies = 0;
293 nbEpochs = 0;
294 done = false;
295 //posVelBase = 2d;
296 //clockBase = 2d;
297 }
298 }
299
300 /** Parsers for specific lines. */
301 private enum LineParser {
302
303 /** Parser for version, epoch, data used and agency information. */
304 HEADER_VERSION("^#[a-z].*") {
305
306 /** {@inheritDoc} */
307 @Override
308 public void parse(final String line, final ParseInfo pi) {
309 try (Scanner s1 = new Scanner(line);
310 Scanner s2 = s1.useDelimiter(SPACES);
311 Scanner scanner = s2.useLocale(Locale.US)) {
312 scanner.skip("#");
313 final String v = scanner.next();
314
315 final char version = v.substring(0, 1).toLowerCase().charAt(0);
316 if (version != 'a' && version != 'b' && version != 'c' && version != 'd') {
317 throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_VERSION, version);
318 }
319
320 pi.hasVelocityEntries = "V".equals(v.substring(1, 2));
321 pi.file.setFilter(pi.hasVelocityEntries ?
322 CartesianDerivativesFilter.USE_PV :
323 CartesianDerivativesFilter.USE_P);
324
325 final int year = Integer.parseInt(v.substring(2));
326 final int month = scanner.nextInt();
327 final int day = scanner.nextInt();
328 final int hour = scanner.nextInt();
329 final int minute = scanner.nextInt();
330 final double second = scanner.nextDouble();
331
332 pi.epoch = new DateTimeComponents(year, month, day,
333 hour, minute, second);
334
335 final int numEpochs = scanner.nextInt();
336 pi.file.setNumberOfEpochs(numEpochs);
337
338 // data used indicator
339 pi.file.setDataUsed(scanner.next());
340
341 pi.file.setCoordinateSystem(scanner.next());
342 pi.file.setOrbitTypeKey(scanner.next());
343 pi.file.setAgency(scanner.next());
344 }
345 }
346
347 /** {@inheritDoc} */
348 @Override
349 public Stream<LineParser> allowedNext() {
350 return Stream.of(HEADER_DATE_TIME_REFERENCE);
351 }
352
353 },
354
355 /** Parser for additional date/time references in gps/julian day notation. */
356 HEADER_DATE_TIME_REFERENCE("^##.*") {
357
358 /** {@inheritDoc} */
359 @Override
360 public void parse(final String line, final ParseInfo pi) {
361 try (Scanner s1 = new Scanner(line);
362 Scanner s2 = s1.useDelimiter(SPACES);
363 Scanner scanner = s2.useLocale(Locale.US)) {
364 scanner.skip("##");
365
366 // gps week
367 pi.file.setGpsWeek(scanner.nextInt());
368 // seconds of week
369 pi.file.setSecondsOfWeek(scanner.nextDouble());
370 // epoch interval
371 pi.file.setEpochInterval(scanner.nextDouble());
372 // julian day
373 pi.file.setJulianDay(scanner.nextInt());
374 // day fraction
375 pi.file.setDayFraction(scanner.nextDouble());
376 }
377 }
378
379 /** {@inheritDoc} */
380 @Override
381 public Stream<LineParser> allowedNext() {
382 return Stream.of(HEADER_SAT_IDS);
383 }
384
385 },
386
387 /** Parser for satellites identifiers. */
388 HEADER_SAT_IDS("^\\+ .*") {
389
390 /** {@inheritDoc} */
391 @Override
392 public void parse(final String line, final ParseInfo pi) {
393
394 if (pi.maxSatellites == 0) {
395 // this is the first ids line, it also contains the number of satellites
396 pi.maxSatellites = Integer.parseInt(line.substring(3, 6).trim());
397 }
398
399 final int lineLength = line.length();
400 int count = pi.file.getSatelliteCount();
401 int startIdx = 9;
402 while (count++ < pi.maxSatellites && (startIdx + 3) <= lineLength) {
403 final String satId = line.substring(startIdx, startIdx + 3).trim();
404 if (satId.length() > 0) {
405 pi.file.addSatellite(satId);
406 }
407 startIdx += 3;
408 }
409 }
410
411 /** {@inheritDoc} */
412 @Override
413 public Stream<LineParser> allowedNext() {
414 return Stream.of(HEADER_SAT_IDS, HEADER_ACCURACY);
415 }
416
417 },
418
419 /** Parser for general accuracy information for each satellite. */
420 HEADER_ACCURACY("^\\+\\+.*") {
421
422 /** {@inheritDoc} */
423 @Override
424 public void parse(final String line, final ParseInfo pi) {
425 final int lineLength = line.length();
426 int startIdx = 9;
427 while (pi.nbAccuracies < pi.maxSatellites && (startIdx + 3) <= lineLength) {
428 final String sub = line.substring(startIdx, startIdx + 3).trim();
429 if (sub.length() > 0) {
430 final int exponent = Integer.parseInt(sub);
431 // the accuracy is calculated as 2**exp (in mm)
432 pi.file.setAccuracy(pi.nbAccuracies++, (2 << exponent) * MILLIMETER);
433 }
434 startIdx += 3;
435 }
436 }
437
438 /** {@inheritDoc} */
439 @Override
440 public Stream<LineParser> allowedNext() {
441 return Stream.of(HEADER_ACCURACY, HEADER_TIME_SYSTEM);
442 }
443
444 },
445
446 /** Parser for time system. */
447 HEADER_TIME_SYSTEM("^%c.*") {
448
449 /** {@inheritDoc} */
450 @Override
451 public void parse(final String line, final ParseInfo pi) {
452
453 if (pi.file.getType() == null) {
454 // this the first custom fields line, the only one really used
455 pi.file.setType(getFileType(line.substring(3, 5).trim()));
456
457 // now identify the time system in use
458 final String tsStr = line.substring(9, 12).trim();
459 final TimeSystem ts;
460 if (tsStr.equalsIgnoreCase("ccc")) {
461 ts = TimeSystem.GPS;
462 } else {
463 ts = TimeSystem.valueOf(tsStr);
464 }
465 pi.file.setTimeSystem(ts);
466 pi.timeScale = ts.getTimeScale(pi.timeScales);
467
468 // now we know the time scale used, we can set the file epoch
469 pi.file.setEpoch(new AbsoluteDate(pi.epoch, pi.timeScale));
470 }
471
472 }
473
474 /** {@inheritDoc} */
475 @Override
476 public Stream<LineParser> allowedNext() {
477 return Stream.of(HEADER_TIME_SYSTEM, HEADER_STANDARD_DEVIATIONS);
478 }
479
480 },
481
482 /** Parser for standard deviations of position/velocity/clock components. */
483 HEADER_STANDARD_DEVIATIONS("^%f.*") {
484
485 /** {@inheritDoc} */
486 @Override
487 public void parse(final String line, final ParseInfo pi) {
488 // String base = line.substring(3, 13).trim();
489 // if (!base.equals("0.0000000")) {
490 // // (mm or 10**-4 mm/sec)
491 // pi.posVelBase = Double.valueOf(base);
492 // }
493
494 // base = line.substring(14, 26).trim();
495 // if (!base.equals("0.000000000")) {
496 // // (psec or 10**-4 psec/sec)
497 // pi.clockBase = Double.valueOf(base);
498 // }
499 }
500
501 /** {@inheritDoc} */
502 @Override
503 public Stream<LineParser> allowedNext() {
504 return Stream.of(HEADER_STANDARD_DEVIATIONS, HEADER_CUSTOM_PARAMETERS);
505 }
506
507 },
508
509 /** Parser for custom parameters. */
510 HEADER_CUSTOM_PARAMETERS("^%i.*") {
511
512 /** {@inheritDoc} */
513 @Override
514 public void parse(final String line, final ParseInfo pi) {
515 // ignore additional custom parameters
516 }
517
518 /** {@inheritDoc} */
519 @Override
520 public Stream<LineParser> allowedNext() {
521 return Stream.of(HEADER_CUSTOM_PARAMETERS, HEADER_COMMENTS);
522 }
523
524 },
525
526 /** Parser for comments. */
527 HEADER_COMMENTS("^/\\*.*") {
528
529 /** {@inheritDoc} */
530 @Override
531 public void parse(final String line, final ParseInfo pi) {
532 // ignore comments
533 }
534
535 /** {@inheritDoc} */
536 @Override
537 public Stream<LineParser> allowedNext() {
538 return Stream.of(HEADER_COMMENTS, DATA_EPOCH);
539 }
540
541 },
542
543 /** Parser for epoch. */
544 DATA_EPOCH("^\\* .*") {
545
546 /** {@inheritDoc} */
547 @Override
548 public void parse(final String line, final ParseInfo pi) {
549 final int year = Integer.parseInt(line.substring(3, 7).trim());
550 final int month = Integer.parseInt(line.substring(8, 10).trim());
551 final int day = Integer.parseInt(line.substring(11, 13).trim());
552 final int hour = Integer.parseInt(line.substring(14, 16).trim());
553 final int minute = Integer.parseInt(line.substring(17, 19).trim());
554 final double second = Double.parseDouble(line.substring(20, 31).trim());
555
556 pi.latestEpoch = new AbsoluteDate(year, month, day,
557 hour, minute, second,
558 pi.timeScale);
559 pi.nbEpochs++;
560 }
561
562 /** {@inheritDoc} */
563 @Override
564 public Stream<LineParser> allowedNext() {
565 return Stream.of(DATA_POSITION);
566 }
567
568 },
569
570 /** Parser for position. */
571 DATA_POSITION("^P.*") {
572
573 /** {@inheritDoc} */
574 @Override
575 public void parse(final String line, final ParseInfo pi) {
576 final String satelliteId = line.substring(1, 4).trim();
577
578 if (!pi.file.containsSatellite(satelliteId)) {
579 pi.latestPosition = null;
580 } else {
581 final double x = Double.parseDouble(line.substring(4, 18).trim());
582 final double y = Double.parseDouble(line.substring(18, 32).trim());
583 final double z = Double.parseDouble(line.substring(32, 46).trim());
584
585 // the position values are in km and have to be converted to m
586 pi.latestPosition = new Vector3D(x * 1000, y * 1000, z * 1000);
587
588 // clock (microsec)
589 pi.latestClock = line.length() <= 46 ?
590 DEFAULT_CLOCK_VALUE :
591 Double.parseDouble(line.substring(46, 60).trim()) * 1e-6;
592
593 // the additional items are optional and not read yet
594
595 // if (line.length() >= 73) {
596 // // x-sdev (b**n mm)
597 // int xStdDevExp = Integer.valueOf(line.substring(61,
598 // 63).trim());
599 // // y-sdev (b**n mm)
600 // int yStdDevExp = Integer.valueOf(line.substring(64,
601 // 66).trim());
602 // // z-sdev (b**n mm)
603 // int zStdDevExp = Integer.valueOf(line.substring(67,
604 // 69).trim());
605 // // c-sdev (b**n psec)
606 // int cStdDevExp = Integer.valueOf(line.substring(70,
607 // 73).trim());
608 //
609 // pi.posStdDevRecord =
610 // new PositionStdDevRecord(FastMath.pow(pi.posVelBase, xStdDevExp),
611 // FastMath.pow(pi.posVelBase,
612 // yStdDevExp), FastMath.pow(pi.posVelBase, zStdDevExp),
613 // FastMath.pow(pi.clockBase, cStdDevExp));
614 //
615 // String clockEventFlag = line.substring(74, 75);
616 // String clockPredFlag = line.substring(75, 76);
617 // String maneuverFlag = line.substring(78, 79);
618 // String orbitPredFlag = line.substring(79, 80);
619 // }
620
621 if (!pi.hasVelocityEntries) {
622 final SP3Coordinate coord =
623 new SP3Coordinate(pi.latestEpoch,
624 pi.latestPosition,
625 pi.latestClock);
626 pi.file.addSatelliteCoordinate(satelliteId, coord);
627 }
628 }
629 }
630
631 /** {@inheritDoc} */
632 @Override
633 public Stream<LineParser> allowedNext() {
634 return Stream.of(DATA_EPOCH, DATA_POSITION, DATA_POSITION_CORRELATION, DATA_VELOCITY, EOF);
635 }
636
637 },
638
639 /** Parser for position correlation. */
640 DATA_POSITION_CORRELATION("^EP.*") {
641
642 /** {@inheritDoc} */
643 @Override
644 public void parse(final String line, final ParseInfo pi) {
645 // ignored for now
646 }
647
648 /** {@inheritDoc} */
649 @Override
650 public Stream<LineParser> allowedNext() {
651 return Stream.of(DATA_EPOCH, DATA_POSITION, DATA_VELOCITY, EOF);
652 }
653
654 },
655
656 /** Parser for velocity. */
657 DATA_VELOCITY("^V.*") {
658
659 /** {@inheritDoc} */
660 @Override
661 public void parse(final String line, final ParseInfo pi) {
662 final String satelliteId = line.substring(1, 4).trim();
663
664 if (pi.file.containsSatellite(satelliteId)) {
665 final double xv = Double.parseDouble(line.substring(4, 18).trim());
666 final double yv = Double.parseDouble(line.substring(18, 32).trim());
667 final double zv = Double.parseDouble(line.substring(32, 46).trim());
668
669 // the velocity values are in dm/s and have to be converted to m/s
670 final Vector3D velocity = new Vector3D(xv / 10d, yv / 10d, zv / 10d);
671
672 // clock rate in file is 1e-4 us / s
673 final double clockRateChange = line.length() <= 46 ?
674 DEFAULT_CLOCK_VALUE :
675 Double.parseDouble(line.substring(46, 60).trim()) * 1e-4;
676
677 // the additional items are optional and not read yet
678
679 // if (line.length() >= 73) {
680 // // xvel-sdev (b**n 10**-4 mm/sec)
681 // int xVstdDevExp = Integer.valueOf(line.substring(61,
682 // 63).trim());
683 // // yvel-sdev (b**n 10**-4 mm/sec)
684 // int yVstdDevExp = Integer.valueOf(line.substring(64,
685 // 66).trim());
686 // // zvel-sdev (b**n 10**-4 mm/sec)
687 // int zVstdDevExp = Integer.valueOf(line.substring(67,
688 // 69).trim());
689 // // clkrate-sdev (b**n 10**-4 psec/sec)
690 // int clkStdDevExp = Integer.valueOf(line.substring(70,
691 // 73).trim());
692 // }
693
694 final SP3Coordinate coord =
695 new SP3Coordinate(pi.latestEpoch,
696 pi.latestPosition,
697 velocity,
698 pi.latestClock,
699 clockRateChange);
700 pi.file.addSatelliteCoordinate(satelliteId, coord);
701 }
702 }
703
704 /** {@inheritDoc} */
705 @Override
706 public Stream<LineParser> allowedNext() {
707 return Stream.of(DATA_EPOCH, DATA_POSITION, DATA_VELOCITY_CORRELATION, EOF);
708 }
709
710 },
711
712 /** Parser for velocity correlation. */
713 DATA_VELOCITY_CORRELATION("^EV.*") {
714
715 /** {@inheritDoc} */
716 @Override
717 public void parse(final String line, final ParseInfo pi) {
718 // ignored for now
719 }
720
721 /** {@inheritDoc} */
722 @Override
723 public Stream<LineParser> allowedNext() {
724 return Stream.of(DATA_EPOCH, DATA_POSITION, EOF);
725 }
726
727 },
728
729 /** Parser for End Of File marker. */
730 EOF("^[eE][oO][fF]\\s*$") {
731
732 /** {@inheritDoc} */
733 @Override
734 public void parse(final String line, final ParseInfo pi) {
735 pi.done = true;
736 }
737
738 /** {@inheritDoc} */
739 @Override
740 public Stream<LineParser> allowedNext() {
741 return Stream.of(EOF);
742 }
743
744 };
745
746 /** Pattern for identifying line. */
747 private final Pattern pattern;
748
749 /** Simple constructor.
750 * @param lineRegexp regular expression for identifying line
751 */
752 LineParser(final String lineRegexp) {
753 pattern = Pattern.compile(lineRegexp);
754 }
755
756 /** Parse a line.
757 * @param line line to parse
758 * @param pi holder for transient data
759 */
760 public abstract void parse(String line, ParseInfo pi);
761
762 /** Get the allowed parsers for next line.
763 * @return allowed parsers for next line
764 */
765 public abstract Stream<LineParser> allowedNext();
766
767 /** Check if parser can handle line.
768 * @param line line to parse
769 * @return true if parser can handle the specified line
770 */
771 public boolean canHandle(final String line) {
772 return pattern.matcher(line).matches();
773 }
774
775 }
776
777 }