1 /* Copyright 2002-2022 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.propagation.analytical.gnss;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InputStreamReader;
23 import java.nio.charset.StandardCharsets;
24 import java.text.ParseException;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.hipparchus.util.FastMath;
29 import org.hipparchus.util.Pair;
30 import org.junit.Assert;
31 import org.junit.Test;
32 import org.orekit.Utils;
33 import org.orekit.data.DataLoader;
34 import org.orekit.data.DataProvidersManager;
35 import org.orekit.errors.OrekitException;
36 import org.orekit.errors.OrekitMessages;
37 import org.orekit.gnss.SatelliteSystem;
38 import org.orekit.gnss.YUMAParser;
39 import org.orekit.propagation.analytical.gnss.data.QZSSAlmanac;
40 import org.orekit.time.AbsoluteDate;
41 import org.orekit.time.GNSSDate;
42
43 public class QZSSAlmanacTest {
44
45 @Test
46 public void testLoadData() throws IOException, ParseException, OrekitException {
47 Utils.setDataRoot("regular-data");
48 // the parser for reading Yuma files with a pattern
49 QZSSYUMAParser reader = new QZSSYUMAParser(".*\\.yum$");
50 // the YUMA file to read
51 final String fileName = "/gnss/q2019034.alm";
52 final InputStream in = getClass().getResourceAsStream(fileName);
53 reader.loadData(in, fileName);
54
55 Assert.assertEquals(".*\\.yum$", reader.getSupportedNames());
56
57 // Checks the whole file read
58 Assert.assertEquals(4, reader.getAlmanacs().size());
59 Assert.assertEquals(4, reader.getPRNNumbers().size());
60
61 // Checks the last almanac read
62 final QZSSAlmanac alm = reader.getAlmanacs().get(reader.getAlmanacs().size() - 1);
63 Assert.assertEquals(199, alm.getPRN());
64 Assert.assertEquals(1015, alm.getWeek());
65 Assert.assertEquals(262144.0, alm.getTime(), 0.);
66 Assert.assertEquals(6493.484863, FastMath.sqrt(alm.getSma()), FastMath.ulp(5.E+03));
67 Assert.assertEquals(1.387596130E-04, alm.getE(), FastMath.ulp(8E-05));
68 Assert.assertEquals(0.0007490141, alm.getI0(), 0.);
69 Assert.assertEquals(0., alm.getIDot(), 0.);
70 Assert.assertEquals(9.194173760E-01, alm.getOmega0(), 0.);
71 Assert.assertEquals(9.714690370E-10, alm.getOmegaDot(), FastMath.ulp(-8E-09));
72 Assert.assertEquals(2.722442515, alm.getPa(), 0.);
73 Assert.assertEquals(-1.158294811, alm.getM0(), 0.);
74 Assert.assertEquals(6.351470947E-04, alm.getAf0(), 0.);
75 Assert.assertEquals(0.0, alm.getAf1(), 0.);
76 Assert.assertEquals(0, alm.getHealth());
77 Assert.assertEquals("YUMA", alm.getSource());
78 Assert.assertTrue(alm.getDate().durationFrom(new GNSSDate(1015, 262144 * 1000., SatelliteSystem.QZSS).getDate()) == 0);
79 Assert.assertEquals(0., alm.getCic(), 0.);
80 Assert.assertEquals(0., alm.getCis(), 0.);
81 Assert.assertEquals(0., alm.getCrc(), 0.);
82 Assert.assertEquals(0., alm.getCrs(), 0.);
83 Assert.assertEquals(0., alm.getCuc(), 0.);
84 Assert.assertEquals(0., alm.getCus(), 0.);
85 }
86
87 /**
88 * This class reads Yuma almanac files and provides {@link QZSSAlmanac QZSS almanacs}.
89 *
90 * <p>This class is a rewrite of {@link YUMAParser} adapted to QZSS yuma files</p>
91 *
92 * @author Pascal Parraud
93 *
94 */
95 private class QZSSYUMAParser implements DataLoader {
96
97 // Constants
98 /** The source of the almanacs. */
99 private static final String SOURCE = "YUMA";
100
101 /** the useful keys in the YUMA file. */
102 private final String[] KEY = {
103 "id", // ID
104 "health", // Health
105 "eccentricity", // Eccentricity
106 "time", // Time of Applicability(s)
107 "orbital", // Orbital Inclination(rad)
108 "rate", // Rate of Right Ascen(r/s)
109 "sqrt", // SQRT(A) (m 1/2)
110 "right", // Right Ascen at Week(rad)
111 "argument", // Argument of Perigee(rad)
112 "mean", // Mean Anom(rad)
113 "af0", // Af0(s)
114 "af1", // Af1(s/s)
115 "week" // week
116 };
117
118 /** Default supported files name pattern. */
119 private static final String DEFAULT_SUPPORTED_NAMES = ".*\\.alm$";
120
121 // Fields
122 /** Regular expression for supported files names. */
123 private final String supportedNames;
124
125 /** the list of all the almanacs read from the file. */
126 private final List<QZSSAlmanac> almanacs;
127
128 /** the list of all the PRN numbers of all the almanacs read from the file. */
129 private final List<Integer> prnList;
130
131 /** Simple constructor.
132 *
133 * <p>This constructor does not load any data by itself. Data must be loaded
134 * later on by calling one of the {@link #loadData() loadData()} method or
135 * the {@link #loadData(InputStream, String) loadData(inputStream, fileName)}
136 * method.</p>
137 *
138 * <p>The supported files names are used when getting data from the
139 * {@link #loadData() loadData()} method that relies on the
140 * {@link DataProvidersManager data providers manager}. They are useless when
141 * getting data from the {@link #loadData(InputStream, String) loadData(input, name)}
142 * method.</p>
143 *
144 * @param supportedNames regular expression for supported files names
145 * (if null, a default pattern matching files with a ".alm" extension will be used)
146 * @see #loadData()
147 */
148 public QZSSYUMAParser(final String supportedNames) {
149 this.supportedNames = (supportedNames == null) ? DEFAULT_SUPPORTED_NAMES : supportedNames;
150 this.almanacs = new ArrayList<QZSSAlmanac>();
151 this.prnList = new ArrayList<Integer>();
152 }
153
154 @Override
155 public void loadData(final InputStream input, final String name)
156 throws IOException, ParseException, OrekitException {
157
158 // Clears the lists
159 almanacs.clear();
160 prnList.clear();
161
162 // Creates the reader
163 final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
164
165 try {
166 // Gathers data to create one QZSSAlmanac from 13 consecutive lines
167 final List<Pair<String, String>> entries =
168 new ArrayList<Pair<String, String>>(KEY.length);
169
170 // Reads the data one line at a time
171 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
172 // Try to split the line into 2 tokens as key:value
173 final String[] token = line.trim().split(":");
174 // If the line is made of 2 tokens
175 if (token.length == 2) {
176 // Adds these tokens as an entry to the entries
177 entries.add(new Pair<String, String>(token[0].trim(), token[1].trim()));
178 }
179 // If the number of entries equals the expected number
180 if (entries.size() == KEY.length) {
181 // Gets a QZSSAlmanac from the entries
182 final QZSSAlmanac almanac = getAlmanac(entries, name);
183 // Adds the QZSSAlmanac to the list
184 almanacs.add(almanac);
185 // Adds the PRN number of the QZSSAlmanac to the list
186 prnList.add(almanac.getPRN());
187 // Clears the entries
188 entries.clear();
189 }
190 }
191 } catch (IOException ioe) {
192 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
193 name);
194 }
195 }
196
197 @Override
198 public boolean stillAcceptsData() {
199 return almanacs.isEmpty();
200 }
201
202 /** Get the supported names for data files.
203 * @return regular expression for the supported names for data files
204 */
205 public String getSupportedNames() {
206 return supportedNames;
207 }
208
209 /**
210 * Gets all the {@link QZSSAlmanac QZSS Almanacs} read from the file.
211 *
212 * @return the list of {@link QZSSAlmanac} from the file
213 */
214 public List<QZSSAlmanac> getAlmanacs() {
215 return almanacs;
216 }
217
218 /**
219 * Gets the PRN numbers of all the {@link QZSSAlmanac QZSS Almanacs} read from the file.
220 *
221 * @return the PRN numbers of all the {@link QZSSAlmanac QZSS Almanacs} read from the file
222 */
223 public List<Integer> getPRNNumbers() {
224 return prnList;
225 }
226
227 /**
228 * Builds a {@link QZSSAlmanac QZSS Almanac} from data read in the file.
229 *
230 * @param entries the data read from the file
231 * @param name name of the file
232 * @return a {@link QZSSAlmanac QZSS Almanac}
233 */
234 private QZSSAlmanac getAlmanac(final List<Pair<String, String>> entries, final String name) {
235 try {
236 // Initializes almanac
237 final QZSSAlmanac almanac = new QZSSAlmanac();
238 almanac.setSource(SOURCE);
239
240 // Initializes checks
241 final boolean[] checks = new boolean[KEY.length];
242 // Loop over entries
243 for (Pair<String, String> entry: entries) {
244 if (entry.getKey().toLowerCase().startsWith(KEY[0])) {
245 // Gets the PRN of the SVN
246 almanac.setPRN(Integer.parseInt(entry.getValue()));
247 checks[0] = true;
248 } else if (entry.getKey().toLowerCase().startsWith(KEY[1])) {
249 // Gets the Health status
250 almanac.setHealth(Integer.parseInt(entry.getValue()));
251 checks[1] = true;
252 } else if (entry.getKey().toLowerCase().startsWith(KEY[2])) {
253 // Gets the eccentricity
254 almanac.setE(Double.parseDouble(entry.getValue()));
255 checks[2] = true;
256 } else if (entry.getKey().toLowerCase().startsWith(KEY[3])) {
257 // Gets the Time of Applicability
258 almanac.setTime(Double.parseDouble(entry.getValue()));
259 checks[3] = true;
260 } else if (entry.getKey().toLowerCase().startsWith(KEY[4])) {
261 // Gets the Inclination
262 almanac.setI0(Double.parseDouble(entry.getValue()));
263 checks[4] = true;
264 } else if (entry.getKey().toLowerCase().startsWith(KEY[5])) {
265 // Gets the Rate of Right Ascension
266 almanac.setOmegaDot(Double.parseDouble(entry.getValue()));
267 checks[5] = true;
268 } else if (entry.getKey().toLowerCase().startsWith(KEY[6])) {
269 // Gets the square root of the semi-major axis
270 almanac.setSqrtA(Double.parseDouble(entry.getValue()));
271 checks[6] = true;
272 } else if (entry.getKey().toLowerCase().startsWith(KEY[7])) {
273 // Gets the Right Ascension of Ascending Node
274 almanac.setOmega0(Double.parseDouble(entry.getValue()));
275 checks[7] = true;
276 } else if (entry.getKey().toLowerCase().startsWith(KEY[8])) {
277 // Gets the Argument of Perigee
278 almanac.setPa(Double.parseDouble(entry.getValue()));
279 checks[8] = true;
280 } else if (entry.getKey().toLowerCase().startsWith(KEY[9])) {
281 // Gets the Mean Anomalie
282 almanac.setM0(Double.parseDouble(entry.getValue()));
283 checks[9] = true;
284 } else if (entry.getKey().toLowerCase().startsWith(KEY[10])) {
285 // Gets the SV clock bias
286 almanac.setAf0(Double.parseDouble(entry.getValue()));
287 checks[10] = true;
288 } else if (entry.getKey().toLowerCase().startsWith(KEY[11])) {
289 // Gets the SV clock Drift
290 almanac.setAf1(Double.parseDouble(entry.getValue()));
291 checks[11] = true;
292 } else if (entry.getKey().toLowerCase().startsWith(KEY[12])) {
293 // Gets the week number
294 almanac.setWeek(Integer.parseInt(entry.getValue()));
295 checks[12] = true;
296 } else {
297 // Unknown entry: the file is not a YUMA file
298 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
299 name);
300 }
301 }
302
303 // If all expected fields have been read
304 if (readOK(checks)) {
305 // Returns a QZSSAlmanac built from the entries
306 final AbsoluteDate date =
307 new GNSSDate(almanac.getWeek(), almanac.getTime() * 1000, SatelliteSystem.QZSS)
308 .getDate();
309 almanac.setDate(date);
310 return almanac;
311 } else {
312 // The file is not a YUMA file
313 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
314 name);
315 }
316 } catch (NumberFormatException nfe) {
317 throw new OrekitException(OrekitMessages.NOT_A_SUPPORTED_YUMA_ALMANAC_FILE,
318 name);
319 }
320 }
321
322 /** Checks if all expected fields have been read.
323 * @param checks flags for read fields
324 * @return true if all expected fields have been read, false if not
325 */
326 private boolean readOK(final boolean[] checks) {
327 for (boolean check: checks) {
328 if (!check) {
329 return false;
330 }
331 }
332 return true;
333 }
334 }
335
336 }