diff --git a/core/src/main/java/org/orekit/rugged/api/RuggedMessages.java b/core/src/main/java/org/orekit/rugged/api/RuggedMessages.java index 044571dd9b86d4fbf0a194c7bcaeffdc6abd3c6e..0f63da906b0335009f4fad7e5af68b8f0f4ef440 100644 --- a/core/src/main/java/org/orekit/rugged/api/RuggedMessages.java +++ b/core/src/main/java/org/orekit/rugged/api/RuggedMessages.java @@ -57,6 +57,7 @@ public enum RuggedMessages implements Localizable { UNINITIALIZED_CONTEXT("general context has not been initialized"), EMPTY_TILE("tile is empty: {0} ⨉ {1}"), UNKNOWN_SENSOR("unknown sensor {0}"), + LINE_OF_SIGHT_DOES_NOT_REACH_GROUND("line-of-sight does not reach ground"), LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE("line-of-sight never crosses latitude {0}"), LINE_OF_SIGHT_NEVER_CROSSES_LONGITUDE("line-of-sight never crosses longitude {0}"), LINE_OF_SIGHT_NEVER_CROSSES_ALTITUDE("line-of-sight never crosses altitude {0}"), diff --git a/core/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java b/core/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java index 9079bc36c0d74dd1dd5a04323b93223f77b3a1a9..ac359e18104440393675b71133d775f8e9103b77 100644 --- a/core/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java +++ b/core/src/main/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithm.java @@ -18,6 +18,7 @@ package org.orekit.rugged.intersection.duvenhage; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; import org.orekit.bodies.GeodeticPoint; import org.orekit.errors.OrekitException; import org.orekit.rugged.api.RuggedException; @@ -72,6 +73,9 @@ public class DuvenhageAlgorithm implements IntersectionAlgorithm { // compute intersection with ellipsoid final GeodeticPoint gp0 = ellipsoid.pointOnGround(position, los); + if (gp0 == null) { + throw new RuggedException(RuggedMessages.LINE_OF_SIGHT_DOES_NOT_REACH_GROUND); + } // locate the entry tile along the line-of-sight MinMaxTreeTile tile = cache.getTile(gp0.getLatitude(), gp0.getLongitude()); @@ -381,7 +385,11 @@ public class DuvenhageAlgorithm implements IntersectionAlgorithm { final Vector3D exitP = ellipsoid.pointAtAltitude(position, los, tile.getMinElevation() - STEP); final GeodeticPoint exitGP = ellipsoid.transform(exitP, ellipsoid.getBodyFrame(), null); - switch (tile.getLocation(exitGP.getLatitude(), exitGP.getLongitude())) { + // fix longitude discontinuity + final double meanTileLongitude = tile.getLongitudeAtIndex(tile.getLongitudeColumns() / 2); + final double fixedLongitude = MathUtils.normalizeAngle(exitGP.getLongitude(), meanTileLongitude); + + switch (tile.getLocation(exitGP.getLatitude(), fixedLongitude)) { case SOUTH_WEST : return new LimitPoint(ellipsoid, selectClosest(ellipsoid.pointAtLatitude(position, los, tile.getMinimumLatitude(), exitP), diff --git a/core/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java b/core/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java index 5c2ea740d9e553ad9abf551e43dbc467a21fdb1c..273bb4d06c5e07d0a91522bdc3e4675b55dbee65 100644 --- a/core/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java +++ b/core/src/main/java/org/orekit/rugged/intersection/duvenhage/MinMaxTreeTile.java @@ -30,7 +30,7 @@ import org.orekit.rugged.raster.SimpleTile; * Level n-1, which is the deepest one, is computed from the raw pixels by * merging adjacent pixels pairs columns (i.e. pixels at indices (i, 2j) * and (i, 2j+1) are merged together by computing and storing the minimum - * and maxium in a sub-tile. Level n-1 therefore has the same number of rows + * and maximum in a sub-tile. Level n-1 therefore has the same number of rows * but half the number of columns of the raw tile, and its sub-tiles are * 1 pixel high and 2 pixels wide. Level n-2 is computed from level n-1 by * merging sub-tiles rows. Level n-2 therefore has half the number of rows diff --git a/core/src/main/java/org/orekit/rugged/raster/Tile.java b/core/src/main/java/org/orekit/rugged/raster/Tile.java index 21d5547e5a65e820263aab611a5f3f2b036012bd..eb91e9d2f14f473062bd25b7774abacc62c6b299 100644 --- a/core/src/main/java/org/orekit/rugged/raster/Tile.java +++ b/core/src/main/java/org/orekit/rugged/raster/Tile.java @@ -21,6 +21,12 @@ import org.orekit.bodies.GeodeticPoint; import org.orekit.rugged.api.RuggedException; /** Interface representing a raster tile. + * <p> + * The elevations are considered to be at the <em>center</em> of each pixels. + * The minimum latitude and longitude hence correspond to the <em>center</em> + * of the most South-West pixel, and the maximum latitude and longitude + * correspond to the <em>center</em> of the most North-East pixel. + * </p> * @author Luc Maisonobe */ public interface Tile extends UpdatableTile { @@ -65,33 +71,39 @@ public interface Tile extends UpdatableTile { /** Get minimum latitude. * @return minimum latitude + * (latitude of the center of the pixels of South row) */ double getMinimumLatitude(); /** Get the latitude at some index. * @param latitudeIndex latitude index * @return latitude at the specified index + * (latitude of the center of the pixels of specified row) */ double getLatitudeAtIndex(int latitudeIndex); /** Get maximum latitude. * @return maximum latitude + * (latitude of the center of the pixels of North row) */ double getMaximumLatitude(); /** Get minimum longitude. * @return minimum longitude + * (longitude of the center of the pixels of West column) */ double getMinimumLongitude(); /** Get the longitude at some index. * @param longitudeIndex longitude index * @return longitude at the specified index + * (longitude of the center of the pixels of specified column) */ double getLongitudeAtIndex(int longitudeIndex); /** Get maximum longitude. * @return maximum longitude + * (longitude of the center of the pixels of East column) */ double getMaximumLongitude(); @@ -116,13 +128,21 @@ public interface Tile extends UpdatableTile { int getLongitudeColumns(); /** Get the latitude index of a point. + * <p> + * This method shift indices 1/2 pixel, so that + * the specified latitude is always between index and index+1. + * </p> * @param latitude geodetic latitude - * @return latirute index (it may lie outside of the tile!) + * @return latitude index (it may lie outside of the tile!) */ int getLatitudeIndex(double latitude); /** Get the longitude index of a point. - * @param longitude geodetic latitude + * <p> + * This method shift indices 1/2 pixel, so that + * the specified longitude is always between index and index+1. + * </p> + * @param longitude geodetic longitude * @return longitude index (it may lie outside of the tile!) */ int getLongitudeIndex(double longitude); diff --git a/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 b/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 index 587c7eb1baa279cffde6a495a6917daa637cc78d..118da04a8d37f0a9c650aa6711e6f899732802c0 100644 --- a/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 +++ b/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_en.utf8 @@ -19,6 +19,9 @@ EMPTY_TILE = tile is empty: {0} ⨉ {1} # unknown sensor {0} UNKNOWN_SENSOR = unknown sensor {0} +# line-of-sight does not reach ground +LINE_OF_SIGHT_DOES_NOT_REACH_GROUND = line-of-sight does not reach ground + # line-of-sight never crosses latitude {0} LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE = line-of-sight never crosses latitude {0} diff --git a/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 b/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 index 825795b4dcba86e9ce285976c93e05e64e79c5e6..dbbd3973ec50be29cb9e2470279e4a56b34b7831 100644 --- a/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 +++ b/core/src/main/resources/assets/org/orekit/rugged/RuggedMessages_fr.utf8 @@ -19,6 +19,9 @@ EMPTY_TILE = la tuile est vide : {0} ⨉ {1} # unknown sensor {0} UNKNOWN_SENSOR = capteur {0} inconnu +# line-of-sight does not reach ground +LINE_OF_SIGHT_DOES_NOT_REACH_GROUND = la ligne de visée n''atteint pas le sol + # line-of-sight never crosses latitude {0} LINE_OF_SIGHT_NEVER_CROSSES_LATITUDE = la ligne de visée ne franchit jamais la latitude {0} diff --git a/core/src/test/java/org/orekit/rugged/api/RuggedMessagesTest.java b/core/src/test/java/org/orekit/rugged/api/RuggedMessagesTest.java index abde2c084c32ffdd4b53e80a323426902e43971d..9b7d06081d59da6d8361c24a531878951235b7cf 100644 --- a/core/src/test/java/org/orekit/rugged/api/RuggedMessagesTest.java +++ b/core/src/test/java/org/orekit/rugged/api/RuggedMessagesTest.java @@ -29,7 +29,7 @@ public class RuggedMessagesTest { @Test public void testMessageNumber() { - Assert.assertEquals(13, RuggedMessages.values().length); + Assert.assertEquals(14, RuggedMessages.values().length); } @Test diff --git a/core/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java b/core/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java index 8c5bc03b4d75610d1896fb26ea52a20fed472b35..ce1e1f9c74c53b606fb4ade18013dd92cd074860 100644 --- a/core/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java +++ b/core/src/test/java/org/orekit/rugged/intersection/duvenhage/DuvenhageAlgorithmTest.java @@ -18,10 +18,12 @@ package org.orekit.rugged.intersection.duvenhage; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; +import org.junit.Assert; import org.junit.Test; import org.orekit.bodies.GeodeticPoint; import org.orekit.errors.OrekitException; import org.orekit.rugged.api.RuggedException; +import org.orekit.rugged.api.RuggedMessages; import org.orekit.rugged.intersection.AbstractAlgorithmTest; import org.orekit.rugged.intersection.IntersectionAlgorithm; import org.orekit.rugged.intersection.duvenhage.DuvenhageAlgorithm; @@ -55,4 +57,18 @@ public class DuvenhageAlgorithmTest extends AbstractAlgorithmTest { checkIntersection(position, los, intersection); } + @Test + public void testWrongPositionMissesGround() throws RuggedException, OrekitException { + setUpMayonVolcanoContext(); + final IntersectionAlgorithm algorithm = createAlgorithm(updater, 8); + Vector3D position = new Vector3D(7.551889113912788E9, -3.173692685491814E10, 1.5727517321541348E9); + Vector3D los = new Vector3D(0.010401349221417867, -0.17836068905951286, 0.9839101973923178); + try { + algorithm.intersection(earth, position, los); + Assert.fail("an exception should have been thrown"); + } catch (RuggedException re) { + Assert.assertEquals(RuggedMessages.LINE_OF_SIGHT_DOES_NOT_REACH_GROUND, re.getSpecifier()); + } + } + } diff --git a/core/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java b/core/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java index a71c1ce171a7b9fb20f183ae4ce879d1020fb108..ac0d4f974b11f3dd96a46359366c75bd6bc82fb6 100644 --- a/core/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java +++ b/core/src/test/java/org/orekit/rugged/raster/SimpleTileTest.java @@ -115,6 +115,42 @@ public class SimpleTileTest { checkOutOfBound( 50, 200, tile); } + @Test + public void testIndexShift() throws RuggedException { + + SimpleTile tile = new SimpleTileFactory().createTile(); + tile.setGeometry(1.0, 2.0, 0.1, 0.2, 100, 200); + tile.setElevation(50, 100, 1000.0); + tile.tileUpdateCompleted(); + + // indices correspond to pixels centers + double latCenterColumn50 = tile.getLatitudeAtIndex(50); + double latCenterColumn51 = tile.getLatitudeAtIndex(51); + double lonCenterRow23 = tile.getLongitudeAtIndex(23); + double lonCenterRow24 = tile.getLongitudeAtIndex(24); + + // getLatitudeIndex shift indices 1/2 pixel, so that + // the specified latitude is always between index and index+1 + // so despite latWestColumn51 is very close to column 51 center, + // getLatitudeIndex should return 50 + double latWestColumn51 = 0.001 * latCenterColumn50 + 0.999 * latCenterColumn51; + int retrievedLatIndex = tile.getLatitudeIndex(latWestColumn51); + Assert.assertEquals(50, retrievedLatIndex); + Assert.assertTrue(tile.getLatitudeAtIndex(retrievedLatIndex) < latWestColumn51); + Assert.assertTrue(latWestColumn51 < tile.getLatitudeAtIndex(retrievedLatIndex + 1)); + + // getLongitudeIndex shift indices 1/2 pixel, so that + // the specified longitude is always between index and index+1 + // so despite lonSouthRow24 is very close to row 24 center, + // getLongitudeIndex should return 23 + double lonSouthRow24 = 0.001 * lonCenterRow23 + 0.999 * lonCenterRow24; + int retrievedLonIndex = tile.getLongitudeIndex(lonSouthRow24); + Assert.assertEquals(23, retrievedLonIndex); + Assert.assertTrue(tile.getLongitudeAtIndex(retrievedLonIndex) < lonSouthRow24); + Assert.assertTrue(lonSouthRow24 < tile.getLongitudeAtIndex(retrievedLonIndex + 1)); + + } + private void checkOutOfBound(int i, int j, Tile tile) { try { tile.setElevation(i, j, 1000.0); diff --git a/core/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java b/core/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java index 620b03fdbd31ab672e6e94fbbd8c18f90b7ed183..cad96cd479813d85881c9f9ea4ddf83b50be490e 100644 --- a/core/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java +++ b/core/src/test/java/org/orekit/rugged/raster/TilesCacheTest.java @@ -77,7 +77,7 @@ public class TilesCacheTest { cache.getTile(FastMath.toRadians(20.5), FastMath.toRadians(30.5)); Assert.assertEquals(14, factory.getCount()); - // evict all the tiles, goind to a completely different zone + // evict all the tiles, going to a completely different zone for (int i = 0; i < 4; ++i) { for (int j = 0; j < 3; ++j) { cache.getTile(FastMath.toRadians(40.5 + i), FastMath.toRadians(90.5 + j)); diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml index 2751dc151d5072dfec39c171d295649752397994..1c42211de3f9d700b20ad53bdd6c6b0cd476bb94 100644 --- a/src/site/xdoc/changes.xml +++ b/src/site/xdoc/changes.xml @@ -22,6 +22,9 @@ <body> <release version="1.0" date="TBD" description="TBD"> + <action dev="luc" type="fix"> + Added detection of wrong position/line-of-sight that misses the ground. + </action> <action dev="luc" type="add"> Added a way to reuse transform interpolator from one run to another by dumping its state into a file, thus avoiding costly initialization.