MultiLayerModel.java

  1. /* Copyright 2013-2017 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.rugged.refraction;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.hipparchus.util.FastMath;
  23. import org.orekit.bodies.GeodeticPoint;
  24. import org.orekit.errors.OrekitException;
  25. import org.orekit.rugged.errors.RuggedException;
  26. import org.orekit.rugged.errors.RuggedMessages;
  27. import org.orekit.rugged.intersection.IntersectionAlgorithm;
  28. import org.orekit.rugged.utils.ExtendedEllipsoid;
  29. import org.orekit.rugged.utils.NormalizedGeodeticPoint;

  30. /**
  31.  * Atmospheric refraction model based on multiple layers with associated refractive index.
  32.  * @author Sergio Esteves, Luc Maisonobe
  33.  * @since 2.0
  34.  */
  35. public class MultiLayerModel implements AtmosphericRefraction {

  36.     /** Observed body ellipsoid. */
  37.     private final ExtendedEllipsoid ellipsoid;

  38.     /** Constant refraction layers. */
  39.     private final List<ConstantRefractionLayer> refractionLayers;

  40.     /** Atmosphere lowest altitude. */
  41.     private final double atmosphereLowestAltitude;

  42.     /** Simple constructor.
  43.      * <p>
  44.      * This model uses a built-in set of layers.
  45.      * </p>
  46.      * @param ellipsoid the ellipsoid to be used.
  47.      */
  48.     public MultiLayerModel(final ExtendedEllipsoid ellipsoid) {
  49.         this.ellipsoid = ellipsoid;

  50.         refractionLayers = new ArrayList<ConstantRefractionLayer>(15);
  51.         refractionLayers.add(new ConstantRefractionLayer(100000.00, 1.000000));
  52.         refractionLayers.add(new ConstantRefractionLayer( 50000.00, 1.000000));
  53.         refractionLayers.add(new ConstantRefractionLayer( 40000.00, 1.000001));
  54.         refractionLayers.add(new ConstantRefractionLayer( 30000.00, 1.000004));
  55.         refractionLayers.add(new ConstantRefractionLayer( 23000.00, 1.000012));
  56.         refractionLayers.add(new ConstantRefractionLayer( 18000.00, 1.000028));
  57.         refractionLayers.add(new ConstantRefractionLayer( 14000.00, 1.000052));
  58.         refractionLayers.add(new ConstantRefractionLayer( 11000.00, 1.000083));
  59.         refractionLayers.add(new ConstantRefractionLayer(  9000.00, 1.000106));
  60.         refractionLayers.add(new ConstantRefractionLayer(  7000.00, 1.000134));
  61.         refractionLayers.add(new ConstantRefractionLayer(  5000.00, 1.000167));
  62.         refractionLayers.add(new ConstantRefractionLayer(  3000.00, 1.000206));
  63.         refractionLayers.add(new ConstantRefractionLayer(  1000.00, 1.000252));
  64.         refractionLayers.add(new ConstantRefractionLayer(     0.00, 1.000278));
  65.         refractionLayers.add(new ConstantRefractionLayer( -1000.00, 1.000306));

  66.         atmosphereLowestAltitude = refractionLayers.get(refractionLayers.size() - 1).getLowestAltitude();
  67.     }

  68.     /** Simple constructor.
  69.      * @param ellipsoid the ellipsoid to be used.
  70.      * @param refractionLayers the refraction layers to be used with this model (layers can be in any order).
  71.      */
  72.     public MultiLayerModel(final ExtendedEllipsoid ellipsoid, final List<ConstantRefractionLayer> refractionLayers) {
  73.         this.ellipsoid = ellipsoid;
  74.         this.refractionLayers = new ArrayList<>(refractionLayers);
  75.         Collections.sort(this.refractionLayers,
  76.             (l1, l2) -> Double.compare(l2.getLowestAltitude(), l1.getLowestAltitude()));
  77.         atmosphereLowestAltitude = this.refractionLayers.get(this.refractionLayers.size() - 1).getLowestAltitude();
  78.     }

  79.     /** {@inheritDoc} */
  80.     @Override
  81.     public NormalizedGeodeticPoint applyCorrection(final Vector3D satPos, final Vector3D satLos,
  82.                                                    final NormalizedGeodeticPoint rawIntersection,
  83.                                                    final IntersectionAlgorithm algorithm)
  84.         throws RuggedException {

  85.         try {
  86.             if (rawIntersection.getAltitude() < atmosphereLowestAltitude) {
  87.                 throw new RuggedException(RuggedMessages.NO_LAYER_DATA, rawIntersection.getAltitude(),
  88.                         atmosphereLowestAltitude);
  89.             }

  90.             Vector3D pos = satPos;
  91.             Vector3D los = satLos.normalize();
  92.             double n1 = -1;
  93.             GeodeticPoint gp = ellipsoid.transform(satPos, ellipsoid.getBodyFrame(), null);

  94.             for (ConstantRefractionLayer refractionLayer : refractionLayers) {

  95.                 if (refractionLayer.getLowestAltitude() > gp.getAltitude()) {
  96.                     continue;
  97.                 }

  98.                 final double n2 = refractionLayer.getRefractiveIndex();

  99.                 if (n1 > 0) {

  100.                     // when we get here, we have already performed one iteration in the loop
  101.                     // so gp is the los intersection with the layers interface (it was a
  102.                     // point on ground at loop initialization, but is overridden at each iteration)

  103.                     // get new los by applying Snell's law at atmosphere layers interfaces
  104.                     // we avoid computing sequences of inverse-trigo/trigo/inverse-trigo functions
  105.                     // we just use linear algebra and square roots, it is faster and more accurate

  106.                     // at interface crossing, the interface normal is z, the local zenith direction
  107.                     // the ray direction (i.e. los) is u in the upper layer and v in the lower layer
  108.                     // v is in the (u, zenith) plane, so we can say
  109.                     //  (1) v = α u + β z
  110.                     // with α>0 as u and v are roughly in the same direction as the ray is slightly bent

  111.                     // let θ₁ be the los incidence angle at interface crossing
  112.                     // θ₁ = π - angle(u, zenith) is between 0 and π/2 for a downwards observation
  113.                     // let θ₂ be the exit angle at interface crossing
  114.                     // from Snell's law, we have n₁ sin θ₁ = n₂ sin θ₂ and θ₂ is also between 0 and π/2
  115.                     // we have:
  116.                     //   (2) u·z = -cos θ₁
  117.                     //   (3) v·z = -cos θ₂
  118.                     // combining equations (1), (2) and (3) and remembering z·z = 1 as z is normalized , we get
  119.                     //   (4) β = α cos θ₁ - cos θ₂
  120.                     // with all the expressions above, we can rewrite the fact v is normalized:
  121.                     //       1 = v·v
  122.                     //         = α² u·u + 2αβ u·z + β² z·z
  123.                     //         = α² - 2αβ cos θ₁ + β²
  124.                     //         = α² - 2α² cos² θ₁ + 2 α cos θ₁ cos θ₂ + α² cos² θ₁ - 2 α cos θ₁ cos θ₂ + cos² θ₂
  125.                     //         = α²(1 - cos² θ₁) + cos² θ₂
  126.                     // hence α² = (1 - cos² θ₂)/(1 - cos² θ₁)
  127.                     //          = sin² θ₂ / sin² θ₁
  128.                     // as α is positive, and both θ₁ and θ₂ are between 0 and π/2, we finally get
  129.                     //       α  = sin θ₂ / sin θ₁
  130.                     //   (5) α  = n₁/n₂
  131.                     // the α coefficient is independent from the incidence angle,
  132.                     // it depends only on the ratio of refractive indices!
  133.                     //
  134.                     // back to equation (4) and using again the fact θ₂ is between 0 and π/2, we can now write
  135.                     //       β = α cos θ₁ - cos θ₂
  136.                     //         = n₁/n₂ cos θ₁ - cos θ₂
  137.                     //         = n₁/n₂ cos θ₁ - √(1 - sin² θ₂)
  138.                     //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² sin² θ₁)
  139.                     //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² (1 - cos² θ₁))
  140.                     //         = n₁/n₂ cos θ₁ - √(1 - (n₁/n₂)² + (n₁/n₂)² cos² θ₁)
  141.                     //   (6) β = -k - √(k² - ζ)
  142.                     // where ζ = (n₁/n₂)² - 1 and k = n₁/n₂ u·z, which is negative, and close to -1 for
  143.                     // nadir observations. As we expect atmosphere models to have small transitions between
  144.                     // layers, we have to handle accurately the case where n₁/n₂ ≈ 1 so ζ ≈ 0. In this case,
  145.                     // a cancellation occurs inside the square root: √(k² - ζ) ≈ √k² ≈ -k (because k is negative).
  146.                     // So β ≈ -k + k ≈ 0 and another cancellation occurs, outside of the square root.
  147.                     // This means that despite equation (6) is mathematically correct, it is prone to numerical
  148.                     // errors when consecutive layers have close refractive indices. A better equivalent
  149.                     // expression is needed. The fact β is close to 0 in this case was expected because
  150.                     // equation (1) reads v = α u + β z, and α = n₁/n₂, so when n₁/n₂ ≈ 1, we have
  151.                     // α ≈ 1 and β ≈ 0, so v ≈ u: when two layers have similar refractive indices, the
  152.                     // propagation direction is almost unchanged.
  153.                     //
  154.                     // The first step for the accurate computation of β is to compute ζ = (n₁/n₂)² - 1
  155.                     // accurately and avoid a cancellation just after a division (which is the least accurate
  156.                     // of the four operations) and a squaring. We will simply use:
  157.                     //   ζ = (n₁/n₂)² - 1
  158.                     //     = (n₁ - n₂) (n₁ + n₂) / n₂²
  159.                     // The cancellation is still there, but it occurs in the subtraction n₁ - n₂, which are
  160.                     // the most accurate values we can get.
  161.                     // The second step for the accurate computation of β is to rewrite equation (6)
  162.                     // by both multiplying and dividing by the dual factor -k + √(k² - ζ):
  163.                     //     β = -k - √(k² - ζ)
  164.                     //       = (-k - √(k² - ζ)) * (-k + √(k² - ζ)) / (-k + √(k² - ζ))
  165.                     //       = (k² - (k² - ζ)) / (-k + √(k² - ζ))
  166.                     // (7) β = ζ / (-k + √(k² - ζ))
  167.                     // expression (7) is more stable numerically than expression (6), because when ζ ≈ 0
  168.                     // its denominator is about -2k, there are no cancellation anymore after the square root.
  169.                     // β is computed with the same accuracy as ζ
  170.                     final double alpha = n1 / n2;
  171.                     final double k     = alpha * Vector3D.dotProduct(los, gp.getZenith());
  172.                     final double zeta  = (n1 - n2) * (n1 + n2) / (n2 * n2);
  173.                     final double beta  = zeta / (FastMath.sqrt(k * k - zeta) - k);
  174.                     los = new Vector3D(alpha, los, beta, gp.getZenith());
  175.                 }

  176.                 if (rawIntersection.getAltitude() > refractionLayer.getLowestAltitude()) {
  177.                     break;
  178.                 }

  179.                 // get intersection point
  180.                 pos = ellipsoid.pointAtAltitude(pos, los, refractionLayer.getLowestAltitude());
  181.                 gp  = ellipsoid.transform(pos, ellipsoid.getBodyFrame(), null);

  182.                 n1 = n2;

  183.             }

  184.             return algorithm.refineIntersection(ellipsoid, pos, los, rawIntersection);

  185.         } catch (OrekitException oe) {
  186.             throw new RuggedException(oe, oe.getSpecifier(), oe.getParts());
  187.         }
  188.     }
  189. }