1 /* Copyright 2002-2025 CS GROUP
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.rinex.section;
18
19 import org.hipparchus.util.FastMath;
20 import org.orekit.errors.OrekitException;
21 import org.orekit.errors.OrekitMessages;
22 import org.orekit.files.rinex.utils.ParsingUtils;
23 import org.orekit.files.rinex.utils.RinexFileType;
24 import org.orekit.gnss.PredefinedTimeSystem;
25 import org.orekit.gnss.SatelliteSystem;
26 import org.orekit.time.AbsoluteDate;
27 import org.orekit.time.DateComponents;
28 import org.orekit.time.DateTimeComponents;
29 import org.orekit.time.Month;
30 import org.orekit.time.TimeComponents;
31 import org.orekit.time.TimeScale;
32 import org.orekit.time.TimeScales;
33
34 import java.time.ZoneId;
35 import java.time.ZonedDateTime;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 /** Base container for Rinex headers.
40 * @since 12.0
41 */
42 public abstract class RinexBaseHeader {
43
44 /** Pattern for splitting date, time and time zone. */
45 private static final Pattern SPLITTING_PATTERN = Pattern.compile("([0-9]+[/ -]?[0-9A-Za-z]+[/ -]?[0-9]+) +([0-9:]+) *([A-Z]+)?");
46
47 /** Pattern for dates with month abbrevation. */
48 private static final Pattern DATE_DD_MMM_YY_PATTERN = Pattern.compile("([0-9]{1,2})-([A-Za-z]{3})-([0-9]{2,4})");
49
50 /** Pattern for dates with month abbrevation.
51 * @since 14.0
52 */
53 private static final Pattern DATE_YYYY_MMM_DD_PATTERN = Pattern.compile("([0-9]{4})[- ]([A-Za-z]{3})[- ]([0-9]{1,2})");
54
55 /** Pattern for dates in ISO-8601 complete representation (basic or extended). */
56 private static final Pattern DATE_ISO_8601_PATTERN = Pattern.compile("([0-9]{4})-?([0-9]{2})-?([0-9]{2})");
57
58 /** Pattern for dates in european format. */
59 private static final Pattern DATE_EUROPEAN_PATTERN = Pattern.compile("([0-9]{2})/([0-9]{2})/([0-9]{2})");
60
61 /** Pattern for time. */
62 private static final Pattern TIME_PATTERN = Pattern.compile("([0-9]{2}):?([0-9]{2})(?::?([0-9]{2}))?");
63
64 /** Orekit program name.
65 * @since 14.0
66 */
67 private static final String OREKIT = "Orekit";
68
69 /** User name property.
70 * @since 14.0
71 */
72 private static final String USER_NAME = "user.name";
73
74 /** File type . */
75 private final RinexFileType fileType;
76
77 /** Rinex format Version. */
78 private double formatVersion;
79
80 /** Satellite System of the Rinex file (G/R/S/E/M). */
81 private SatelliteSystem satelliteSystem;
82
83 /** Name of the program creating current file. */
84 private String programName;
85
86 /** Name of the creator of the current file. */
87 private String runByName;
88
89 /** Date of the file creation. */
90 private DateTimeComponents creationDateComponents;
91
92 /** Time zone of the file creation. */
93 private String creationTimeZone;
94
95 /** Creation date as absolute date. */
96 private AbsoluteDate creationDate;
97
98 /** Receiver Number.
99 * @since 14.0
100 */
101 private String receiverNumber;
102
103 /** Receiver Type.
104 * @since 14.0
105 */
106 private String receiverType;
107
108 /** Receiver version.
109 * @since 14.0
110 */
111 private String receiverVersion;
112
113 /** Number of leap seconds separating UTC and GNSS time systems.
114 * <p>
115 * This is really the number of leap seconds since GPS epoch
116 * on 1980-01-06.
117 * </p>
118 * @since 14.0
119 */
120 private int leapSecondsGNSS;
121
122 /** Future or past leap seconds ΔtLSF (BNK).
123 * i.e. future leap second if the week and day number are in the future.
124 * @since 14.0
125 */
126 private int leapSecondsFuture;
127
128 /** Respective leap second week number.
129 * For GPS, GAL, QZS and IRN, weeks since 6-Jan-1980.
130 * When BDS only file leap seconds specified, weeks since 1-Jan-2006
131 * @since 14.0
132 */
133 private int leapSecondsWeekNum;
134
135 /** Respective leap second day number.
136 * @since 14.0
137 */
138 private int leapSecondsDayNum;
139
140 /** Digital Object Identifier.
141 * @since 12.0
142 */
143 private String doi;
144
145 /** License of use.
146 * @since 12.0
147 */
148 private String license;
149
150 /** Station information.
151 * @since 12.0
152 */
153 private String stationInformation;
154
155 /** Simple constructor.
156 * @param fileType file type
157 */
158 protected RinexBaseHeader(final RinexFileType fileType) {
159
160 this.fileType = fileType;
161 this.formatVersion = Double.NaN;
162
163 // set default creation date to now
164 final ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));
165 setCreationDateComponents(new DateTimeComponents(new DateComponents(now.getYear(),
166 now.getMonthValue(),
167 now.getDayOfMonth()),
168 new TimeComponents(now.getHour(),
169 now.getMinute(),
170 now.getSecond())));
171
172 // set default program name to Orekit
173 setProgramName(OREKIT);
174
175 // set default run-by name to user
176 setRunByName(System.getProperty(USER_NAME));
177
178 }
179
180 /**
181 * Get the file type.
182 * @return file type
183 */
184 public RinexFileType getFileType() {
185 return fileType;
186 }
187
188 /**
189 * Getter for the format version.
190 * @return the format version
191 */
192 public double getFormatVersion() {
193 return formatVersion;
194 }
195
196 /**
197 * Setter for the format version.
198 * @param formatVersion the format version to set
199 */
200 public void setFormatVersion(final double formatVersion) {
201 this.formatVersion = formatVersion;
202 }
203
204 /**
205 * Getter for the satellite system.
206 * <p>
207 * Not specified for RINEX 2.X versions (value is null).
208 * </p>
209 * @return the satellite system
210 */
211 public SatelliteSystem getSatelliteSystem() {
212 return satelliteSystem;
213 }
214
215 /**
216 * Setter for the satellite system.
217 * @param satelliteSystem the satellite system to set
218 */
219 public void setSatelliteSystem(final SatelliteSystem satelliteSystem) {
220 this.satelliteSystem = satelliteSystem;
221 }
222
223 /**
224 * Parse satellite system.
225 * @param line header line
226 * @param defaultSatelliteSystem satellite system to use if string is null or empty
227 * @return parsed satellite system
228 * @since 14.0
229 */
230 public abstract SatelliteSystem parseSatelliteSystem(String line, SatelliteSystem defaultSatelliteSystem);
231
232 /**
233 * Getter for the program name.
234 * @return the program name
235 */
236 public String getProgramName() {
237 return programName;
238 }
239
240 /**
241 * Setter for the program name.
242 * @param programName the program name to set
243 */
244 public void setProgramName(final String programName) {
245 this.programName = programName;
246 }
247
248 /**
249 * Getter for the run/by name.
250 * @return the run/by name
251 */
252 public String getRunByName() {
253 return runByName;
254 }
255
256 /**
257 * Setter for the run/by name.
258 * @param runByName the run/by name to set
259 */
260 public void setRunByName(final String runByName) {
261 this.runByName = runByName;
262 }
263
264 /**
265 * Getter for the creation date of the file as a string.
266 * @return the creation date
267 */
268 public DateTimeComponents getCreationDateComponents() {
269 return creationDateComponents;
270 }
271
272 /**
273 * Setter for the creation date as a string.
274 * @param creationDateComponents the creation date to set
275 */
276 public void setCreationDateComponents(final DateTimeComponents creationDateComponents) {
277 this.creationDateComponents = creationDateComponents;
278 }
279
280 /**
281 * Getter for the creation time zone of the file as a string.
282 * @return the creation time zone as a string
283 */
284 public String getCreationTimeZone() {
285 return creationTimeZone;
286 }
287
288 /**
289 * Setter for the creation time zone.
290 * @param creationTimeZone the creation time zone to set
291 */
292 public void setCreationTimeZone(final String creationTimeZone) {
293 this.creationTimeZone = creationTimeZone;
294 }
295
296 /**
297 * Getter for the creation date.
298 * <p>
299 * The creation date seems to be mandatory, but we have seen several files
300 * missing it, even files created by IGS itself (in clock files, essentially).
301 * We accept these null dates to at least allow parsing the files
302 * as this header information does not really seem essential
303 * </p>
304 * @return the creation date
305 */
306 public AbsoluteDate getCreationDate() {
307 return creationDate;
308 }
309
310 /**
311 * Setter for the creation date.
312 * @param creationDate the creation date to set
313 */
314 public void setCreationDate(final AbsoluteDate creationDate) {
315 this.creationDate = creationDate;
316 }
317
318 /** Set the number of the receiver.
319 * @param receiverNumber number of the receiver
320 */
321 public void setReceiverNumber(final String receiverNumber) {
322 this.receiverNumber = receiverNumber;
323 }
324
325 /** Get the number of the receiver.
326 * @return number of the receiver
327 */
328 public String getReceiverNumber() {
329 return receiverNumber;
330 }
331
332 /** Set the type of the receiver.
333 * @param receiverType type of the receiver
334 */
335 public void setReceiverType(final String receiverType) {
336 this.receiverType = receiverType;
337 }
338
339 /** Get the type of the receiver.
340 * @return type of the receiver
341 */
342 public String getReceiverType() {
343 return receiverType;
344 }
345
346 /** Set the version of the receiver.
347 * @param receiverVersion version of the receiver
348 */
349 public void setReceiverVersion(final String receiverVersion) {
350 this.receiverVersion = receiverVersion;
351 }
352
353 /** Get the version of the receiver.
354 * @return version of the receiver
355 */
356 public String getReceiverVersion() {
357 return receiverVersion;
358 }
359
360 /** Getter for the number of leap second for GNSS time scales.
361 * @return the number of leap seconds for GNSS time scales
362 * @since 14.0
363 */
364 public int getLeapSecondsGNSS() {
365 return leapSecondsGNSS;
366 }
367
368 /** Setter for the number of leap seconds for GNSS time scales.
369 * @param leapSecondsGNSS the number of leap seconds for GNSS time scales to set
370 * @since 14.0
371 */
372 public void setLeapSecondsGNSS(final int leapSecondsGNSS) {
373 this.leapSecondsGNSS = leapSecondsGNSS;
374 }
375
376 /** Set the future or past leap seconds.
377 * @param leapSecondsFuture Future or past leap seconds
378 * @since 14.0
379 */
380 public void setLeapSecondsFuture(final int leapSecondsFuture) {
381 this.leapSecondsFuture = leapSecondsFuture;
382 }
383
384 /** Get the future or past leap seconds.
385 * @return Future or past leap seconds
386 * @since 14.0
387 */
388 public int getLeapSecondsFuture() {
389 return leapSecondsFuture;
390 }
391
392 /** Set the respective leap second week number.
393 * @param leapSecondsWeekNum Respective leap second week number
394 * @since 14.0
395 */
396 public void setLeapSecondsWeekNum(final int leapSecondsWeekNum) {
397 this.leapSecondsWeekNum = leapSecondsWeekNum;
398 }
399
400 /** Get the respective leap second week number.
401 * @return Respective leap second week number
402 * @since 14.0
403 */
404 public int getLeapSecondsWeekNum() {
405 return leapSecondsWeekNum;
406 }
407
408 /** Set the respective leap second day number.
409 * @param leapSecondsDayNum Respective leap second day number
410 * @since 14.0
411 */
412 public void setLeapSecondsDayNum(final int leapSecondsDayNum) {
413 this.leapSecondsDayNum = leapSecondsDayNum;
414 }
415
416 /** Get the respective leap second day number.
417 * @return Respective leap second day number
418 * @since 14.0
419 */
420 public int getLeapSecondsDayNum() {
421 return leapSecondsDayNum;
422 }
423
424 /**
425 * Getter for the Digital Object Information.
426 * @return the Digital Object Information
427 * @since 12.0
428 */
429 public String getDoi() {
430 return doi;
431 }
432
433 /**
434 * Setter for the Digital Object Information.
435 * @param doi the Digital Object Information to set
436 * @since 12.0
437 */
438 public void setDoi(final String doi) {
439 this.doi = doi;
440 }
441
442 /**
443 * Getter for the license of use.
444 * @return the license of use
445 * @since 12.0
446 */
447 public String getLicense() {
448 return license;
449 }
450
451 /**
452 * Setter for the license of use.
453 * @param license the license of use
454 * @since 12.0
455 */
456 public void setLicense(final String license) {
457 this.license = license;
458 }
459
460 /**
461 * Getter for the station information.
462 * @return the station information
463 * @since 12.0
464 */
465 public String getStationInformation() {
466 return stationInformation;
467 }
468
469 /**
470 * Setter for the station information.
471 * @param stationInformation the station information to set
472 * @since 12.0
473 */
474 public void setStationInformation(final String stationInformation) {
475 this.stationInformation = stationInformation;
476 }
477
478 /** Parse version, file type and satellite system.
479 * @param line line to parse
480 * @param defaultSatelliteSystem satellite system to use if string is null or empty
481 * @param name file name (for error message generation)
482 * @param supportedVersions supported versions
483 * @since 14.0
484 */
485 public void parseVersionFileTypeSatelliteSystem(final String line, final SatelliteSystem defaultSatelliteSystem,
486 final String name, final double... supportedVersions) {
487
488 // Rinex version
489 final double parsedVersion = ParsingUtils.parseDouble(line, 0, 9);
490
491 boolean found = false;
492 for (final double supported : supportedVersions) {
493 if (FastMath.abs(parsedVersion - supported) < 1.0e-4) {
494 found = true;
495 break;
496 }
497 }
498 if (!found) {
499 final StringBuilder builder = new StringBuilder();
500 for (final double supported : supportedVersions) {
501 if (builder.length() > 0) {
502 builder.append(", ");
503 }
504 builder.append(supported);
505 }
506 throw new OrekitException(OrekitMessages.UNSUPPORTED_FILE_FORMAT_VERSION,
507 parsedVersion, name, builder.toString());
508 }
509 setFormatVersion(parsedVersion);
510
511 // file type
512 checkType(line, name);
513
514 // Satellite system
515 setSatelliteSystem(parseSatelliteSystem(line, defaultSatelliteSystem));
516
517 }
518
519 /** Parse program, run/by and date.
520 * @param line line to parse
521 * @param timeScales the set of time scales used for parsing dates
522 * @since 14.0
523 */
524 public abstract void parseProgramRunByDate(String line, TimeScales timeScales);
525
526 /** Parse program, run/by and date.
527 * @param prgm PGM field
528 * @param run RUN BY field
529 * @param date date field
530 * @param timeScales the set of time scales used for parsing dates
531 * @since 14.0
532 */
533 protected void parseProgramRunByDate(final String prgm, final String run, final String date,
534 final TimeScales timeScales) {
535
536 // Name of the generating program
537 setProgramName(prgm);
538
539 // Name of the run/by name
540 setRunByName(run);
541
542 // there are several variations for date formatting in the PGM / RUN BY / DATE line
543
544 // in versions 2.x, the pattern is expected to be:
545 // XXRINEXO V9.9 AIUB 24-MAR-01 14:43 PGM / RUN BY / DATE
546 // however, we have also found this:
547 // teqc 2016Nov7 root 20180130 10:38:06UTCPGM / RUN BY / DATE
548 // BJFMTLcsr UTCSR 2007-09-30 05:30:06 PGM / RUN BY / DATE
549 // NEODIS TAS 27/05/22 10:28 PGM / RUN BY / DATE
550
551 // in versions 3.x, the pattern is expected to be:
552 // sbf2rin-11.3.3 20180130 002558 LCL PGM / RUN BY / DATE
553 // however, we have also found:
554 // NetR9 5.03 Receiver Operator 11-JAN-16 00:00:00 PGM / RUN BY / DATE
555
556 // in clock files, we have found patterns like:
557 // CLKRINEX V1.0 NRCan 1-Mar-2000 20:36 PGM / RUN BY / DATE
558 // tdp2clk v1.13 JPL 2002 Jan 3 13:36:17 PGM / RUN BY / DATE
559
560 // so we cannot rely on the format version, we have to check several variations
561 final Matcher splittingMatcher = SPLITTING_PATTERN.matcher(date);
562 if (splittingMatcher.matches()) {
563
564 // date part
565 final DateComponents dc;
566 final Matcher abbrev1Matcher = DATE_DD_MMM_YY_PATTERN.matcher(splittingMatcher.group(1));
567 if (abbrev1Matcher.matches()) {
568 final int rawYear = Integer.parseInt(abbrev1Matcher.group(3));
569 // hoping this obsolete format will not be used past year 2079…
570 dc = new DateComponents(rawYear < 100 ? ParsingUtils.convert2DigitsYear(rawYear) : rawYear,
571 Month.parseMonth(abbrev1Matcher.group(2)).getNumber(),
572 Integer.parseInt(abbrev1Matcher.group(1)));
573 } else {
574 final Matcher abbrev2Matcher = DATE_YYYY_MMM_DD_PATTERN.matcher(splittingMatcher.group(1));
575 if (abbrev2Matcher.matches()) {
576 dc = new DateComponents(Integer.parseInt(abbrev2Matcher.group(1)),
577 Month.parseMonth(abbrev2Matcher.group(2)).getNumber(),
578 Integer.parseInt(abbrev2Matcher.group(3)));
579 } else {
580 final Matcher isoMatcher = DATE_ISO_8601_PATTERN.matcher(splittingMatcher.group(1));
581 if (isoMatcher.matches()) {
582 dc = new DateComponents(Integer.parseInt(isoMatcher.group(1)),
583 Integer.parseInt(isoMatcher.group(2)),
584 Integer.parseInt(isoMatcher.group(3)));
585 } else {
586 final Matcher europeanMatcher = DATE_EUROPEAN_PATTERN.matcher(splittingMatcher.group(1));
587 if (europeanMatcher.matches()) {
588 dc = new DateComponents(
589 ParsingUtils.convert2DigitsYear(Integer.parseInt(europeanMatcher.group(3))),
590 Integer.parseInt(europeanMatcher.group(2)),
591 Integer.parseInt(europeanMatcher.group(1)));
592 } else {
593 dc = null;
594 }
595 }
596 }
597 }
598
599 // time part
600 final TimeComponents tc;
601 final Matcher timeMatcher = TIME_PATTERN.matcher(splittingMatcher.group(2));
602 if (timeMatcher.matches()) {
603 tc = new TimeComponents(Integer.parseInt(timeMatcher.group(1)),
604 Integer.parseInt(timeMatcher.group(2)),
605 timeMatcher.group(3) != null ? Integer.parseInt(timeMatcher.group(3)) : 0);
606 } else {
607 tc = TimeComponents.H00;
608 }
609
610 // zone part
611 final String zone = splittingMatcher.group(3);
612 setCreationTimeZone(zone == null ? "" : zone);
613
614 if (dc == null) {
615 // despite the creation date seems to be mandatory, we have seen several files
616 // missing it, even files created by IGS itself (in clock files, essentially).
617 // We accept these null dates to at least allow parsing the files
618 // as this header information does not really seem essential
619 setCreationDate(null);
620 } else {
621 // we successfully parsed everything
622 final DateTimeComponents dtc = new DateTimeComponents(dc, tc);
623 setCreationDateComponents(dtc);
624 final TimeScale timeScale = zone == null ?
625 timeScales.getUTC() :
626 PredefinedTimeSystem.parseTimeSystem(zone).getTimeScale(timeScales);
627 setCreationDate(new AbsoluteDate(dtc, timeScale));
628 }
629
630 } else {
631 setCreationDate(null);
632 setCreationTimeZone("");
633 }
634
635 }
636
637 /** Check file type.
638 * @param line header line
639 * @param name file name (for error message)
640 * @since 14.0
641 */
642 public abstract void checkType(String line, String name);
643
644 /** Check file type.
645 * @param line header line
646 * @param typeIndex index of the file type in the line
647 * @param name file name (for error message)
648 * @since 14.0
649 */
650 protected void checkType(final String line, final int typeIndex, final String name) {
651 if (fileType != RinexFileType.parseRinexFileType(line.substring(typeIndex, typeIndex + 1))) {
652 throw new OrekitException(OrekitMessages.WRONG_PARSING_TYPE, name);
653 }
654 }
655
656 /** Get the index of the header label.
657 * @return index of the header label
658 * @since 14.0
659 */
660 public abstract int getLabelIndex();
661
662 /** Check if a label is found in a line.
663 * @param label label to check
664 * @param line header line
665 * @return true if label is found in the header line
666 * @since 14.0
667 */
668 public abstract boolean matchFound(Label label, String line);
669
670 }