diff --git a/src/main/java/org/orekit/rugged/api/AlgorithmId.java b/src/main/java/org/orekit/rugged/api/AlgorithmId.java index b8322f16abab49ca0e4e4c5aa2d4ebb4184def55..5616bf7a77ad6e4b5421e1a4f95e8ba210ae6c41 100644 --- a/src/main/java/org/orekit/rugged/api/AlgorithmId.java +++ b/src/main/java/org/orekit/rugged/api/AlgorithmId.java @@ -57,10 +57,25 @@ public enum AlgorithmId { */ BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY, + /** Algorithm that simply uses a constant elevation over ellipsoid. + * <p> + * Intersections are computed only with respect to the reference ellipsoid + * and a user-specified elevation. If the user-specified elevation is 0.0, + * then this algorithm is equivalent to {@link #IGNORE_DEM_USE_ELLIPSOID}, + * only slower. + * </p> + */ + CONSTANT_ELEVATION_OVER_ELLIPSOID, + /** Dummy algorithm that simply ignores the Digital Elevation Model. * <p> * Intersections are computed only with respect to the reference ellipsoid. * </p> + * <p> + * This algorithm is equivalent to {@link #CONSTANT_ELEVATION_OVER_ELLIPSOID} + * when the elevation is set to 0.0, but this one is much faster in this + * specific case. + * </p> */ IGNORE_DEM_USE_ELLIPSOID diff --git a/src/main/java/org/orekit/rugged/api/RuggedBuilder.java b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java index dbcc1a353eda86a350de0d624145bc86a611c7ae..17d7324752b5d002ec1d3f8cdd65734b8c75b228 100644 --- a/src/main/java/org/orekit/rugged/api/RuggedBuilder.java +++ b/src/main/java/org/orekit/rugged/api/RuggedBuilder.java @@ -39,6 +39,7 @@ import org.orekit.propagation.sampling.OrekitFixedStepHandler; import org.orekit.rugged.errors.RuggedException; import org.orekit.rugged.errors.RuggedMessages; import org.orekit.rugged.intersection.BasicScanAlgorithm; +import org.orekit.rugged.intersection.ConstantElevationAlgorithm; import org.orekit.rugged.intersection.IgnoreDEMAlgorithm; import org.orekit.rugged.intersection.IntersectionAlgorithm; import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm; @@ -96,6 +97,10 @@ public class RuggedBuilder { /** Updater used to load Digital Elevation Model tiles. */ private TileUpdater tileUpdater; + /** Constant elevation over ellipsoid. + * used only with {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID. */ + private double constantElevation; + /** Maximum number of tiles stored in the cache. */ private int maxCachedTiles; @@ -162,6 +167,7 @@ public class RuggedBuilder { */ public RuggedBuilder() { sensors = new ArrayList<LineSensor>(); + constantElevation = Double.NaN; lightTimeCorrection = true; aberrationOfLightCorrection = true; } @@ -211,10 +217,21 @@ public class RuggedBuilder { /** Set the algorithm to use for Digital Elevation Model intersection. * <p> - * Note that when the specified algorithm is {@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID}, - * then calling {@link #setDigitalElevationModel(TileUpdater, int)} is irrelevant and can either - * be completely avoided, or the method can be called with an updater set to {@code null}. - * For all other algorithms, the updater must be properly configured. + * Note that some algorithms require specific other methods to be called too: + * <ul> + * <li>{@link AlgorithmId#DUVENHAGE DUVENHAGE}, + * {@link AlgorithmId#DUVENHAGE_FLAT_BODY DUVENHAGE_FLAT_BODY} + * and {@link AlgorithmId#BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY + * BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY} all + * require {@link #setDigitalElevationModel(TileUpdater, int) setDigitalElevationModel} + * to be called,</li> + * <li>{@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID + * CONSTANT_ELEVATION_OVER_ELLIPSOID} requires + * {@link #setConstantElevation(double) setConstantElevation} to be called,</li> + * <li>{@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID + * IGNORE_DEM_USE_ELLIPSOID} does not require + * any methods tobe called.</li> + * </ul> * </p> * @param algorithmID identifier of algorithm to use for Digital Elevation Model intersection * @return the builder instance @@ -239,9 +256,12 @@ public class RuggedBuilder { /** Set the user-provided {@link TileUpdater tile updater}. * <p> * Note that when the algorithm specified in {@link #setAlgorithm(AlgorithmId)} - * is {@link AlgorithmId#IGNORE_DEM_USE_ELLIPSOID},then this method becomes irrelevant - * and can either be not called at all, or it can be called with an updater set to - * {@code null}. For all other algorithms, the updater must be properly configured. + * is either {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID + * CONSTANT_ELEVATION_OVER_ELLIPSOID} or {@link + * AlgorithmId#IGNORE_DEM_USE_ELLIPSOID IGNORE_DEM_USE_ELLIPSOID}, + * then this method becomes irrelevant and can either be not called at all, + * or it can be called with an updater set to {@code null}. For all other + * algorithms, the updater must be properly configured. * </p> * @param tileUpdater updater used to load Digital Elevation Model tiles * @param maxCachedTiles maximum number of tiles stored in the cache @@ -267,6 +287,33 @@ public class RuggedBuilder { return tileUpdater; } + /** Set the user-provided constant elevation model. + * <p> + * Note that this method is relevant <em>only</em> if the algorithm specified + * in {@link #setAlgorithm(AlgorithmId)} is {@link + * AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID CONSTANT_ELEVATION_OVER_ELLIPSOID}. + * If it is called for this algorithm, the elevation set here will be ignored. + * </p> + * @param constantElevation constant elevation to use + * @return the builder instance + * @see #setAlgorithm(AlgorithmId) + * @see #getConstantElevation() + */ + // CHECKSTYLE: stop HiddenField check + public RuggedBuilder setConstantElevation(final double constantElevation) { + // CHECKSTYLE: resume HiddenField check + this.constantElevation = constantElevation; + return this; + } + + /** Get the constant elevation over ellipsoid to use with {@link AlgorithmId#CONSTANT_ELEVATION_OVER_ELLIPSOID}. + * @return updater used to load Digital Elevation Model tiles + * @see #setConstantElevation(double) + */ + public double getConstantElevation() { + return constantElevation; + } + /** Get the maximum number of tiles stored in the cache. * @return maximum number of tiles stored in the cache * @see #setDigitalElevationModel(TileUpdater, int) @@ -903,10 +950,12 @@ public class RuggedBuilder { * @param algorithmID intersection algorithm identifier * @param updater updater used to load Digital Elevation Model tiles * @param maxCachedTiles maximum number of tiles stored in the cache + * @param constantElevation constant elevation over ellipsoid * @return selected algorithm */ private static IntersectionAlgorithm createAlgorithm(final AlgorithmId algorithmID, - final TileUpdater updater, final int maxCachedTiles) { + final TileUpdater updater, final int maxCachedTiles, + final double constantElevation) { // set up the algorithm switch (algorithmID) { @@ -916,6 +965,8 @@ public class RuggedBuilder { return new DuvenhageAlgorithm(updater, maxCachedTiles, true); case BASIC_SLOW_EXHAUSTIVE_SCAN_FOR_TESTS_ONLY : return new BasicScanAlgorithm(updater, maxCachedTiles); + case CONSTANT_ELEVATION_OVER_ELLIPSOID : + return new ConstantElevationAlgorithm(constantElevation); case IGNORE_DEM_USE_ELLIPSOID : return new IgnoreDEMAlgorithm(); default : @@ -934,11 +985,17 @@ public class RuggedBuilder { if (algorithmID == null) { throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setAlgorithmID()"); } - if (algorithmID != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID && (tileUpdater == null)) { - throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setDigitalElevationModel()"); + if (algorithmID == AlgorithmId.CONSTANT_ELEVATION_OVER_ELLIPSOID) { + if (Double.isNaN(constantElevation)) { + throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setConstantElevation()"); + } + } else if (algorithmID != AlgorithmId.IGNORE_DEM_USE_ELLIPSOID) { + if (tileUpdater == null) { + throw new RuggedException(RuggedMessages.UNINITIALIZED_CONTEXT, "RuggedBuilder.setDigitalElevationModel()"); + } } createInterpolatorIfNeeded(); - return new Rugged(createAlgorithm(algorithmID, tileUpdater, maxCachedTiles), ellipsoid, + return new Rugged(createAlgorithm(algorithmID, tileUpdater, maxCachedTiles, constantElevation), ellipsoid, lightTimeCorrection, aberrationOfLightCorrection, scToBody, sensors); } diff --git a/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java b/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java new file mode 100644 index 0000000000000000000000000000000000000000..8e9cb7b3981306b39c0556c6e37c10039946faed --- /dev/null +++ b/src/main/java/org/orekit/rugged/intersection/ConstantElevationAlgorithm.java @@ -0,0 +1,85 @@ +/* Copyright 2013-2015 CS Systèmes d'Information + * Licensed to CS Systèmes d'Information (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.rugged.intersection; + +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import org.orekit.bodies.GeodeticPoint; +import org.orekit.errors.OrekitException; +import org.orekit.rugged.errors.RuggedException; +import org.orekit.rugged.utils.ExtendedEllipsoid; +import org.orekit.rugged.utils.NormalizedGeodeticPoint; + +/** Intersection ignoring Digital Elevation Model. + * <p> + * This dummy implementation simply uses the ellipsoid itself. + * </p> + * @author Luc Maisonobe + */ +public class ConstantElevationAlgorithm implements IntersectionAlgorithm { + + /** Constant elevation over ellipsoid. */ + private final double constantElevation; + + /** Simple constructor. + * @param constantElevation constant elevation over ellipsoid + */ + public ConstantElevationAlgorithm(final double constantElevation) { + this.constantElevation = constantElevation; + } + + /** {@inheritDoc} */ + @Override + public NormalizedGeodeticPoint intersection(final ExtendedEllipsoid ellipsoid, + final Vector3D position, final Vector3D los) + throws RuggedException { + try { + final Vector3D p = ellipsoid.pointAtAltitude(position, los, constantElevation); + final GeodeticPoint gp = ellipsoid.transform(p, ellipsoid.getFrame(), null); + return new NormalizedGeodeticPoint(gp.getLatitude(), gp.getLongitude(), gp.getAltitude(), 0.0); + } catch (OrekitException oe) { + throw new RuggedException(oe, oe.getSpecifier(), oe.getParts()); + } + } + + /** {@inheritDoc} */ + @Override + public NormalizedGeodeticPoint refineIntersection(final ExtendedEllipsoid ellipsoid, + final Vector3D position, final Vector3D los, + final NormalizedGeodeticPoint closeGuess) + throws RuggedException { + try { + final Vector3D p = ellipsoid.pointAtAltitude(position, los, constantElevation); + final GeodeticPoint gp = ellipsoid.transform(p, ellipsoid.getFrame(), null); + return new NormalizedGeodeticPoint(gp.getLatitude(), gp.getLongitude(), gp.getAltitude(), + closeGuess.getLongitude()); + } catch (OrekitException oe) { + throw new RuggedException(oe, oe.getSpecifier(), oe.getParts()); + } + } + + /** {@inheritDoc} + * <p> + * As this algorithm uses a constant elevation, + * this method always returns the same value. + * </p> + */ + @Override + public double getElevation(final double latitude, final double longitude) { + return constantElevation; + } + +} diff --git a/src/site/markdown/design/digital-elevation-model.md b/src/site/markdown/design/digital-elevation-model.md index 0d51a6b2962f28be9216b629f6028b01f6901938..cfb648e6ce6aaabe0f162b9dd850fe80e7ca3aea 100644 --- a/src/site/markdown/design/digital-elevation-model.md +++ b/src/site/markdown/design/digital-elevation-model.md @@ -26,12 +26,13 @@ a nadir view), some algorithms are more suitable than others. This computation i programming unit possible in the Rugged library and an interface is defined with several different implementations among which user can select. -Four different algorithms are predefined in Rugged: +Five different algorithms are predefined in Rugged: * a recursive algorithm based on Bernardt Duvenhage's 2009 paper [Using An Implicit Min/Max KD-Tree for Doing Efficient Terrain Line of Sight Calculations](http://researchspace.csir.co.za/dspace/bitstream/10204/3041/1/Duvenhage_2009.pdf) * an alternate version of the Duvenhage algorithm using flat-body hypothesis, * a basic scan algorithm sequentially checking all pixels in the rectangular array defined by Digital Elevation Model entry and exit points, + * an algorithm that ignores the Digital Elevation Model and uses a constant elevation over the ellipsoid. * a no-operation algorithm that ignores the Digital Elevation Model and uses only the ellipsoid. It is expected that other algorithms like line-stepping (perhaps using Bresenham line algorithm) will be added afterwards. diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml index df750dc72905dd40e0182f3c1d7ff20de94dc625..8f9d7f4595a5a8b1de8d019055f51b87e64934ee 100644 --- a/src/site/xdoc/changes.xml +++ b/src/site/xdoc/changes.xml @@ -22,6 +22,12 @@ <body> <release version="1.0" date="TBD" description="TBD"> + <action dev="luc" type="add" > + Added a CONSTANT_ELEVATION_OVER_ELLIPSOID algorithm, similar in spirit + to the IGNORE_DEM_USE_ELLIPSOID, but with a user-specified elevation + instead of always using 0.0 elevation. + Implements feature #187. + </action> <action dev="luc" type="add" due-to="Espen Bjørntvedt"> Added Norwegian translation of error messages. </action> diff --git a/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java index 51bbca8b08414cc71cb6b05cf81df7dfcf9dafe6..fcc8e22eae31cf0d78770d5b979c09b05742f58f 100644 --- a/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java +++ b/src/test/java/org/orekit/rugged/intersection/AbstractAlgorithmTest.java @@ -147,6 +147,7 @@ public abstract class AbstractAlgorithmTest { Vector3D position = state.getPVCoordinates(earth.getBodyFrame()).getPosition(); Vector3D los = groundP.subtract(position); + System.out.println(los.normalize()); GeodeticPoint result = algorithm.refineIntersection(earth, position, los, algorithm.intersection(earth, position, los)); checkIntersection(position, los, result); diff --git a/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java b/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java new file mode 100644 index 0000000000000000000000000000000000000000..471180cf802a74990c49987529d9a8a9d2c5d2c2 --- /dev/null +++ b/src/test/java/org/orekit/rugged/intersection/ConstantElevationAlgorithmTest.java @@ -0,0 +1,143 @@ +/* Copyright 2013-2015 CS Systèmes d'Information + * Licensed to CS Systèmes d'Information (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.rugged.intersection; + + +import java.io.File; +import java.net.URISyntaxException; + +import org.apache.commons.math3.geometry.euclidean.threed.Rotation; +import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import org.apache.commons.math3.util.FastMath; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.orekit.attitudes.Attitude; +import org.orekit.data.DataProvidersManager; +import org.orekit.data.DirectoryCrawler; +import org.orekit.errors.OrekitException; +import org.orekit.frames.FramesFactory; +import org.orekit.orbits.CartesianOrbit; +import org.orekit.propagation.SpacecraftState; +import org.orekit.rugged.errors.RuggedException; +import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm; +import org.orekit.rugged.raster.CheckedPatternElevationUpdater; +import org.orekit.rugged.raster.TileUpdater; +import org.orekit.rugged.utils.ExtendedEllipsoid; +import org.orekit.rugged.utils.NormalizedGeodeticPoint; +import org.orekit.time.AbsoluteDate; +import org.orekit.time.TimeScalesFactory; +import org.orekit.utils.Constants; +import org.orekit.utils.IERSConventions; +import org.orekit.utils.PVCoordinates; + +public class ConstantElevationAlgorithmTest { + + @Test + public void testDuvenhageComparison() throws RuggedException { + final Vector3D los = new Vector3D(-0.626242839, 0.0124194184, -0.7795291301); + IntersectionAlgorithm duvenhage = new DuvenhageAlgorithm(new CheckedPatternElevationUpdater(FastMath.toRadians(1.0), + 256, 150.0, 150.0), + 8, false); + IntersectionAlgorithm constantElevation = new ConstantElevationAlgorithm(150.0); + NormalizedGeodeticPoint gpRef = duvenhage.intersection(earth, state.getPVCoordinates().getPosition(), los); + NormalizedGeodeticPoint gpConst = constantElevation.intersection(earth, state.getPVCoordinates().getPosition(), los); + Assert.assertEquals(gpRef.getLatitude(), gpConst.getLatitude(), 1.0e-6); + Assert.assertEquals(gpRef.getLongitude(), gpConst.getLongitude(), 1.0e-6); + Assert.assertEquals(gpRef.getAltitude(), gpConst.getAltitude(), 1.0e-3); + Assert.assertEquals(150.0, constantElevation.getElevation(0.0, 0.0), 1.0e-3); + + // shift longitude 2π + NormalizedGeodeticPoint shifted = + constantElevation.refineIntersection(earth, state.getPVCoordinates().getPosition(), los, + new NormalizedGeodeticPoint(gpConst.getLatitude(), + gpConst.getLongitude(), + gpConst.getAltitude(), + 2 * FastMath.PI)); + Assert.assertEquals(2 * FastMath.PI + gpConst.getLongitude(), shifted.getLongitude(), 1.0e-6); + + } + + @Test + public void testIgnoreDEMComparison() throws RuggedException { + final Vector3D los = new Vector3D(-0.626242839, 0.0124194184, -0.7795291301); + IntersectionAlgorithm ignore = new IgnoreDEMAlgorithm(); + IntersectionAlgorithm constantElevation = new ConstantElevationAlgorithm(0.0); + NormalizedGeodeticPoint gpRef = ignore.intersection(earth, state.getPVCoordinates().getPosition(), los); + NormalizedGeodeticPoint gpConst = constantElevation.intersection(earth, state.getPVCoordinates().getPosition(), los); + Assert.assertEquals(gpRef.getLatitude(), gpConst.getLatitude(), 1.0e-6); + Assert.assertEquals(gpRef.getLongitude(), gpConst.getLongitude(), 1.0e-6); + Assert.assertEquals(gpRef.getAltitude(), gpConst.getAltitude(), 1.0e-3); + Assert.assertEquals(0.0, constantElevation.getElevation(0.0, 0.0), 1.0e-3); + + // shift longitude 2π + NormalizedGeodeticPoint shifted = + constantElevation.refineIntersection(earth, state.getPVCoordinates().getPosition(), los, + new NormalizedGeodeticPoint(gpConst.getLatitude(), + gpConst.getLongitude(), + gpConst.getAltitude(), + 2 * FastMath.PI)); + Assert.assertEquals(2 * FastMath.PI + gpConst.getLongitude(), shifted.getLongitude(), 1.0e-6); + + } + + @Before + public void setUp() + throws OrekitException, URISyntaxException { + String path = getClass().getClassLoader().getResource("orekit-data").toURI().getPath(); + DataProvidersManager.getInstance().addProvider(new DirectoryCrawler(new File(path))); + earth = new ExtendedEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, + Constants.WGS84_EARTH_FLATTENING, + FramesFactory.getITRF(IERSConventions.IERS_2010, true)); + + AbsoluteDate crossing = new AbsoluteDate("2012-01-07T11:50:04.935272115", TimeScalesFactory.getUTC()); + state = new SpacecraftState(new CartesianOrbit(new PVCoordinates(new Vector3D( 412324.544397459, + -4325872.329311633, + 5692124.593989491), + new Vector3D(-1293.174701214779, + -5900.764863603793, + -4378.671036383179)), + FramesFactory.getEME2000(), + crossing, + Constants.EIGEN5C_EARTH_MU), + new Attitude(crossing, + FramesFactory.getEME2000(), + new Rotation(-0.17806699079182878, + 0.60143347387211290, + -0.73251248177468900, + -0.26456641385623986, + true), + new Vector3D(-4.289600857433520e-05, + -1.039151496480297e-03, + 5.811423736843181e-05), + Vector3D.ZERO)); + + } + + @After + public void tearDown() { + earth = null; + updater = null; + state = null; + } + + protected ExtendedEllipsoid earth; + protected TileUpdater updater; + protected SpacecraftState state; + +}