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.models.earth.ionosphere.nequick;
18
19 import org.hipparchus.util.FastMath;
20 import org.orekit.data.DataSource;
21 import org.orekit.errors.OrekitException;
22 import org.orekit.errors.OrekitMessages;
23 import org.orekit.time.DateComponents;
24
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.io.Reader;
28 import java.util.Locale;
29 import java.util.regex.Pattern;
30
31 /**
32 * Parser for CCIR files.
33 * <p>
34 * Numerical grid maps which describe the regular variation of the ionosphere. They are used to derive other variables
35 * such as critical frequencies and transmission factors.
36 * </p> <p>
37 * The coefficients correspond to low and high solar activity conditions.
38 * </p> <p>
39 * The CCIR file naming convention is ccirXX.asc where each XX means month + 10.
40 * </p> <p>
41 * Coefficients are store into tow arrays, F2 and Fm3. F2 coefficients are used for the computation of the F2 layer
42 * critical frequency. Fm3 for the computation of the F2 layer maximum usable frequency factor. The size of these two
43 * arrays is fixed and discussed into the section 2.5.3.2 of the reference document.
44 * </p>
45 * @author Bryan Cazabonne
46 * @since 13.0
47 */
48 class CCIRLoader {
49
50 /** Total number of F2 coefficients contained in the file. */
51 private static final int NUMBER_F2_COEFFICIENTS = 1976;
52
53 /** Pattern for delimiting regular expressions. */
54 private static final Pattern SEPARATOR = Pattern.compile("\\s+");
55
56 /** Rows number for F2 and Fm3 arrays. */
57 private static final int ROWS = 2;
58
59 /** Columns number for F2 array. */
60 private static final int TOTAL_COLUMNS_F2 = 76;
61
62 /** Columns number for Fm3 array. */
63 private static final int TOTAL_COLUMNS_FM3 = 49;
64
65 /** Depth of F2 array. */
66 private static final int DEPTH_F2 = 13;
67
68 /** Depth of Fm3 array. */
69 private static final int DEPTH_FM3 = 9;
70
71 /** F2 coefficients used for the computation of the F2 layer critical frequency. */
72 private double[][][] parsedF2;
73
74 /** Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor. */
75 private double[][][] parsedFm3;
76
77 /**
78 * Build a new instance.
79 */
80 CCIRLoader() {
81 this.parsedF2 = new double[ROWS][TOTAL_COLUMNS_F2][DEPTH_F2];
82 this.parsedFm3 = new double[ROWS][TOTAL_COLUMNS_FM3][DEPTH_FM3];
83 }
84
85 /**
86 * Get the F2 coefficients used for the computation of the F2 layer critical frequency.
87 *
88 * @return the F2 coefficients
89 */
90 public double[][][] getF2() {
91 return parsedF2.clone();
92 }
93
94 /**
95 * Get the Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor.
96 *
97 * @return the F2 coefficients
98 */
99 public double[][][] getFm3() {
100 return parsedFm3.clone();
101 }
102
103 /**
104 * Load the data for a given month.
105 *
106 * @param dateComponents month given but its DateComponents
107 */
108 public void loadCCIRCoefficients(final DateComponents dateComponents) {
109
110 // The files are named ccirXX.asc where XX substitute the month of the year + 10
111 final int currentMonth = dateComponents.getMonth();
112 final String fileName = String.format(Locale.US, "/assets/org/orekit/nequick/ccir%02d.asc",
113 currentMonth + 10);
114 loadData(new DataSource(fileName, () -> CCIRLoader.class.getResourceAsStream(fileName)));
115
116 }
117
118 /** Load data.
119 * @param dataSource data source
120 */
121 public void loadData(final DataSource dataSource) {
122
123 // Placeholders for parsed data
124 int lineNumber = 0;
125 int index = 0;
126 int currentRowF2 = 0;
127 int currentColumnF2 = 0;
128 int currentDepthF2 = 0;
129 int currentRowFm3 = 0;
130 int currentColumnFm3 = 0;
131 int currentDepthFm3 = 0;
132 String line = null;
133
134 try (Reader r = dataSource.getOpener().openReaderOnce();
135 BufferedReader br = new BufferedReader(r)) {
136
137 for (line = br.readLine(); line != null; line = br.readLine()) {
138 ++lineNumber;
139 line = line.trim();
140
141 // Read grid data
142 if (!line.isEmpty()) {
143 final String[] ccir_line = SEPARATOR.split(line);
144 for (final String field : ccir_line) {
145
146 if (index < NUMBER_F2_COEFFICIENTS) {
147 // Parse F2 coefficients
148 if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 < (TOTAL_COLUMNS_F2 - 1)) {
149 currentDepthF2 = 0;
150 currentColumnF2++;
151 } else if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 >= (TOTAL_COLUMNS_F2 - 1)) {
152 currentDepthF2 = 0;
153 currentColumnF2 = 0;
154 currentRowF2++;
155 }
156 parsedF2[currentRowF2][currentColumnF2][currentDepthF2++] = Double.parseDouble(field);
157 index++;
158 } else {
159 // Parse Fm3 coefficients
160 if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 < (TOTAL_COLUMNS_FM3 - 1)) {
161 currentDepthFm3 = 0;
162 currentColumnFm3++;
163 } else if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 >= (TOTAL_COLUMNS_FM3 - 1)) {
164 currentDepthFm3 = 0;
165 currentColumnFm3 = 0;
166 currentRowFm3++;
167 }
168 parsedFm3[currentRowFm3][currentColumnFm3][currentDepthFm3++] = Double.parseDouble(field);
169 index++;
170 }
171
172 }
173 }
174
175 }
176
177 } catch (IOException ioe) {
178 throw new OrekitException(ioe, OrekitMessages.NEQUICK_F2_FM3_NOT_LOADED, dataSource.getName());
179 } catch (NumberFormatException nfe) {
180 throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
181 lineNumber, dataSource.getName(), line);
182 }
183
184 checkDimensions(currentRowF2, currentColumnF2, currentDepthF2, parsedF2, dataSource.getName());
185 checkDimensions(currentRowFm3, currentColumnFm3, currentDepthFm3, parsedFm3, dataSource.getName());
186
187 }
188
189 /** Check dimensions.
190 * @param currentRow current row index
191 * @param currentColumn current column index
192 * @param currentDepth current depth index
193 * @param array storage array
194 * @param name data source name
195 */
196 private void checkDimensions(final int currentRow, final int currentColumn, final int currentDepth,
197 final double[][][] array, final String name) {
198 // just three equality tests
199 // written in a way test coverage doesn't complain about missing cases…
200 if (FastMath.max(FastMath.max(FastMath.abs(currentRow - (array.length - 1)),
201 FastMath.abs(currentColumn - (array[0].length - 1))),
202 FastMath.abs(currentDepth - array[0][0].length)) != 0) {
203 throw new OrekitException(OrekitMessages.NEQUICK_F2_FM3_NOT_LOADED, name);
204 }
205 }
206
207 }