CCIRLoader.java

  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. import org.hipparchus.util.FastMath;
  19. import org.orekit.data.DataSource;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.errors.OrekitMessages;
  22. import org.orekit.time.DateComponents;

  23. import java.io.BufferedReader;
  24. import java.io.IOException;
  25. import java.io.Reader;
  26. import java.util.Locale;
  27. import java.util.regex.Pattern;

  28. /**
  29.  * Parser for CCIR files.
  30.  * <p>
  31.  * Numerical grid maps which describe the regular variation of the ionosphere. They are used to derive other variables
  32.  * such as critical frequencies and transmission factors.
  33.  * </p> <p>
  34.  * The coefficients correspond to low and high solar activity conditions.
  35.  * </p> <p>
  36.  * The CCIR file naming convention is ccirXX.asc where each XX means month + 10.
  37.  * </p> <p>
  38.  * Coefficients are store into tow arrays, F2 and Fm3. F2 coefficients are used for the computation of the F2 layer
  39.  * critical frequency. Fm3 for the computation of the F2 layer maximum usable frequency factor. The size of these two
  40.  * arrays is fixed and discussed into the section 2.5.3.2 of the reference document.
  41.  * </p>
  42.  * @author Bryan Cazabonne
  43.  * @since 13.0
  44.  */
  45. class CCIRLoader {

  46.     /** Total number of F2 coefficients contained in the file. */
  47.     private static final int NUMBER_F2_COEFFICIENTS = 1976;

  48.     /** Pattern for delimiting regular expressions. */
  49.     private static final Pattern SEPARATOR = Pattern.compile("\\s+");

  50.     /** Rows number for F2 and Fm3 arrays. */
  51.     private static final int ROWS = 2;

  52.     /** Columns number for F2 array. */
  53.     private static final int TOTAL_COLUMNS_F2 = 76;

  54.     /** Columns number for Fm3 array. */
  55.     private static final int TOTAL_COLUMNS_FM3 = 49;

  56.     /** Depth of F2 array. */
  57.     private static final int DEPTH_F2 = 13;

  58.     /** Depth of Fm3 array. */
  59.     private static final int DEPTH_FM3 = 9;

  60.     /** F2 coefficients used for the computation of the F2 layer critical frequency. */
  61.     private double[][][] parsedF2;

  62.     /** Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor. */
  63.     private double[][][] parsedFm3;

  64.     /**
  65.      * Build a new instance.
  66.      */
  67.     CCIRLoader() {
  68.         this.parsedF2  = new double[ROWS][TOTAL_COLUMNS_F2][DEPTH_F2];
  69.         this.parsedFm3 = new double[ROWS][TOTAL_COLUMNS_FM3][DEPTH_FM3];
  70.     }

  71.     /**
  72.      * Get the F2 coefficients used for the computation of the F2 layer critical frequency.
  73.      *
  74.      * @return the F2 coefficients
  75.      */
  76.     public double[][][] getF2() {
  77.         return parsedF2.clone();
  78.     }

  79.     /**
  80.      * Get the Fm3 coefficients used for the computation of the F2 layer maximum usable frequency factor.
  81.      *
  82.      * @return the F2 coefficients
  83.      */
  84.     public double[][][] getFm3() {
  85.         return parsedFm3.clone();
  86.     }

  87.     /**
  88.      * Load the data for a given month.
  89.      *
  90.      * @param dateComponents month given but its DateComponents
  91.      */
  92.     public void loadCCIRCoefficients(final DateComponents dateComponents) {

  93.         // The files are named ccirXX.asc where XX substitute the month of the year + 10
  94.         final int currentMonth = dateComponents.getMonth();
  95.         final String fileName = String.format(Locale.US, "/assets/org/orekit/nequick/ccir%02d.asc",
  96.                                               currentMonth + 10);
  97.         loadData(new DataSource(fileName, () -> CCIRLoader.class.getResourceAsStream(fileName)));

  98.     }

  99.     /** Load data.
  100.      * @param dataSource data source
  101.      */
  102.     public void loadData(final DataSource dataSource) {

  103.         // Placeholders for parsed data
  104.         int    lineNumber       = 0;
  105.         int    index            = 0;
  106.         int    currentRowF2     = 0;
  107.         int    currentColumnF2  = 0;
  108.         int    currentDepthF2   = 0;
  109.         int    currentRowFm3    = 0;
  110.         int    currentColumnFm3 = 0;
  111.         int    currentDepthFm3  = 0;
  112.         String line             = null;

  113.         try (Reader         r  = dataSource.getOpener().openReaderOnce();
  114.              BufferedReader br = new BufferedReader(r)) {

  115.             for (line = br.readLine(); line != null; line = br.readLine()) {
  116.                 ++lineNumber;
  117.                 line = line.trim();

  118.                 // Read grid data
  119.                 if (!line.isEmpty()) {
  120.                     final String[] ccir_line = SEPARATOR.split(line);
  121.                     for (final String field : ccir_line) {

  122.                         if (index < NUMBER_F2_COEFFICIENTS) {
  123.                             // Parse F2 coefficients
  124.                             if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 < (TOTAL_COLUMNS_F2 - 1)) {
  125.                                 currentDepthF2 = 0;
  126.                                 currentColumnF2++;
  127.                             } else if (currentDepthF2 >= DEPTH_F2 && currentColumnF2 >= (TOTAL_COLUMNS_F2 - 1)) {
  128.                                 currentDepthF2 = 0;
  129.                                 currentColumnF2 = 0;
  130.                                 currentRowF2++;
  131.                             }
  132.                             parsedF2[currentRowF2][currentColumnF2][currentDepthF2++] = Double.parseDouble(field);
  133.                             index++;
  134.                         } else {
  135.                             // Parse Fm3 coefficients
  136.                             if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 < (TOTAL_COLUMNS_FM3 - 1)) {
  137.                                 currentDepthFm3 = 0;
  138.                                 currentColumnFm3++;
  139.                             } else if (currentDepthFm3 >= DEPTH_FM3 && currentColumnFm3 >= (TOTAL_COLUMNS_FM3 - 1)) {
  140.                                 currentDepthFm3 = 0;
  141.                                 currentColumnFm3 = 0;
  142.                                 currentRowFm3++;
  143.                             }
  144.                             parsedFm3[currentRowFm3][currentColumnFm3][currentDepthFm3++] = Double.parseDouble(field);
  145.                             index++;
  146.                         }

  147.                     }
  148.                 }

  149.             }

  150.         } catch (IOException ioe) {
  151.             throw new OrekitException(ioe, OrekitMessages.NEQUICK_F2_FM3_NOT_LOADED, dataSource.getName());
  152.         } catch (NumberFormatException nfe) {
  153.             throw new OrekitException(OrekitMessages.UNABLE_TO_PARSE_LINE_IN_FILE,
  154.                                       lineNumber, dataSource.getName(), line);
  155.         }

  156.         checkDimensions(currentRowF2,  currentColumnF2,  currentDepthF2,  parsedF2,  dataSource.getName());
  157.         checkDimensions(currentRowFm3, currentColumnFm3, currentDepthFm3, parsedFm3, dataSource.getName());

  158.     }

  159.     /** Check dimensions.
  160.      * @param currentRow current row index
  161.      * @param currentColumn current column index
  162.      * @param currentDepth current depth index
  163.      * @param array storage array
  164.      * @param name data source name
  165.      */
  166.     private void checkDimensions(final int currentRow, final int currentColumn, final int currentDepth,
  167.                                  final double[][][] array, final String name) {
  168.         // just three equality tests
  169.         // written in a way test coverage doesn't complain about missing cases…
  170.         if (FastMath.max(FastMath.max(FastMath.abs(currentRow - (array.length - 1)),
  171.                                       FastMath.abs(currentColumn - (array[0].length - 1))),
  172.                          FastMath.abs(currentDepth - array[0][0].length)) != 0) {
  173.             throw new OrekitException(OrekitMessages.NEQUICK_F2_FM3_NOT_LOADED, name);
  174.         }
  175.     }

  176. }