diff --git a/atmospheric-deviation.csv b/atmospheric-deviation.csv deleted file mode 100644 index 2807245d5cfeb5959c8dc80a22722c2b6f87614a..0000000000000000000000000000000000000000 --- a/atmospheric-deviation.csv +++ /dev/null @@ -1,41 +0,0 @@ -angle,correction -0.0,0 -0.01,0 -0.02,0 -0.03,0 -0.04,0 -0.05,0 -0.060000000000000005,0 -0.07,0 -0.08,0 -0.09,0 -0.09999999999999999,0 -0.10999999999999999,0 -0.11999999999999998,0 -0.12999999999999998,0 -0.13999999999999999,0 -0.15,0 -0.16,1 -0.17,1 -0.18000000000000002,1 -0.19000000000000003,1 -0.20000000000000004,1 -0.21000000000000005,1 -0.22000000000000006,1 -0.23000000000000007,1 -0.24000000000000007,1 -0.25000000000000006,1 -0.26000000000000006,1 -0.2700000000000001,1 -0.2800000000000001,1 -0.2900000000000001,1 -0.3000000000000001,1 -0.3100000000000001,1 -0.3200000000000001,1 -0.3300000000000001,1 -0.34000000000000014,1 -0.35000000000000014,1 -0.36000000000000015,1 -0.37000000000000016,1 -0.38000000000000017,2 -0.3900000000000002,2 diff --git a/atmospheric-deviation.gp b/atmospheric-deviation.gp deleted file mode 100644 index 88716c6e85efa2af4d61f2ddb0aaf927e17c5fee..0000000000000000000000000000000000000000 --- a/atmospheric-deviation.gp +++ /dev/null @@ -1,13 +0,0 @@ -# gnuplot script file for plotting bandwidth over time -#!/usr/bin/gnuplot - - -set terminal pdf size 3,2 -set output 'atmospheric-deviation.pdf' -set grid - -set title "Atmospheric Correction" -set xlabel "angle (radians)" -set ylabel "correction (meters)" - -plot "atmospheric-deviation.csv" using 1:0 with lines title "" diff --git a/atmospheric-deviation.pdf b/atmospheric-deviation.pdf deleted file mode 100644 index 1ce2e9bf36894482b4061c28bf544f2d59192897..0000000000000000000000000000000000000000 Binary files a/atmospheric-deviation.pdf and /dev/null differ diff --git a/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java b/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java index 409a6a7180c494ebcc8968d9f27976da4523689b..27c595f6ff9329e7659ee35df7bbec4901ec26a0 100644 --- a/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java +++ b/src/test/java/org/orekit/rugged/refraction/MultiLayerModelTest.java @@ -16,46 +16,63 @@ */ package org.orekit.rugged.refraction; +import org.hipparchus.analysis.UnivariateFunction; import org.hipparchus.geometry.euclidean.threed.Rotation; import org.hipparchus.geometry.euclidean.threed.RotationConvention; import org.hipparchus.geometry.euclidean.threed.Vector3D; import org.hipparchus.util.FastMath; import org.junit.Assert; import org.junit.Test; -import org.orekit.bodies.GeodeticPoint; import org.orekit.errors.OrekitException; import org.orekit.rugged.errors.RuggedException; +import org.orekit.rugged.errors.RuggedMessages; import org.orekit.rugged.intersection.AbstractAlgorithmTest; import org.orekit.rugged.intersection.IntersectionAlgorithm; import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm; import org.orekit.rugged.raster.TileUpdater; import org.orekit.rugged.utils.NormalizedGeodeticPoint; -import java.io.FileNotFoundException; -import java.io.PrintWriter; +import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class MultiLayerModelTest extends AbstractAlgorithmTest { @Test - public void testApplyCorrection() throws OrekitException, RuggedException, FileNotFoundException { + public void testAlmostNadir() throws OrekitException, RuggedException { setUpMayonVolcanoContext(); final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); final Vector3D los = new Vector3D( 0.5127552821932051, -0.8254313129088879, -0.2361041470463311); - final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, - algorithm.intersection(earth, position, los)); + final NormalizedGeodeticPoint rawIntersection = + algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); MultiLayerModel model = new MultiLayerModel(earth); - GeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); + NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); // this is almost a Nadir observation (LOS deviates between 1.4 and 1.6 degrees from vertical) // so the refraction correction is small Assert.assertEquals(0.0553797, distance, 1.0e-6); + } + + @Test + public void testNoOpRefraction() throws OrekitException, RuggedException { + + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + final Vector3D los = los(position, FastMath.toRadians(50.0)); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); + + MultiLayerModel model = new MultiLayerModel(earth); + NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); + double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); // a test with indices all set to 1.0 - correction must be zero final int numberOfLayers = 16; @@ -66,78 +83,185 @@ public class MultiLayerModelTest extends AbstractAlgorithmTest { model = new MultiLayerModel(earth, refractionLayers); correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); - Assert.assertEquals(0.0, distance, 0.001); + Assert.assertEquals(0.0, distance, 1.0e-9); + } + + @Test + public void testReversedAtmosphere() + throws OrekitException, RuggedException, IllegalArgumentException, + IllegalAccessException, NoSuchFieldException, SecurityException { + + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + final Vector3D los = los(position, FastMath.toRadians(50.0)); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); + + MultiLayerModel baseModel = new MultiLayerModel(earth); + NormalizedGeodeticPoint correctedIntersection = baseModel.applyCorrection(position, los, rawIntersection, algorithm); // an intentionally flawed atmosphere with refractive indices decreasing with altitude, // that should exhibit a LOS bending upwards - refractionLayers = new ArrayList<ConstantRefractionLayer>(numberOfLayers); - for(int i = numberOfLayers - 1; i >= 0; i--) { - refractionLayers.add(new ConstantRefractionLayer(i * 1.0e4, 1.0 + i*i*1e-6)); + Field refractionLayersField = MultiLayerModel.class.getDeclaredField("refractionLayers"); + refractionLayersField.setAccessible(true); + @SuppressWarnings("unchecked") + List<ConstantRefractionLayer> baseRefractionLayers = + (List<ConstantRefractionLayer>) refractionLayersField.get(baseModel); + List<ConstantRefractionLayer> denserRefractionLayers = new ArrayList<>(); + for (final ConstantRefractionLayer layer : baseRefractionLayers) { + denserRefractionLayers.add(new ConstantRefractionLayer(layer.getLowestAltitude(), + 1.0 / layer.getRefractiveIndex())); } - model = new MultiLayerModel(earth, refractionLayers); - correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); - double anglePosRawIntersection = Vector3D.angle(position, earth.transform(rawIntersection)); + MultiLayerModel reversedModel = new MultiLayerModel(earth, denserRefractionLayers); + NormalizedGeodeticPoint reversedIntersection = reversedModel.applyCorrection(position, los, rawIntersection, algorithm); + double anglePosRawIntersection = Vector3D.angle(position, earth.transform(rawIntersection)); double anglePosCorrectedIntersection = Vector3D.angle(position, earth.transform(correctedIntersection)); - Assert.assertTrue(anglePosRawIntersection < anglePosCorrectedIntersection); + double anglePosReversedIntersection = Vector3D.angle(position, earth.transform(reversedIntersection)); + + // with regular atmosphere, the ray bends downwards, + // so the ground point is closer to the sub-satellite point than the raw intersection + Assert.assertTrue(anglePosCorrectedIntersection < anglePosRawIntersection); + + // with reversed atmosphere, the ray bends upwards, + // so the ground point is farther from the sub-satellite point than the raw intersection + Assert.assertTrue(anglePosReversedIntersection > anglePosRawIntersection); + // the points are almost aligned (for distances around 20m, Earth curvature is small enough) + double dRawCorrected = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); + double dRawReversed = Vector3D.distance(earth.transform(rawIntersection), earth.transform(reversedIntersection)); + double dReversedCorrected = Vector3D.distance(earth.transform(reversedIntersection), earth.transform(correctedIntersection)); + Assert.assertEquals(dRawCorrected + dRawReversed, dReversedCorrected, 1.0e-12 * dReversedCorrected); + + } + + @Test + public void testTwoAtmospheres() + throws OrekitException, RuggedException, IllegalArgumentException, + IllegalAccessException, NoSuchFieldException, SecurityException { + + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + final Vector3D los = los(position, FastMath.toRadians(50.0)); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); // a comparison between two atmospheres, one more dense than the other and showing correction // is more important with high indices - List<ConstantRefractionLayer> baseRefracLayers = new ArrayList<ConstantRefractionLayer>(numberOfLayers); - List<ConstantRefractionLayer> denserRefracLayers = new ArrayList<ConstantRefractionLayer>(numberOfLayers); - for(int i = numberOfLayers - 1; i >= 0; i--) { - double baseRefractiveIndex = FastMath.pow(numberOfLayers - i + 1, 2) * 1e-6; - baseRefracLayers.add(new ConstantRefractionLayer(i * 1.0e4, baseRefractiveIndex)); - denserRefracLayers.add(new ConstantRefractionLayer(i * 1.0e4, baseRefractiveIndex + 1e-3)); + MultiLayerModel baseModel = new MultiLayerModel(earth); + Field refractionLayersField = MultiLayerModel.class.getDeclaredField("refractionLayers"); + refractionLayersField.setAccessible(true); + @SuppressWarnings("unchecked") + List<ConstantRefractionLayer> baseRefractionLayers = + (List<ConstantRefractionLayer>) refractionLayersField.get(baseModel); + List<ConstantRefractionLayer> denserRefractionLayers = new ArrayList<>(); + double previousBaseN = 1.0; + double previousDenserN = 1.0; + double factor = 1.00001; + for (final ConstantRefractionLayer layer : baseRefractionLayers) { + final double currentBaseN = layer.getRefractiveIndex(); + final double baseRatio = currentBaseN / previousBaseN; + final double currentDenserN = previousDenserN * factor * baseRatio; + denserRefractionLayers.add(new ConstantRefractionLayer(layer.getLowestAltitude(), + currentDenserN)); + previousBaseN = currentBaseN; + previousDenserN = currentDenserN; } - MultiLayerModel baseModel = new MultiLayerModel(earth, baseRefracLayers); - MultiLayerModel denserModel = new MultiLayerModel(earth, denserRefracLayers); - GeodeticPoint baseIntersection = baseModel.applyCorrection(position, los, rawIntersection, algorithm); - GeodeticPoint denserIntersection = denserModel.applyCorrection(position, los, rawIntersection, algorithm); - double baseDistance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(baseIntersection)); + MultiLayerModel denserModel = new MultiLayerModel(earth, denserRefractionLayers); + NormalizedGeodeticPoint baseIntersection = baseModel.applyCorrection(position, los, rawIntersection, algorithm); + NormalizedGeodeticPoint denserIntersection = denserModel.applyCorrection(position, los, rawIntersection, algorithm); + double baseDistance = Vector3D.distance(earth.transform(rawIntersection), + earth.transform(baseIntersection)); double denserDistance = Vector3D.distance(earth.transform(rawIntersection), - earth.transform(denserIntersection)); - // denserDistance: 291.6042252928431, baseDistance: 2710.1036961651967 - // Assert.assertTrue(denserDistance > baseDistance); + earth.transform(denserIntersection)); + Assert.assertTrue(denserDistance > baseDistance); + } - // a test with a single refraction layer - refractionLayers = new ArrayList<ConstantRefractionLayer>(numberOfLayers); - refractionLayers.add(new ConstantRefractionLayer(0, 1.2)); - model = new MultiLayerModel(earth, refractionLayers); - correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); - distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); - Assert.assertEquals(0.0, distance, 0.0); + @Test + public void testMissingLayers() throws OrekitException, RuggedException { + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + final Vector3D los = los(position, FastMath.toRadians(50.0)); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); + final double h = rawIntersection.getAltitude(); - // deviation should increase as the angle between los and zenith increases - PrintWriter writer = new PrintWriter("atmospheric-deviation.csv"); - writer.println("angle,correction"); + MultiLayerModel model = new MultiLayerModel(earth, + Collections.singletonList(new ConstantRefractionLayer(h + 100.0, + 1.5))); + try { + model.applyCorrection(position, los, rawIntersection, algorithm); + Assert.fail("an exception should have been thrown"); + } catch (RuggedException re) { + Assert.assertEquals(RuggedMessages.NO_LAYER_DATA, re.getSpecifier()); + Assert.assertEquals(h, ((Double) re.getParts()[0]).doubleValue(), 1.0e-6); + Assert.assertEquals(h + 100.0, ((Double) re.getParts()[1]).doubleValue(), 1.0e-6); + } - GeodeticPoint satGP = earth.transform(position, earth.getBodyFrame(), null); - Vector3D nadir = satGP.getNadir(); - Vector3D horizontal = nadir.orthogonal(); - for (double alpha = 0; alpha < 0.4; alpha += 0.01) { - Vector3D rotatingLos = new Rotation(horizontal, alpha, RotationConvention.VECTOR_OPERATOR).applyTo(nadir); - NormalizedGeodeticPoint uncorrectedIntersection = algorithm.refineIntersection(earth, position, rotatingLos, - algorithm.intersection(earth, position, rotatingLos)); + } - model = new MultiLayerModel(earth); - correctedIntersection = model.applyCorrection(position, rotatingLos, uncorrectedIntersection, algorithm); - distance = Vector3D.distance(earth.transform(uncorrectedIntersection), - earth.transform(correctedIntersection)); + @Test + public void testLayersBelowDEM() throws OrekitException, RuggedException { - writer.println(alpha + "," + FastMath.round(distance)); - } - writer.close(); + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + final Vector3D los = los(position, FastMath.toRadians(50.0)); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, los, + algorithm.intersection(earth, position, los)); + MultiLayerModel model = new MultiLayerModel(earth, + Collections.singletonList(new ConstantRefractionLayer(rawIntersection.getAltitude() - 100.0, + 1.5))); + NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, los, rawIntersection, algorithm); + double distance = Vector3D.distance(earth.transform(rawIntersection), earth.transform(correctedIntersection)); + Assert.assertEquals(0.0, distance, 1.0e-20); + } + + @Test + public void testDivingAngleChange() throws OrekitException, RuggedException { + + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + final Vector3D position = new Vector3D(-3787079.6453602533, 5856784.405679551, 1655869.0582939098); + AtmosphericRefraction model = new MultiLayerModel(earth); + + // deviation should increase from 0 to about 17m + // as the angle between los and nadir increases from 0 to 50 degrees + // the reference model below has been obtained by fitting the test results themselves + // it is NOT considered a full featured model, it's just a helper function for this specific test + UnivariateFunction reference = alpha -> 1.17936 * FastMath.tan((2.94613 - 1.40162 * alpha) * alpha); + + for (double alpha = 0; alpha < FastMath.toRadians(50.0); alpha += 0.1) { + final Vector3D rotatingLos = los(position, alpha); + final NormalizedGeodeticPoint rawIntersection = algorithm.refineIntersection(earth, position, rotatingLos, + algorithm.intersection(earth, position, rotatingLos)); + + final NormalizedGeodeticPoint correctedIntersection = model.applyCorrection(position, rotatingLos, rawIntersection, algorithm); + final double distance = Vector3D.distance(earth.transform(rawIntersection), + earth.transform(correctedIntersection)); + Assert.assertEquals(reference.value(alpha), distance, 0.12); + } } + private Vector3D los(final Vector3D position, final double angleFromNadir) + throws OrekitException { + final Vector3D nadir = earth.transform(position, earth.getBodyFrame(), null).getNadir(); + final Rotation losRotation = new Rotation(nadir.orthogonal(), angleFromNadir, + RotationConvention.VECTOR_OPERATOR); + return losRotation.applyTo(nadir); + } + @Override protected IntersectionAlgorithm createAlgorithm(TileUpdater updater, int maxCachedTiles) { return new DuvenhageAlgorithm(updater, maxCachedTiles, false); } + }