NeQuickItu.java
- /* Copyright 2022-2025 Thales Alenia Space
- * Licensed to CS Communication & Systèmes (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.ionosphere.nequick;
- import org.hipparchus.CalculusFieldElement;
- import org.hipparchus.util.FastMath;
- import org.hipparchus.util.MathArrays;
- import org.orekit.bodies.FieldGeodeticPoint;
- import org.orekit.bodies.GeodeticPoint;
- import org.orekit.data.DataSource;
- import org.orekit.time.DateTimeComponents;
- import org.orekit.time.TimeScale;
- import org.orekit.utils.units.Unit;
- /** Original model from Aeronomy and Radiopropagation Laboratory
- * of the Abdus Salam International Centre for Theoretical Physics Trieste, Italy.
- * <p>
- * None of the code from Abdus Salam International Centre for Theoretical Physics Trieste
- * has been used, the models have been reimplemented from scratch by the Orekit team.
- * </p>
- * @since 13.0
- * @author Luc Maisonobe
- */
- public class NeQuickItu extends NeQuickModel {
- /** One thousand kilometer height. */
- private static final double H_1000 = 1000000.0;
- /** Two thousands kilometer height. */
- private static final double H_2000 = 2000000.0;
- /** H0 (km). */
- private static final double H0 = 90.0;
- /** HD (km). */
- private static final double HD = 5.0;
- /** Starting number of points for integration. */
- private static final int N_START = 8;
- /** Max number of points for integration. */
- private static final int N_STOP = 1024;
- /** Small convergence criterion. */
- private static final double EPS_SMALL = 1.0e-3;
- /** Medium convergence criterion. */
- private static final double EPS_MEDIUM = 1.0e-2;
- /** Solar flux. */
- private final double f107;
- /** Build a new instance.
- * @param f107 solar flux
- * @param utc UTC time scale
- */
- public NeQuickItu(final double f107, final TimeScale utc) {
- super(utc);
- this.f107 = f107;
- }
- /** Get solar flux.
- * @return solar flux
- */
- public double getF107() {
- return f107;
- }
- /** {@inheritDoc} */
- @Override
- double stec(final DateTimeComponents dateTime, final Ray ray) {
- if (ray.getSatH() <= H_2000) {
- if (ray.getRecH() >= H_1000) {
- // only one integration interval
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
- } else {
- // two integration intervals, below and above 1000km
- final double h1000 = NeQuickModel.RE + H_1000;
- final double s1000 = FastMath.sqrt(h1000 * h1000 - ray.getRadius() * ray.getRadius());
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000) +
- stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, ray.getS2());
- }
- } else {
- if (ray.getRecH() >= H_2000) {
- // only one integration interval
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
- } else {
- final double h2000 = NeQuickModel.RE + H_2000;
- final double s2000 = FastMath.sqrt(h2000 * h2000 - ray.getRadius() * ray.getRadius());
- if (ray.getRecH() >= H_1000) {
- // two integration intervals, below and above 2000km
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s2000) +
- stecIntegration(dateTime, EPS_SMALL, ray, s2000, ray.getS2());
- } else {
- // three integration intervals, below 1000km, between 1000km and 2000km, and above 2000km
- final double h1000 = NeQuickModel.RE + H_1000;
- final double s1000 = FastMath.sqrt(h1000 * h1000 - ray.getRadius() * ray.getRadius());
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000) +
- stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, s2000) +
- stecIntegration(dateTime, EPS_MEDIUM, ray, s2000, ray.getS2());
- }
- }
- }
- }
- /** {@inheritDoc} */
- @Override
- <T extends CalculusFieldElement<T>> T stec(final DateTimeComponents dateTime, final FieldRay<T> ray) {
- if (ray.getSatH().getReal() <= H_2000) {
- if (ray.getRecH().getReal() >= H_1000) {
- // only one integration interval
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
- } else {
- // two integration intervals, below and above 1000km
- final double h1000 = NeQuickModel.RE + H_1000;
- final T s1000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h1000 * h1000));
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000).
- add(stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, ray.getS2()));
- }
- } else {
- if (ray.getRecH().getReal() >= H_2000) {
- // only one integration interval
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
- } else {
- final double h2000 = NeQuickModel.RE + H_2000;
- final T s2000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h2000 * h2000));
- if (ray.getRecH().getReal() >= H_1000) {
- // two integration intervals, below and above 2000km
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s2000).
- add(stecIntegration(dateTime, EPS_SMALL, ray, s2000, ray.getS2()));
- } else {
- // three integration intervals, below 1000km, between 1000km and 2000km, and above 2000km
- final double h1000 = NeQuickModel.RE + H_1000;
- final T s1000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h1000 * h1000));
- return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000).
- add(stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, s2000)).
- add(stecIntegration(dateTime, EPS_MEDIUM, ray, s2000, ray.getS2()));
- }
- }
- }
- }
- /**
- * This method performs the STEC integration.
- *
- * @param dateTime current date and time components
- * @param eps convergence criterion
- * @param ray ray-perigee parameters
- * @param s1 lower boundary of integration
- * @param s2 upper boundary for integration
- * @return result of the integration
- */
- private double stecIntegration(final DateTimeComponents dateTime, final double eps, final Ray ray, final double s1,
- final double s2) {
- double gInt1 = Double.NaN;
- double gInt2 = Double.NaN;
- for (int n = N_START; n <= N_STOP; n = 2 * n) {
- // integrate with n intervals (2n points)
- final Segment segment = new Segment(n, ray, s1, s2);
- double sum = 0;
- for (int i = 0; i < segment.getNbPoints(); ++i) {
- final GeodeticPoint gp = segment.getPoint(i);
- final double ed = electronDensity(dateTime, f107,
- gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
- sum += ed;
- }
- gInt1 = gInt2;
- gInt2 = sum * 0.5 * segment.getInterval();
- if (FastMath.abs(gInt1 - gInt2) <= FastMath.abs(gInt1 * eps)) {
- // convergence reached
- break;
- }
- }
- return Unit.TOTAL_ELECTRON_CONTENT_UNIT.fromSI(gInt2 + (gInt2 - gInt1) / 15.0);
- }
- /**
- * This method performs the STEC integration.
- *
- * @param <T> type of the field elements
- * @param dateTime current date and time components
- * @param eps convergence criterion
- * @param ray ray-perigee parameters
- * @param s1 lower boundary of integration
- * @param s2 upper boundary for integration
- * @return result of the integration
- */
- private <T extends CalculusFieldElement<T>> T stecIntegration(final DateTimeComponents dateTime,
- final double eps,
- final FieldRay<T> ray, final T s1, final T s2) {
- T gInt1 = s1.newInstance(Double.NaN);
- T gInt2 = s1.newInstance(Double.NaN);
- final T f107T = s1.newInstance(f107);
- for (int n = N_START; n <= N_STOP; n = 2 * n) {
- // integrate with n intervals (2n points)
- final FieldSegment<T> segment = new FieldSegment<>(n, ray, s1, s2);
- T sum = s1.getField().getZero();
- for (int i = 0; i < segment.getNbPoints(); ++i) {
- final FieldGeodeticPoint<T> gp = segment.getPoint(i);
- final T ed = electronDensity(dateTime, f107T,
- gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
- sum = sum.add(ed);
- }
- gInt1 = gInt2;
- gInt2 = sum.multiply(0.5).multiply(segment.getInterval());
- if (FastMath.abs(gInt1.subtract(gInt2).getReal()) <= FastMath.abs(gInt1.getReal() * eps)) {
- // convergence reached
- break;
- }
- }
- return Unit.TOTAL_ELECTRON_CONTENT_UNIT.fromSI(gInt2.add(gInt2.subtract(gInt1).divide(15.0)));
- }
- /** {@inheritDoc} */
- @Override
- protected double computeMODIP(final double latitude, final double longitude) {
- return ItuHolder.INSTANCE.computeMODIP(latitude, longitude);
- }
- /** {@inheritDoc} */
- @Override
- protected <T extends CalculusFieldElement<T>> T computeMODIP(final T latitude, final T longitude) {
- return ItuHolder.INSTANCE.computeMODIP(latitude, longitude);
- }
- /** {@inheritDoc} */
- @Override
- boolean applyChapmanParameters(final double hInKm) {
- return false;
- }
- /** {@inheritDoc} */
- @Override
- double[] computeExponentialArguments(final double h, final NeQuickParameters parameters) {
- final double exp = clipExp(10.0 / (1.0 + FastMath.abs(h - parameters.getHmF2())));
- final double[] arguments = new double[3];
- arguments[0] = fixLowHArg( (h - parameters.getHmF2()) / parameters.getB2Bot(), h);
- arguments[1] = fixLowHArg(((h - parameters.getHmF1()) / parameters.getBF1(h)) * exp, h);
- arguments[2] = fixLowHArg(((h - parameters.getHmE()) / parameters.getBE(h)) * exp, h);
- return arguments;
- }
- /** {@inheritDoc} */
- @Override
- <T extends CalculusFieldElement<T>> T[] computeExponentialArguments(final T h,
- final FieldNeQuickParameters<T> parameters) {
- final T exp = clipExp(FastMath.abs(h.subtract(parameters.getHmF2())).add(1.0).reciprocal().multiply(10.0));
- final T[] arguments = MathArrays.buildArray(h.getField(), 3);
- arguments[0] = fixLowHArg(h.subtract(parameters.getHmF2()).divide(parameters.getB2Bot()), h);
- arguments[1] = fixLowHArg(h.subtract(parameters.getHmF1()).divide(parameters.getBF1(h)).multiply(exp), h);
- arguments[2] = fixLowHArg(h.subtract(parameters.getHmE()).divide(parameters.getBE(h)).multiply(exp), h);
- return arguments;
- }
- /**
- * Fix arguments for low altitudes.
- * @param arg argument of the exponential
- * @param h height in km
- * @return fixed argument
- * @since 13.0
- */
- private double fixLowHArg(final double arg, final double h) {
- return h < H0 ? arg * (HD + H0 - h) / HD : arg;
- }
- /**
- * Fix arguments for low altitudes.
- * @param <T> type of the field elements
- * @param arg argument of the exponential
- * @param h height in km
- * @return fixed argument
- * @since 13.0
- */
- private <T extends CalculusFieldElement<T>> T fixLowHArg(final T arg, final T h) {
- return h.getReal() < H0 ? arg.multiply(h.negate().add(HD + H0)).divide(HD) : arg;
- }
- /** {@inheritDoc} */
- @Override
- double computeH0(final NeQuickParameters parameters) {
- final double b2k = -0.0538 * parameters.getFoF2() -
- 0.00664 * parameters.getHmF2() +
- 0.113 * parameters.getHmF2() / parameters.getB2Bot() +
- 0.00257 * parameters.getAzr() +
- 3.22;
- return parameters.getB2Bot() * parameters.join(b2k, 1.0, 2.0, b2k - 1.0);
- }
- /** {@inheritDoc} */
- @Override
- <T extends CalculusFieldElement<T>> T computeH0(final FieldNeQuickParameters<T> parameters) {
- final T b2k = parameters.getFoF2().multiply(-0.0538).
- subtract(parameters.getHmF2().multiply(0.00664)).
- add(parameters.getHmF2().multiply(0.113).divide(parameters.getB2Bot())).
- add(parameters.getAzr().multiply(0.00257)).
- add (3.22);
- return parameters.getB2Bot().
- multiply(parameters.join(b2k, b2k.newInstance(1.0), b2k.newInstance(2.0), b2k.subtract(1.0)));
- }
- /** Holder for the ITU modip singleton.
- * <p>
- * We use the initialization on demand holder idiom to store the singleton,
- * as it is both thread-safe, efficient (no synchronization) and works with
- * all versions of java.
- * </p>
- */
- private static class ItuHolder {
- /** Resource for modip grid. */
- private static final String MODIP_GRID = "/assets/org/orekit/nequick/modip.asc";
- /** Unique instance. */
- private static final ModipGrid INSTANCE =
- new ModipGrid(180, 180,
- new DataSource(MODIP_GRID, () -> ItuHolder.class.getResourceAsStream(MODIP_GRID)),
- false);
- /** Private constructor.
- * <p>This class is a utility class, it should neither have a public
- * nor a default constructor. This private constructor prevents
- * the compiler from generating one automatically.</p>
- */
- private ItuHolder() {
- }
- }
- }