1 /* Copyright 2002-2012 Space Applications Services
2 * Licensed to CS Systèmes d'Information (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.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.util.Locale;
26 import java.util.Scanner;
27
28 import org.hipparchus.exception.DummyLocalizable;
29 import org.hipparchus.geometry.euclidean.threed.Vector3D;
30 import org.hipparchus.util.FastMath;
31 import org.orekit.errors.OrekitException;
32 import org.orekit.errors.OrekitMessages;
33 import org.orekit.files.general.OrbitFile.TimeSystem;
34 import org.orekit.files.general.OrbitFileParser;
35 import org.orekit.files.general.SatelliteInformation;
36 import org.orekit.files.general.SatelliteTimeCoordinate;
37 import org.orekit.files.sp3.SP3File.SP3FileType;
38 import org.orekit.files.sp3.SP3File.SP3OrbitType;
39 import org.orekit.time.AbsoluteDate;
40 import org.orekit.time.TimeScale;
41 import org.orekit.time.TimeScalesFactory;
42 import org.orekit.utils.PVCoordinates;
43
44 /** A parser for the SP3 orbit file format. It supports the original format as
45 * well as the latest SP3-c version.
46 * <p>
47 * <b>Note:</b> this parser is thread-safe, so calling {@link #parse} from
48 * different threads is allowed.
49 * </p>
50 * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3_docu.txt">SP3-a file format</a>
51 * @see <a href="http://igscb.jpl.nasa.gov/igscb/data/format/sp3c.txt">SP3-c file format</a>
52 * @author Thomas Neidhart
53 */
54 public class SP3Parser implements OrbitFileParser {
55
56 /** {@inheritDoc} */
57 public SP3File parse(final String fileName) throws OrekitException {
58
59 InputStream stream = null;
60
61 try {
62 stream = new FileInputStream(fileName);
63 return parse(stream);
64 } catch (FileNotFoundException e) {
65 throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, fileName);
66 } finally {
67 try {
68 if (stream != null) {
69 stream.close();
70 }
71 } catch (IOException e) {
72 // ignore
73 }
74 }
75 }
76
77 /** {@inheritDoc} */
78 public SP3File parse(final InputStream stream) throws OrekitException {
79 try {
80 return parseInternal(stream);
81 } catch (IOException e) {
82 throw new OrekitException(e, new DummyLocalizable(e.getMessage()));
83 }
84 }
85
86 /** Parses the SP3 file from the given {@link InputStream} and
87 * returns a {@link SP3File} object.
88 * @param stream the stream to read the SP3File from
89 * @return the parsed {@link SP3File} object
90 * @throws OrekitException if the file could not be parsed successfully
91 * @throws IOException if an error occurs while reading from the stream
92 */
93 private SP3File parseInternal(final InputStream stream)
94 throws OrekitException, IOException {
95
96 final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
97
98 // initialize internal data structures
99 final ParseInfo pi = new ParseInfo();
100
101 String line = null;
102 int lineNumber = 1;
103 try {
104 while (lineNumber < 23) {
105 line = reader.readLine();
106 if (line == null) {
107 throw new OrekitException(OrekitMessages.SP3_UNEXPECTED_END_OF_FILE, lineNumber - 1);
108 } else {
109 parseHeaderLine(lineNumber++, line, pi);
110 }
111 }
112
113 // now handle the epoch/position/velocity entries
114
115 boolean done = false;
116 do {
117 line = reader.readLine();
118 if (line == null || "EOF".equalsIgnoreCase(line.trim())) {
119 done = true;
120 } else if (line.length() > 0) {
121 parseContentLine(line, pi);
122 }
123 } while (!done);
124 } finally {
125 try {
126 reader.close();
127 } catch (IOException e1) {
128 // ignore
129 }
130 }
131
132 return pi.file;
133 }
134
135 /** Parses a header line from the SP3 file (line number 1 - 22).
136 * @param lineNumber the current line number
137 * @param line the line as read from the SP3 file
138 * @param pi the current {@link ParseInfo} object
139 * @throws OrekitException if a non-supported construct is found
140 */
141 private void parseHeaderLine(final int lineNumber, final String line, final ParseInfo pi)
142 throws OrekitException {
143
144 final SP3File file = pi.file;
145
146 try (final Scanner s1 = new Scanner(line);
147 final Scanner s2 = s1.useDelimiter("\\s+");
148 final Scanner scanner = s2.useLocale(Locale.US)) {
149
150 // CHECKSTYLE: stop FallThrough check
151
152 switch (lineNumber) {
153
154 // version, epoch, data used and agency information
155 case 1: {
156 scanner.skip("#");
157 final String v = scanner.next();
158
159 final char version = v.substring(0, 1).toLowerCase().charAt(0);
160 if (version != 'a' && version != 'b' && version != 'c') {
161 throw new OrekitException(OrekitMessages.SP3_UNSUPPORTED_VERSION, version);
162 }
163
164 pi.hasVelocityEntries = "V".equals(v.substring(1, 2));
165
166 final int year = Integer.parseInt(v.substring(2));
167 final int month = scanner.nextInt();
168 final int day = scanner.nextInt();
169 final int hour = scanner.nextInt();
170 final int minute = scanner.nextInt();
171 final double second = scanner.nextDouble();
172
173 final AbsoluteDate epoch = new AbsoluteDate(year, month, day,
174 hour, minute, second,
175 TimeScalesFactory.getGPS());
176
177 file.setEpoch(epoch);
178
179 final int numEpochs = scanner.nextInt();
180 file.setNumberOfEpochs(numEpochs);
181
182 // data used indicator
183 file.setDataUsed(scanner.next());
184
185 file.setCoordinateSystem(scanner.next());
186 file.setOrbitType(SP3OrbitType.parseType(scanner.next()));
187 file.setAgency(scanner.next());
188 break;
189 }
190
191 // additional date/time references in gps/julian day notation
192 case 2: {
193 scanner.skip("##");
194
195 // gps week
196 file.setGpsWeek(scanner.nextInt());
197 // seconds of week
198 file.setSecondsOfWeek(scanner.nextDouble());
199 // epoch interval
200 file.setEpochInterval(scanner.nextDouble());
201 // julian day
202 file.setJulianDay(scanner.nextInt());
203 // day fraction
204 file.setDayFraction(scanner.nextDouble());
205 break;
206 }
207
208 // line 3 contains the number of satellites
209 case 3:
210 pi.maxSatellites = Integer.parseInt(line.substring(4, 6).trim());
211 // fall-through intended - the line contains already the first entries
212
213 // the following 4 lines contain additional satellite ids
214 case 4:
215 case 5:
216 case 6:
217 case 7: {
218 final int lineLength = line.length();
219 int count = file.getSatelliteCount();
220 int startIdx = 9;
221 while (count++ < pi.maxSatellites && (startIdx + 3) <= lineLength) {
222 final String satId = line.substring(startIdx, startIdx + 3).trim();
223 file.addSatellite(satId);
224 startIdx += 3;
225 }
226 break;
227 }
228
229 // the following 5 lines contain general accuracy information for each satellite
230 case 8:
231 case 9:
232 case 10:
233 case 11:
234 case 12: {
235 final int lineLength = line.length();
236 int satIdx = (lineNumber - 8) * 17;
237 int startIdx = 9;
238 while (satIdx < pi.maxSatellites && (startIdx + 3) <= lineLength) {
239 final SatelliteInformation satInfo = file.getSatellite(satIdx++);
240 final int exponent = Integer.parseInt(line.substring(startIdx, startIdx + 3).trim());
241 // the accuracy is calculated as 2**exp (in m) -> can be safely
242 // converted to an integer as there will be no fraction
243 satInfo.setAccuracy((int) FastMath.pow(2d, exponent));
244 startIdx += 3;
245 }
246 break;
247 }
248
249 case 13: {
250 file.setType(getFileType(line.substring(3, 5).trim()));
251
252 // now identify the time system in use
253 final String tsStr = line.substring(9, 12).trim();
254 final TimeSystem ts;
255 if (tsStr.equalsIgnoreCase("ccc")) {
256 ts = TimeSystem.GPS;
257 } else {
258 ts = TimeSystem.valueOf(tsStr);
259 }
260 file.setTimeSystem(ts);
261
262 switch (ts) {
263 case GPS:
264 pi.timeScale = TimeScalesFactory.getGPS();
265 break;
266
267 case GAL:
268 pi.timeScale = TimeScalesFactory.getGST();
269 break;
270
271 case GLO:
272 pi.timeScale = TimeScalesFactory.getGLONASS();
273 break;
274
275 case QZS:
276 pi.timeScale = TimeScalesFactory.getQZSS();
277
278 case TAI:
279 pi.timeScale = TimeScalesFactory.getTAI();
280 break;
281
282 case UTC:
283 pi.timeScale = TimeScalesFactory.getUTC();
284 break;
285
286 default:
287 pi.timeScale = TimeScalesFactory.getGPS();
288 break;
289 }
290 break;
291 }
292
293 case 14:
294 // ignore additional custom fields
295 break;
296
297 // load base numbers for the standard deviations of
298 // position/velocity/clock components
299 case 15: {
300 // String base = line.substring(3, 13).trim();
301 // if (!base.equals("0.0000000")) {
302 // // (mm or 10**-4 mm/sec)
303 // pi.posVelBase = Double.valueOf(base);
304 // }
305
306 // base = line.substring(14, 26).trim();
307 // if (!base.equals("0.000000000")) {
308 // // (psec or 10**-4 psec/sec)
309 // pi.clockBase = Double.valueOf(base);
310 // }
311 break;
312 }
313
314 case 16:
315 case 17:
316 case 18:
317 // ignore additional custom parameters
318 break;
319
320 case 19:
321 case 20:
322 case 21:
323 case 22:
324 // ignore comment lines
325 break;
326 default:
327 // ignore -> method should only be called up to line 22
328 break;
329 }
330
331 // CHECKSTYLE: resume FallThrough check
332
333 }
334
335 }
336
337 /** Parses a single content line as read from the SP3 file.
338 * @param line a string containing the line
339 * @param pi the current {@link ParseInfo} object
340 */
341 private void parseContentLine(final String line, final ParseInfo pi) {
342 // EP and EV lines are ignored so far
343
344 final SP3File file = pi.file;
345
346 switch (line.charAt(0)) {
347 case '*': {
348 final int year = Integer.parseInt(line.substring(3, 7).trim());
349 final int month = Integer.parseInt(line.substring(8, 10).trim());
350 final int day = Integer.parseInt(line.substring(11, 13).trim());
351 final int hour = Integer.parseInt(line.substring(14, 16).trim());
352 final int minute = Integer.parseInt(line.substring(17, 19).trim());
353 final double second = Double.parseDouble(line.substring(20, 31).trim());
354
355 pi.latestEpoch = new AbsoluteDate(year, month, day,
356 hour, minute, second,
357 pi.timeScale);
358 break;
359 }
360
361 case 'P': {
362 final String satelliteId = line.substring(1, 4).trim();
363
364 if (!file.containsSatellite(satelliteId)) {
365 pi.latestPosition = null;
366 } else {
367 final double x = Double.parseDouble(line.substring(4, 18).trim());
368 final double y = Double.parseDouble(line.substring(18, 32).trim());
369 final double z = Double.parseDouble(line.substring(32, 46).trim());
370
371 // the position values are in km and have to be converted to m
372 pi.latestPosition = new Vector3D(x * 1000, y * 1000, z * 1000);
373
374 // clock (microsec)
375 pi.latestClock = Double.parseDouble(line.substring(46, 60).trim());
376
377 // the additional items are optional and not read yet
378
379 // if (line.length() >= 73) {
380 // // x-sdev (b**n mm)
381 // int xStdDevExp = Integer.valueOf(line.substring(61,
382 // 63).trim());
383 // // y-sdev (b**n mm)
384 // int yStdDevExp = Integer.valueOf(line.substring(64,
385 // 66).trim());
386 // // z-sdev (b**n mm)
387 // int zStdDevExp = Integer.valueOf(line.substring(67,
388 // 69).trim());
389 // // c-sdev (b**n psec)
390 // int cStdDevExp = Integer.valueOf(line.substring(70,
391 // 73).trim());
392 //
393 // pi.posStdDevRecord =
394 // new PositionStdDevRecord(FastMath.pow(pi.posVelBase, xStdDevExp),
395 // FastMath.pow(pi.posVelBase,
396 // yStdDevExp), FastMath.pow(pi.posVelBase, zStdDevExp),
397 // FastMath.pow(pi.clockBase, cStdDevExp));
398 //
399 // String clockEventFlag = line.substring(74, 75);
400 // String clockPredFlag = line.substring(75, 76);
401 // String maneuverFlag = line.substring(78, 79);
402 // String orbitPredFlag = line.substring(79, 80);
403 // }
404
405 if (!pi.hasVelocityEntries) {
406 final SatelliteTimeCoordinate coord =
407 new SatelliteTimeCoordinate(pi.latestEpoch,
408 pi.latestPosition,
409 pi.latestClock);
410 file.addSatelliteCoordinate(satelliteId, coord);
411 }
412 }
413 break;
414 }
415
416 case 'V': {
417 final String satelliteId = line.substring(1, 4).trim();
418
419 if (file.containsSatellite(satelliteId)) {
420 final double xv = Double.parseDouble(line.substring(4, 18).trim());
421 final double yv = Double.parseDouble(line.substring(18, 32).trim());
422 final double zv = Double.parseDouble(line.substring(32, 46).trim());
423
424 // the velocity values are in dm/s and have to be converted to m/s
425 final Vector3D velocity = new Vector3D(xv / 10d, yv / 10d, zv / 10d);
426
427 final double clockRateChange = Double.parseDouble(line.substring(46, 60).trim());
428
429 // the additional items are optional and not read yet
430
431 // if (line.length() >= 73) {
432 // // xvel-sdev (b**n 10**-4 mm/sec)
433 // int xVstdDevExp = Integer.valueOf(line.substring(61,
434 // 63).trim());
435 // // yvel-sdev (b**n 10**-4 mm/sec)
436 // int yVstdDevExp = Integer.valueOf(line.substring(64,
437 // 66).trim());
438 // // zvel-sdev (b**n 10**-4 mm/sec)
439 // int zVstdDevExp = Integer.valueOf(line.substring(67,
440 // 69).trim());
441 // // clkrate-sdev (b**n 10**-4 psec/sec)
442 // int clkStdDevExp = Integer.valueOf(line.substring(70,
443 // 73).trim());
444 // }
445
446 final SatelliteTimeCoordinate coord =
447 new SatelliteTimeCoordinate(pi.latestEpoch,
448 new PVCoordinates(pi.latestPosition, velocity),
449 pi.latestClock,
450 clockRateChange);
451 file.addSatelliteCoordinate(satelliteId, coord);
452 }
453 break;
454 }
455
456 default:
457 // ignore everything else
458 break;
459 }
460 }
461
462 /** Returns the {@link SP3FileType} that corresponds to a given string in a SP3 file.
463 * @param fileType file type as string
464 * @return file type as enum
465 */
466 private SP3FileType getFileType(final String fileType) {
467 SP3FileType type = SP3FileType.UNDEFINED;
468 if ("G".equalsIgnoreCase(fileType)) {
469 type = SP3FileType.GPS;
470 } else if ("M".equalsIgnoreCase(fileType)) {
471 type = SP3FileType.MIXED;
472 } else if ("R".equalsIgnoreCase(fileType)) {
473 type = SP3FileType.GLONASS;
474 } else if ("L".equalsIgnoreCase(fileType)) {
475 type = SP3FileType.LEO;
476 } else if ("E".equalsIgnoreCase(fileType)) {
477 type = SP3FileType.GALILEO;
478 } else if ("C".equalsIgnoreCase(fileType)) {
479 type = SP3FileType.COMPASS;
480 } else if ("J".equalsIgnoreCase(fileType)) {
481 type = SP3FileType.QZSS;
482 }
483 return type;
484 }
485
486 /** Transient data used for parsing a sp3 file. The data is kept in a
487 * separate data structure to make the parser thread-safe.
488 * <p><b>Note</b>: The class intentionally does not provide accessor
489 * methods, as it is only used internally for parsing a SP3 file.</p>
490 */
491 private static class ParseInfo {
492
493 /** The corresponding SP3File object. */
494 private SP3File file;
495
496 /** The latest epoch as read from the SP3 file. */
497 private AbsoluteDate latestEpoch;
498
499 /** The latest position as read from the SP3 file. */
500 private Vector3D latestPosition;
501
502 /** The latest clock value as read from the SP3 file. */
503 private double latestClock;
504
505 /** Indicates if the SP3 file has velocity entries. */
506 private boolean hasVelocityEntries;
507
508 /** The timescale used in the SP3 file. */
509 private TimeScale timeScale;
510
511 /** The number of satellites as contained in the SP3 file. */
512 private int maxSatellites;
513
514 /** The base for pos/vel. */
515 //private double posVelBase;
516
517 /** The base for clock/rate. */
518 //private double clockBase;
519
520 /** Create a new {@link ParseInfo} object. */
521 protected ParseInfo() {
522 file = new SP3File();
523 latestEpoch = null;
524 latestPosition = null;
525 latestClock = 0.0d;
526 hasVelocityEntries = false;
527 timeScale = TimeScalesFactory.getGPS();
528 maxSatellites = 0;
529 //posVelBase = 2d;
530 //clockBase = 2d;
531 }
532 }
533 }