Grid.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.models.earth.weather;
import java.util.List;
import java.util.SortedSet;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;
import org.hipparchus.util.MathUtils;
import org.hipparchus.util.SinCos;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.utils.Constants;
/** Container for a complete grid.
* @author Bryan Cazabonne
* @author Luc Maisonobe
* @since 12.1
*/
class Grid {
/** Latitude sample. */
private final SortedSet<Integer> latitudeSample;
/** Longitude sample. */
private final SortedSet<Integer> longitudeSample;
/** Grid entries. */
private final GridEntry[][] entries;
/** Simple constructor.
* @param latitudeSample latitude sample
* @param longitudeSample longitude sample
* @param loadedEntries loaded entries, organized as a simple list
* @param name file name
*/
Grid(final SortedSet<Integer> latitudeSample, final SortedSet<Integer> longitudeSample,
final List<GridEntry> loadedEntries, final String name) {
final int nA = latitudeSample.size();
final int nO = longitudeSample.size() + 1; // we add one here for wrapping the grid
this.entries = new GridEntry[nA][nO];
this.latitudeSample = latitudeSample;
this.longitudeSample = longitudeSample;
// organize entries in the regular grid
for (final GridEntry entry : loadedEntries) {
final int latitudeIndex = latitudeSample.headSet(entry.getLatKey() + 1).size() - 1;
final int longitudeIndex = longitudeSample.headSet(entry.getLonKey() + 1).size() - 1;
entries[latitudeIndex][longitudeIndex] = entry;
}
// finalize the grid
for (final GridEntry[] row : entries) {
// check for missing entries
for (int longitudeIndex = 0; longitudeIndex < nO - 1; ++longitudeIndex) {
if (row[longitudeIndex] == null) {
throw new OrekitException(OrekitMessages.IRREGULAR_OR_INCOMPLETE_GRID, name);
}
}
// wrap the grid around the Earth in longitude
row[nO - 1] = row[0].buildWrappedEntry();
}
}
/** Get index of South entries in the grid.
* @param latitude latitude to locate (radians)
* @return index of South entries in the grid
*/
private int getSouthIndex(final double latitude) {
final int latKey = (int) FastMath.rint(FastMath.toDegrees(latitude) * GridEntry.DEG_TO_MAS);
final int index = latitudeSample.headSet(latKey + 1).size() - 1;
// make sure we have at least one point remaining on North by clipping to size - 2
return FastMath.min(index, latitudeSample.size() - 2);
}
/** Get index of West entries in the grid.
* @param longitude longitude to locate (radians)
* @return index of West entries in the grid
*/
private int getWestIndex(final double longitude) {
final int lonKey = (int) FastMath.rint(FastMath.toDegrees(longitude) * GridEntry.DEG_TO_MAS);
// we don't do clipping in longitude because we have added a row to wrap around the Earth
return longitudeSample.headSet(lonKey + 1).size() - 1;
}
/** Get interpolator within a cell.
* @param latitude latitude of point of interest
* @param longitude longitude of point of interest
* @param altitude altitude of point of interest
* @param deltaRef duration since reference date
* @return interpolator for the cell
*/
CellInterpolator getInterpolator(final double latitude, final double longitude,
final double altitude, final double deltaRef) {
// keep longitude within grid range
final double normalizedLongitude =
MathUtils.normalizeAngle(longitude,
entries[0][0].getLongitude() + FastMath.PI);
// find neighboring grid entries
final int southIndex = getSouthIndex(latitude);
final int westIndex = getWestIndex(normalizedLongitude);
final double coef = (deltaRef / Constants.JULIAN_YEAR) * 2 * FastMath.PI;
final SinCos sc1 = FastMath.sinCos(coef);
final SinCos sc2 = FastMath.sinCos(2.0 * coef);
// build interpolator
return new CellInterpolator(latitude, normalizedLongitude,
entries[southIndex ][westIndex ].evaluate(sc1, sc2, altitude),
entries[southIndex ][westIndex + 1].evaluate(sc1, sc2, altitude),
entries[southIndex + 1][westIndex ].evaluate(sc1, sc2, altitude),
entries[southIndex + 1][westIndex + 1].evaluate(sc1, sc2, altitude));
}
/** Get interpolator within a cell.
* @param <T> type of the field elements
* @param latitude latitude of point of interest
* @param longitude longitude of point of interest
* @param altitude altitude of point of interest
* @param deltaRef duration since reference date
* @return interpolator for the cell
*/
<T extends CalculusFieldElement<T>> FieldCellInterpolator<T> getInterpolator(final T latitude, final T longitude,
final T altitude, final T deltaRef) {
// keep longitude within grid range
final T normalizedLongitude =
MathUtils.normalizeAngle(longitude,
longitude.newInstance(entries[0][0].getLongitude() + FastMath.PI));
// find neighboring grid entries
final int southIndex = getSouthIndex(latitude.getReal());
final int westIndex = getWestIndex(normalizedLongitude.getReal());
final T coef = deltaRef.multiply(2 * FastMath.PI / Constants.JULIAN_YEAR);
final FieldSinCos<T> sc1 = FastMath.sinCos(coef);
final FieldSinCos<T> sc2 = FastMath.sinCos(coef.multiply(2));
// build interpolator
return new FieldCellInterpolator<>(latitude, normalizedLongitude,
entries[southIndex ][westIndex ].evaluate(sc1, sc2, altitude),
entries[southIndex ][westIndex + 1].evaluate(sc1, sc2, altitude),
entries[southIndex + 1][westIndex ].evaluate(sc1, sc2, altitude),
entries[southIndex + 1][westIndex + 1].evaluate(sc1, sc2, altitude));
}
/** Check if grid contains all specified models.
* @param types models types
* @return true if grid contain the model
*/
boolean hasModels(final SeasonalModelType... types) {
boolean hasAll = true;
for (final SeasonalModelType type : types) {
hasAll &= entries[0][0].hasModel(type);
}
return hasAll;
}
}