Grid.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.weather;

  18. import java.util.List;
  19. import java.util.SortedSet;

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.util.FastMath;
  22. import org.hipparchus.util.FieldSinCos;
  23. import org.hipparchus.util.MathUtils;
  24. import org.hipparchus.util.SinCos;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.utils.Constants;

  28. /** Container for a complete grid.
  29.  * @author Bryan Cazabonne
  30.  * @author Luc Maisonobe
  31.  * @since 12.1
  32.  */
  33. class Grid {

  34.     /** Latitude sample. */
  35.     private final SortedSet<Integer> latitudeSample;

  36.     /** Longitude sample. */
  37.     private final SortedSet<Integer> longitudeSample;

  38.     /** Grid entries. */
  39.     private final GridEntry[][] entries;

  40.     /** Simple constructor.
  41.      * @param latitudeSample latitude sample
  42.      * @param longitudeSample longitude sample
  43.      * @param loadedEntries loaded entries, organized as a simple list
  44.      * @param name file name
  45.      */
  46.     Grid(final SortedSet<Integer> latitudeSample, final SortedSet<Integer> longitudeSample,
  47.          final List<GridEntry> loadedEntries, final String name) {

  48.         final int nA         = latitudeSample.size();
  49.         final int nO         = longitudeSample.size() + 1; // we add one here for wrapping the grid
  50.         this.entries         = new GridEntry[nA][nO];
  51.         this.latitudeSample  = latitudeSample;
  52.         this.longitudeSample = longitudeSample;

  53.         // organize entries in the regular grid
  54.         for (final GridEntry entry : loadedEntries) {
  55.             final int latitudeIndex  = latitudeSample.headSet(entry.getLatKey() + 1).size() - 1;
  56.             final int longitudeIndex = longitudeSample.headSet(entry.getLonKey() + 1).size() - 1;
  57.             entries[latitudeIndex][longitudeIndex] = entry;
  58.         }

  59.         // finalize the grid
  60.         for (final GridEntry[] row : entries) {

  61.             // check for missing entries
  62.             for (int longitudeIndex = 0; longitudeIndex < nO - 1; ++longitudeIndex) {
  63.                 if (row[longitudeIndex] == null) {
  64.                     throw new OrekitException(OrekitMessages.IRREGULAR_OR_INCOMPLETE_GRID, name);
  65.                 }
  66.             }

  67.             // wrap the grid around the Earth in longitude
  68.             row[nO - 1] = row[0].buildWrappedEntry();

  69.         }

  70.     }

  71.     /** Get index of South entries in the grid.
  72.      * @param latitude latitude to locate (radians)
  73.      * @return index of South entries in the grid
  74.      */
  75.     private int getSouthIndex(final double latitude) {

  76.         final int latKey = (int) FastMath.rint(FastMath.toDegrees(latitude) * GridEntry.DEG_TO_MAS);
  77.         final int index  = latitudeSample.headSet(latKey + 1).size() - 1;

  78.         // make sure we have at least one point remaining on North by clipping to size - 2
  79.         return FastMath.min(index, latitudeSample.size() - 2);

  80.     }

  81.     /** Get index of West entries in the grid.
  82.      * @param longitude longitude to locate (radians)
  83.      * @return index of West entries in the grid
  84.      */
  85.     private int getWestIndex(final double longitude) {

  86.         final int lonKey = (int) FastMath.rint(FastMath.toDegrees(longitude) * GridEntry.DEG_TO_MAS);

  87.         // we don't do clipping in longitude because we have added a row to wrap around the Earth
  88.         return longitudeSample.headSet(lonKey + 1).size() - 1;

  89.     }

  90.     /** Get interpolator within a cell.
  91.      * @param latitude latitude of point of interest
  92.      * @param longitude longitude of point of interest
  93.      * @param altitude altitude of point of interest
  94.      * @param deltaRef duration since reference date
  95.      * @return interpolator for the cell
  96.      */
  97.     CellInterpolator getInterpolator(final double latitude, final double longitude,
  98.                                      final double altitude, final double deltaRef) {

  99.         // keep longitude within grid range
  100.         final double normalizedLongitude =
  101.                         MathUtils.normalizeAngle(longitude,
  102.                                                  entries[0][0].getLongitude() + FastMath.PI);

  103.         // find neighboring grid entries
  104.         final int southIndex = getSouthIndex(latitude);
  105.         final int westIndex  = getWestIndex(normalizedLongitude);

  106.         final double coef = (deltaRef / Constants.JULIAN_YEAR) * 2 * FastMath.PI;
  107.         final SinCos sc1  = FastMath.sinCos(coef);
  108.         final SinCos sc2  = FastMath.sinCos(2.0 * coef);

  109.         // build interpolator
  110.         return new CellInterpolator(latitude, normalizedLongitude,
  111.                                     entries[southIndex    ][westIndex    ].evaluate(sc1, sc2, altitude),
  112.                                     entries[southIndex    ][westIndex + 1].evaluate(sc1, sc2, altitude),
  113.                                     entries[southIndex + 1][westIndex    ].evaluate(sc1, sc2, altitude),
  114.                                     entries[southIndex + 1][westIndex + 1].evaluate(sc1, sc2, altitude));

  115.     }

  116.     /** Get interpolator within a cell.
  117.      * @param <T> type of the field elements
  118.      * @param latitude latitude of point of interest
  119.      * @param longitude longitude of point of interest
  120.      * @param altitude altitude of point of interest
  121.      * @param deltaRef duration since reference date
  122.      * @return interpolator for the cell
  123.      */
  124.     <T extends CalculusFieldElement<T>> FieldCellInterpolator<T> getInterpolator(final T latitude, final T longitude,
  125.                                                                                  final T altitude, final T deltaRef) {

  126.         // keep longitude within grid range
  127.         final T normalizedLongitude =
  128.                         MathUtils.normalizeAngle(longitude,
  129.                                                  longitude.newInstance(entries[0][0].getLongitude() + FastMath.PI));

  130.         // find neighboring grid entries
  131.         final int southIndex = getSouthIndex(latitude.getReal());
  132.         final int westIndex  = getWestIndex(normalizedLongitude.getReal());

  133.         final T              coef = deltaRef.multiply(2 * FastMath.PI / Constants.JULIAN_YEAR);
  134.         final FieldSinCos<T> sc1  = FastMath.sinCos(coef);
  135.         final FieldSinCos<T> sc2  = FastMath.sinCos(coef.multiply(2));

  136.          // build interpolator
  137.         return new FieldCellInterpolator<>(latitude, normalizedLongitude,
  138.                                            entries[southIndex    ][westIndex    ].evaluate(sc1, sc2, altitude),
  139.                                            entries[southIndex    ][westIndex + 1].evaluate(sc1, sc2, altitude),
  140.                                            entries[southIndex + 1][westIndex    ].evaluate(sc1, sc2, altitude),
  141.                                            entries[southIndex + 1][westIndex + 1].evaluate(sc1, sc2, altitude));

  142.     }

  143.     /** Check if grid contains all specified models.
  144.      * @param types models types
  145.      * @return true if grid contain the model
  146.      */
  147.     boolean hasModels(final SeasonalModelType... types) {
  148.         boolean hasAll = true;
  149.         for (final SeasonalModelType type : types) {
  150.             hasAll &= entries[0][0].hasModel(type);
  151.         }
  152.         return hasAll;
  153.     }

  154. }