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