1 /* Copyright 2002-2021 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.sinex;
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.text.ParseException;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.regex.Pattern;
30
31 import org.hipparchus.exception.DummyLocalizable;
32 import org.hipparchus.geometry.euclidean.threed.Vector3D;
33 import org.hipparchus.util.FastMath;
34 import org.orekit.annotation.DefaultDataContext;
35 import org.orekit.data.DataContext;
36 import org.orekit.data.DataLoader;
37 import org.orekit.data.DataProvidersManager;
38 import org.orekit.data.DataSource;
39 import org.orekit.errors.OrekitException;
40 import org.orekit.errors.OrekitMessages;
41 import org.orekit.files.sinex.Station.ReferenceSystem;
42 import org.orekit.time.AbsoluteDate;
43 import org.orekit.time.DateComponents;
44 import org.orekit.time.TimeScale;
45 import org.orekit.utils.Constants;
46
47 /**
48 * Loader for Solution INdependent EXchange (SINEX) files.
49 * <p>
50 * For now only few keys are supported: SITE/ID, SITE/ECCENTRICITY, SOLUTION/EPOCHS and SOLUTION/ESTIMATE.
51 * They represent the minimum set of parameters that are interesting to consider in a SINEX file.
52 * </p>
53 * @author Bryan Cazabonne
54 * @since 10.3
55 */
56 public class SinexLoader {
57
58 /** Pattern for delimiting regular expressions. */
59 private static final Pattern SEPARATOR = Pattern.compile(":");
60
61 /** Station data.
62 * Key: Site code
63 */
64 private final Map<String, Station> stations;
65
66 /** UTC time scale. */
67 private final TimeScale utc;
68
69 /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
70 * default data context}.
71 * @param supportedNames regular expression for supported files names
72 * @see #SinexLoader(String, DataProvidersManager, TimeScale)
73 */
74 @DefaultDataContext
75 public SinexLoader(final String supportedNames) {
76 this(supportedNames,
77 DataContext.getDefault().getDataProvidersManager(),
78 DataContext.getDefault().getTimeScales().getUTC());
79 }
80
81 /**
82 * Construct a loader by specifying the source of SINEX auxiliary data files.
83 * @param supportedNames regular expression for supported files names
84 * @param dataProvidersManager provides access to auxiliary data.
85 * @param utc UTC time scale
86 */
87 public SinexLoader(final String supportedNames,
88 final DataProvidersManager dataProvidersManager,
89 final TimeScale utc) {
90 this.utc = utc;
91 stations = new HashMap<>();
92 dataProvidersManager.feed(supportedNames, new Parser());
93 }
94
95 /** Simple constructor. This constructor uses the {@link DataContext#getDefault()
96 * default data context}.
97 * @param source source for the RINEX data
98 * @see #SinexLoader(String, DataProvidersManager, TimeScale)
99 */
100 @DefaultDataContext
101 public SinexLoader(final DataSource source) {
102 this(source, DataContext.getDefault().getTimeScales().getUTC());
103 }
104
105 /**
106 * Loads SINEX from the given input stream using the specified auxiliary data.
107 * @param source source for the RINEX data
108 * @param utc UTC time scale
109 */
110 public SinexLoader(final DataSource source, final TimeScale utc) {
111 try {
112 this.utc = utc;
113 stations = new HashMap<>();
114 try (InputStream is = source.getOpener().openStreamOnce();
115 BufferedInputStream bis = new BufferedInputStream(is)) {
116 new Parser().loadData(bis, source.getName());
117 }
118 } catch (IOException | ParseException ioe) {
119 throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
120 }
121 }
122
123 /**
124 * Get the parsed station data.
125 * @return unmodifiable view of parsed station data
126 */
127 public Map<String, Station> getStations() {
128 return Collections.unmodifiableMap(stations);
129 }
130
131 /**
132 * Get the station corresponding to the given site code.
133 * @param siteCode site code
134 * @return the corresponding station
135 */
136 public Station getStation(final String siteCode) {
137 return stations.get(siteCode);
138 }
139
140 /**
141 * Add a new entry to the map of stations.
142 * @param station station entry to add
143 */
144 private void addStation(final Station station) {
145 // Check if station already exists
146 if (stations.get(station.getSiteCode()) == null) {
147 stations.put(station.getSiteCode(), station);
148 }
149 }
150
151 /** Parser for SINEX files. */
152 private class Parser implements DataLoader {
153
154 /** Start character of a comment line. */
155 private static final String COMMENT = "*";
156
157 /** {@inheritDoc} */
158 @Override
159 public boolean stillAcceptsData() {
160 // We load all SINEX files we can find
161 return true;
162 }
163
164 /** {@inheritDoc} */
165 @Override
166 public void loadData(final InputStream input, final String name)
167 throws IOException, ParseException {
168
169 // Useful parameters
170 int lineNumber = 0;
171 String line = null;
172 boolean inId = false;
173 boolean inEcc = false;
174 boolean inEpoch = false;
175 boolean inEstimate = false;
176 Vector3D position = Vector3D.ZERO;
177 Vector3D velocity = Vector3D.ZERO;
178
179 try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
180
181 // Loop on lines
182 for (line = reader.readLine(); line != null; line = reader.readLine()) {
183 ++lineNumber;
184 // For now, only few keys are supported
185 // They represent the minimum set of parameters that are interesting to consider in a SINEX file
186 // Other keys can be added depending user needs
187 switch(line.trim()) {
188 case "+SITE/ID" :
189 // Start of site id. data
190 inId = true;
191 break;
192 case "-SITE/ID" :
193 // End of site id. data
194 inId = false;
195 break;
196 case "+SITE/ECCENTRICITY" :
197 // Start of antenna eccentricities data
198 inEcc = true;
199 break;
200 case "-SITE/ECCENTRICITY" :
201 // End of antenna eccentricities data
202 inEcc = false;
203 break;
204 case "+SOLUTION/EPOCHS" :
205 // Start of epoch data
206 inEpoch = true;
207 break;
208 case "-SOLUTION/EPOCHS" :
209 // End of epoch data
210 inEpoch = false;
211 break;
212 case "+SOLUTION/ESTIMATE" :
213 // Start of coordinates data
214 inEstimate = true;
215 break;
216 case "-SOLUTION/ESTIMATE" :
217 // Start of coordinates data
218 inEstimate = false;
219 break;
220 default:
221 if (line.startsWith(COMMENT)) {
222 // ignore that line
223 } else {
224 // parsing data
225 if (inId) {
226 // read site id. data
227 final Station station = new Station();
228 station.setSiteCode(parseString(line, 1, 4));
229 station.setDomes(parseString(line, 9, 9));
230 // add the station to the map
231 addStation(station);
232 } else if (inEcc) {
233 // read antenna eccentricities data
234 final Station station = getStation(parseString(line, 1, 4));
235 // check if start and end dates have been set
236 if (station.getValidFrom() == null) {
237 station.setValidFrom(stringEpochToAbsoluteDate(parseString(line, 16, 12)));
238 station.setValidUntil(stringEpochToAbsoluteDate(parseString(line, 29, 12)));
239 }
240 station.setEccRefSystem(ReferenceSystem.getEccRefSystem(parseString(line, 42, 3)));
241 station.setEccentricities(new Vector3D(parseDouble(line, 46, 8),
242 parseDouble(line, 55, 8),
243 parseDouble(line, 64, 8)));
244 } else if (inEpoch) {
245 // read epoch data
246 final Station station = getStation(parseString(line, 1, 4));
247 station.setValidFrom(stringEpochToAbsoluteDate(parseString(line, 16, 12)));
248 station.setValidUntil(stringEpochToAbsoluteDate(parseString(line, 29, 12)));
249 } else if (inEstimate) {
250 final Station station = getStation(parseString(line, 14, 4));
251 // check if this station exists
252 if (station != null) {
253 // switch on coordinates data
254 switch(parseString(line, 7, 6)) {
255 case "STAX":
256 // station X coordinate
257 final double x = parseDouble(line, 47, 22);
258 position = new Vector3D(x, position.getY(), position.getZ());
259 station.setPosition(position);
260 break;
261 case "STAY":
262 // station Y coordinate
263 final double y = parseDouble(line, 47, 22);
264 position = new Vector3D(position.getX(), y, position.getZ());
265 station.setPosition(position);
266 break;
267 case "STAZ":
268 // station Z coordinate
269 final double z = parseDouble(line, 47, 22);
270 position = new Vector3D(position.getX(), position.getY(), z);
271 station.setPosition(position);
272 // set the reference epoch (identical for all coordinates)
273 station.setEpoch(stringEpochToAbsoluteDate(parseString(line, 27, 12)));
274 // reset position vector
275 position = Vector3D.ZERO;
276 break;
277 case "VELX":
278 // station X velocity (value is in m/y)
279 final double vx = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
280 velocity = new Vector3D(vx, velocity.getY(), velocity.getZ());
281 station.setVelocity(velocity);
282 break;
283 case "VELY":
284 // station Y velocity (value is in m/y)
285 final double vy = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
286 velocity = new Vector3D(velocity.getX(), vy, velocity.getZ());
287 station.setVelocity(velocity);
288 break;
289 case "VELZ":
290 // station Z velocity (value is in m/y)
291 final double vz = parseDouble(line, 47, 22) / Constants.JULIAN_YEAR;
292 velocity = new Vector3D(velocity.getX(), velocity.getY(), vz);
293 station.setVelocity(velocity);
294 // reset position vector
295 velocity = Vector3D.ZERO;
296 break;
297 default:
298 // ignore that field
299 break;
300 }
301 }
302
303 } else {
304 // not supported line, ignore it
305 }
306 }
307 break;
308 }
309 }
310
311 } catch (NumberFormatException nfe) {
312 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
313 lineNumber, name, line);
314 }
315
316 }
317
318 /** Extract a string from a line.
319 * @param line to parse
320 * @param start start index of the string
321 * @param length length of the string
322 * @return parsed string
323 */
324 private String parseString(final String line, final int start, final int length) {
325 return line.substring(start, FastMath.min(line.length(), start + length)).trim();
326 }
327
328 /** Extract a double from a line.
329 * @param line to parse
330 * @param start start index of the real
331 * @param length length of the real
332 * @return parsed real
333 */
334 private double parseDouble(final String line, final int start, final int length) {
335 return Double.parseDouble(parseString(line, start, length));
336 }
337
338 }
339
340 /**
341 * Transform a String epoch to an AbsoluteDate.
342 * @param stringDate string epoch
343 * @return the corresponding AbsoluteDate
344 */
345 private AbsoluteDate stringEpochToAbsoluteDate(final String stringDate) {
346 // Date components
347 final String[] fields = SEPARATOR.split(stringDate);
348
349 // Read fields
350 final int twoDigitsYear = Integer.parseInt(fields[0]);
351 final int day = Integer.parseInt(fields[1]);
352 final int secInDay = Integer.parseInt(fields[2]);
353
354 // Data year
355 final int year;
356 if (twoDigitsYear > 50) {
357 year = 1900 + twoDigitsYear;
358 } else {
359 year = 2000 + twoDigitsYear;
360 }
361
362 // Return an absolute date.
363 // Initialize to 1st January of the given year because
364 // sometimes day in equal to 0 in the file.
365 return new AbsoluteDate(new DateComponents(year, 1, 1), utc).
366 shiftedBy(Constants.JULIAN_DAY * (day - 1)).
367 shiftedBy(secInDay);
368 }
369
370 }