SimpleTile.java

  1. /* Copyright 2013-2022 CS GROUP
  2.  * Licensed to CS GROUP (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.raster;

  18. import java.util.Arrays;

  19. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  20. import org.hipparchus.util.FastMath;
  21. import org.hipparchus.util.Precision;
  22. import org.orekit.rugged.errors.DumpManager;
  23. import org.orekit.rugged.errors.RuggedException;
  24. import org.orekit.rugged.errors.RuggedMessages;
  25. import org.orekit.rugged.utils.MaxSelector;
  26. import org.orekit.rugged.utils.MinSelector;
  27. import org.orekit.rugged.utils.NormalizedGeodeticPoint;


  28. /** Simple implementation of a {@link Tile}.
  29.  * @see SimpleTileFactory
  30.  * @author Luc Maisonobe
  31.  * @author Guylaine Prat
  32.  */
  33. public class SimpleTile implements Tile {

  34.     /** Tolerance used to interpolate points slightly out of tile (in cells). */
  35.     private static final double TOLERANCE = 1.0 / 8.0;

  36.     /** Minimum latitude. */
  37.     private double minLatitude;

  38.     /** Minimum longitude. */
  39.     private double minLongitude;

  40.     /** Step in latitude (size of one raster element). */
  41.     private double latitudeStep;

  42.     /** Step in longitude (size of one raster element). */
  43.     private double longitudeStep;

  44.     /** Number of latitude rows. */
  45.     private int latitudeRows;

  46.     /** Number of longitude columns. */
  47.     private int longitudeColumns;

  48.     /** Minimum elevation. */
  49.     private double minElevation;

  50.     /** Latitude index of min elevation. */
  51.     private int minElevationLatitudeIndex;

  52.     /** Longitude index of min elevation. */
  53.     private int minElevationLongitudeIndex;

  54.     /** Maximum elevation. */
  55.     private double maxElevation;

  56.     /** Latitude index of max elevation. */
  57.     private int maxElevationLatitudeIndex;

  58.     /** Longitude index of max elevation. */
  59.     private int maxElevationLongitudeIndex;

  60.     /** Elevation array. */
  61.     private double[] elevations;

  62.     /** Simple constructor.
  63.      * <p>
  64.      * Creates an empty tile.
  65.      * </p>
  66.      */
  67.     protected SimpleTile() {
  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     public void setGeometry(final double newMinLatitude, final double newMinLongitude,
  72.                             final double newLatitudeStep, final double newLongitudeStep,
  73.                             final int newLatitudeRows, final int newLongitudeColumns) {
  74.         this.minLatitude                = newMinLatitude;
  75.         this.minLongitude               = newMinLongitude;
  76.         this.latitudeStep               = newLatitudeStep;
  77.         this.longitudeStep              = newLongitudeStep;
  78.         this.latitudeRows               = newLatitudeRows;
  79.         this.longitudeColumns           = newLongitudeColumns;
  80.         this.minElevation               = Double.POSITIVE_INFINITY;
  81.         this.minElevationLatitudeIndex  = -1;
  82.         this.minElevationLongitudeIndex = -1;
  83.         this.maxElevation               = Double.NEGATIVE_INFINITY;
  84.         this.maxElevationLatitudeIndex  = -1;
  85.         this.maxElevationLongitudeIndex = -1;

  86.         if (newLatitudeRows < 1 || newLongitudeColumns < 1) {
  87.             throw new RuggedException(RuggedMessages.EMPTY_TILE, newLatitudeRows, newLongitudeColumns);
  88.         }
  89.         this.elevations = new double[newLatitudeRows * newLongitudeColumns];
  90.         Arrays.fill(elevations, Double.NaN);

  91.     }

  92.     /** {@inheritDoc} */
  93.     @Override
  94.     public void tileUpdateCompleted() {
  95.         processUpdatedElevation(elevations);
  96.     }

  97.     /** Process elevation array at completion.
  98.      * <p>
  99.      * This method is called at tile update completion, it is
  100.      * expected to be overridden by subclasses. The default
  101.      * implementation does nothing.
  102.      * </p>
  103.      * @param elevationsArray elevations array
  104.      */
  105.     protected void processUpdatedElevation(final double[] elevationsArray) {
  106.         // do nothing by default
  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     public double getMinimumLatitude() {
  111.         return getLatitudeAtIndex(0);
  112.     }

  113.     /** {@inheritDoc} */
  114.     @Override
  115.     public double getLatitudeAtIndex(final int latitudeIndex) {
  116.         return minLatitude + latitudeStep * latitudeIndex;
  117.     }

  118.     /** {@inheritDoc} */
  119.     @Override
  120.     public double getMaximumLatitude() {
  121.         return getLatitudeAtIndex(latitudeRows - 1);
  122.     }

  123.     /** {@inheritDoc} */
  124.     @Override
  125.     public double getMinimumLongitude() {
  126.         return getLongitudeAtIndex(0);
  127.     }

  128.     /** {@inheritDoc} */
  129.     @Override
  130.     public double getLongitudeAtIndex(final int longitudeIndex) {
  131.         return minLongitude + longitudeStep * longitudeIndex;
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     public double getMaximumLongitude() {
  136.         return getLongitudeAtIndex(longitudeColumns - 1);
  137.     }

  138.     /** {@inheritDoc} */
  139.     @Override
  140.     public double getLatitudeStep() {
  141.         return latitudeStep;
  142.     }

  143.     /** {@inheritDoc} */
  144.     @Override
  145.     public double getLongitudeStep() {
  146.         return longitudeStep;
  147.     }

  148.     /** {@inheritDoc} */
  149.     @Override
  150.     public int getLatitudeRows() {
  151.         return latitudeRows;
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public int getLongitudeColumns() {
  156.         return longitudeColumns;
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public double getMinElevation() {
  161.         return minElevation;
  162.     }

  163.     /** {@inheritDoc} */
  164.     @Override
  165.     public int getMinElevationLatitudeIndex() {
  166.         return minElevationLatitudeIndex;
  167.     }

  168.     /** {@inheritDoc} */
  169.     @Override
  170.     public int getMinElevationLongitudeIndex() {
  171.         return minElevationLongitudeIndex;
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public double getMaxElevation() {
  176.         return maxElevation;
  177.     }

  178.     /** {@inheritDoc} */
  179.     @Override
  180.     public int getMaxElevationLatitudeIndex() {
  181.         return maxElevationLatitudeIndex;
  182.     }

  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public int getMaxElevationLongitudeIndex() {
  186.         return maxElevationLongitudeIndex;
  187.     }

  188.     /** {@inheritDoc} */
  189.     @Override
  190.     public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {

  191.         if (latitudeIndex  < 0 || latitudeIndex  > (latitudeRows - 1) ||
  192.             longitudeIndex < 0 || longitudeIndex > (longitudeColumns - 1)) {
  193.             throw new RuggedException(RuggedMessages.OUT_OF_TILE_INDICES,
  194.                                       latitudeIndex, longitudeIndex,
  195.                                       latitudeRows - 1, longitudeColumns - 1);
  196.         }
  197.         if (MinSelector.getInstance().selectFirst(elevation, minElevation)) {
  198.             minElevation               = elevation;
  199.             minElevationLatitudeIndex  = latitudeIndex;
  200.             minElevationLongitudeIndex = longitudeIndex;
  201.         }
  202.         if (MaxSelector.getInstance().selectFirst(elevation, maxElevation)) {
  203.             maxElevation               = elevation;
  204.             maxElevationLatitudeIndex  = latitudeIndex;
  205.             maxElevationLongitudeIndex = longitudeIndex;
  206.         }
  207.         elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex] = elevation;
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public double getElevationAtIndices(final int latitudeIndex, final int longitudeIndex) {
  212.         final double elevation = elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex];
  213.         DumpManager.dumpTileCell(this, latitudeIndex, longitudeIndex, elevation);
  214.         return elevation;
  215.     }

  216.     /** {@inheritDoc}
  217.      * <p>
  218.      * This classes uses an arbitrary 1/8 cell tolerance for interpolating
  219.      * slightly out of tile points.
  220.      * </p>
  221.      */
  222.     @Override
  223.     public double interpolateElevation(final double latitude, final double longitude) {

  224.         final double doubleLatitudeIndex  = getDoubleLatitudeIndex(latitude);
  225.         final double doubleLongitudeIndex = getDoubleLontitudeIndex(longitude);
  226.         if (doubleLatitudeIndex  < -TOLERANCE || doubleLatitudeIndex  >= (latitudeRows - 1 + TOLERANCE) ||
  227.             doubleLongitudeIndex < -TOLERANCE || doubleLongitudeIndex >= (longitudeColumns - 1 + TOLERANCE)) {
  228.             throw new RuggedException(RuggedMessages.OUT_OF_TILE_ANGLES,
  229.                                       FastMath.toDegrees(latitude),
  230.                                       FastMath.toDegrees(longitude),
  231.                                       FastMath.toDegrees(getMinimumLatitude()),
  232.                                       FastMath.toDegrees(getMaximumLatitude()),
  233.                                       FastMath.toDegrees(getMinimumLongitude()),
  234.                                       FastMath.toDegrees(getMaximumLongitude()));
  235.         }

  236.         final int latitudeIndex  = FastMath.max(0,
  237.                                                 FastMath.min(latitudeRows - 2,
  238.                                                              (int) FastMath.floor(doubleLatitudeIndex)));
  239.         final int longitudeIndex = FastMath.max(0,
  240.                                                 FastMath.min(longitudeColumns - 2,
  241.                                                              (int) FastMath.floor(doubleLongitudeIndex)));

  242.         // bi-linear interpolation
  243.         final double dLat = doubleLatitudeIndex  - latitudeIndex;
  244.         final double dLon = doubleLongitudeIndex - longitudeIndex;
  245.         final double e00  = getElevationAtIndices(latitudeIndex,     longitudeIndex);
  246.         final double e10  = getElevationAtIndices(latitudeIndex,     longitudeIndex + 1);
  247.         final double e01  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex);
  248.         final double e11  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex + 1);

  249.         return (e00 * (1.0 - dLon) + dLon * e10) * (1.0 - dLat) +
  250.                (e01 * (1.0 - dLon) + dLon * e11) * dLat;

  251.     }

  252.     /** {@inheritDoc} */
  253.     @Override
  254.     public NormalizedGeodeticPoint cellIntersection(final NormalizedGeodeticPoint p, final Vector3D los,
  255.                                                     final int latitudeIndex, final int longitudeIndex) {

  256.         // ensure neighboring cells to not fall out of tile
  257.         final int iLat  = FastMath.max(0, FastMath.min(latitudeRows     - 2, latitudeIndex));
  258.         final int jLong = FastMath.max(0, FastMath.min(longitudeColumns - 2, longitudeIndex));

  259.         // Digital Elevation Mode coordinates at cell vertices
  260.         final double x00 = getLongitudeAtIndex(jLong);
  261.         final double y00 = getLatitudeAtIndex(iLat);
  262.         final double z00 = getElevationAtIndices(iLat,     jLong);
  263.         final double z01 = getElevationAtIndices(iLat + 1, jLong);
  264.         final double z10 = getElevationAtIndices(iLat,     jLong + 1);
  265.         final double z11 = getElevationAtIndices(iLat + 1, jLong + 1);

  266.         // normalize back to tile coordinates
  267.         final NormalizedGeodeticPoint tileP = new NormalizedGeodeticPoint(p.getLatitude(),
  268.                                                                           p.getLongitude(),
  269.                                                                           p.getAltitude(),
  270.                                                                           x00);

  271.         // line-of-sight coordinates at close points
  272.         final double dxA = (tileP.getLongitude() - x00) / longitudeStep;
  273.         final double dyA = (tileP.getLatitude()  - y00) / latitudeStep;
  274.         final double dzA = tileP.getAltitude();
  275.         final double dxB = dxA + los.getX() / longitudeStep;
  276.         final double dyB = dyA + los.getY() / latitudeStep;
  277.         final double dzB = dzA + los.getZ();

  278.         // points along line-of-sight can be defined as a linear progression
  279.         // along the line depending on free variable t: p(t) = p + t * los.
  280.         // As the point latitude and longitude are linear with respect to t,
  281.         // and as Digital Elevation Model is quadratic with respect to latitude
  282.         // and longitude, the altitude of DEM at same horizontal position as
  283.         // point is quadratic in t:
  284.         // z_DEM(t) = u t² + v t + w
  285.         final double u = (dxA - dxB) * (dyA - dyB) * (z00 - z10 - z01 + z11);
  286.         final double v = ((dxA - dxB) * (1 - dyA) + (dyA - dyB) * (1 - dxA)) * z00 +
  287.                          (dxA * (dyA - dyB) - (dxA - dxB) * (1 - dyA)) * z10 +
  288.                          (dyA * (dxA - dxB) - (dyA - dyB) * (1 - dxA)) * z01 +
  289.                          ((dxB - dxA) * dyA + (dyB - dyA) * dxA) * z11;
  290.         final double w = (1 - dxA) * ((1 - dyA) * z00 + dyA * z01) +
  291.                          dxA       * ((1 - dyA) * z10 + dyA * z11);

  292.         // subtract linear z from line-of-sight
  293.         // z_DEM(t) - z_LOS(t) = a t² + b t + c
  294.         final double a = u;
  295.         final double b = v + dzA - dzB;
  296.         final double c = w - dzA;

  297.         // solve the equation
  298.         final double t1;
  299.         final double t2;
  300.         if (FastMath.abs(a) <= Precision.EPSILON * FastMath.abs(c)) {
  301.             // the equation degenerates to a linear (or constant) equation
  302.             final double t = -c / b;
  303.             t1 = Double.isNaN(t) ? 0.0 : t;
  304.             t2 = Double.POSITIVE_INFINITY;
  305.         } else {
  306.             // the equation is quadratic
  307.             final double b2  = b * b;
  308.             final double fac = 4 * a * c;
  309.             if (b2 < fac) {
  310.                 // no intersection at all
  311.                 return null;
  312.             }
  313.             final double s = FastMath.sqrt(b2 - fac);
  314.             t1 = (b < 0) ? (s - b) / (2 * a) : -2 * c / (b + s);
  315.             t2 = c / (a * t1);

  316.         }

  317.         final NormalizedGeodeticPoint p1 = interpolate(t1, tileP, dxA, dyA, los, x00);
  318.         final NormalizedGeodeticPoint p2 = interpolate(t2, tileP, dxA, dyA, los, x00);

  319.         // select the first point along line-of-sight
  320.         if (p1 == null) {
  321.             return p2;
  322.         } else if (p2 == null) {
  323.             return p1;
  324.         } else {
  325.             return t1 <= t2 ? p1 : p2;
  326.         }

  327.     }

  328.     /** Interpolate point along a line.
  329.      * @param t abscissa along the line
  330.      * @param tileP start point, normalized to tile area
  331.      * @param dxP relative coordinate of the start point with respect to current cell
  332.      * @param dyP relative coordinate of the start point with respect to current cell
  333.      * @param los direction of the line-of-sight, in geodetic space
  334.      * @param centralLongitude reference longitude lc such that the point longitude will
  335.      * be normalized between lc-π and lc+π
  336.      * @return interpolated point along the line
  337.      */
  338.     private NormalizedGeodeticPoint interpolate(final double t, final NormalizedGeodeticPoint tileP,
  339.                                                 final double dxP, final double dyP,
  340.                                                 final Vector3D los, final double centralLongitude) {

  341.         if (Double.isInfinite(t)) {
  342.             return null;
  343.         }

  344.         final double dx = dxP + t * los.getX() / longitudeStep;
  345.         final double dy = dyP + t * los.getY() / latitudeStep;
  346.         if (dx >= -TOLERANCE && dx <= 1 + TOLERANCE && dy >= -TOLERANCE && dy <= 1 + TOLERANCE) {
  347.             return new NormalizedGeodeticPoint(tileP.getLatitude()  + t * los.getY(),
  348.                                                tileP.getLongitude() + t * los.getX(),
  349.                                                tileP.getAltitude()  + t * los.getZ(),
  350.                                                centralLongitude);
  351.         } else {
  352.             return null;
  353.         }

  354.     }

  355.     /** {@inheritDoc} */
  356.     @Override
  357.     public int getFloorLatitudeIndex(final double latitude) {
  358.         return (int) FastMath.floor(getDoubleLatitudeIndex(latitude));
  359.     }

  360.     /** {@inheritDoc} */
  361.     @Override
  362.     public int getFloorLongitudeIndex(final double longitude) {
  363.         return (int) FastMath.floor(getDoubleLontitudeIndex(longitude));
  364.     }

  365.     /** Get the latitude index of a point.
  366.      * @param latitude geodetic latitude (rad)
  367.      * @return latitude index (it may lie outside of the tile!)
  368.      */
  369.     private double getDoubleLatitudeIndex(final double latitude) {
  370.         return (latitude  - minLatitude)  / latitudeStep;
  371.     }

  372.     /** Get the longitude index of a point.
  373.      * @param longitude geodetic longitude (rad)
  374.      * @return longitude index (it may lie outside of the tile!)
  375.      */
  376.     private double getDoubleLontitudeIndex(final double longitude) {
  377.         return (longitude - minLongitude) / longitudeStep;
  378.     }

  379.     /** {@inheritDoc} */
  380.     @Override
  381.     public Location getLocation(final double latitude, final double longitude) {
  382.         final int latitudeIndex  = getFloorLatitudeIndex(latitude);
  383.         final int longitudeIndex = getFloorLongitudeIndex(longitude);
  384.         if (longitudeIndex < 0) {
  385.             if (latitudeIndex < 0) {
  386.                 return Location.SOUTH_WEST;
  387.             } else if (latitudeIndex <= (latitudeRows - 2)) {
  388.                 return Location.WEST;
  389.             } else {
  390.                 return Location.NORTH_WEST;
  391.             }
  392.         } else if (longitudeIndex <= (longitudeColumns - 2)) {
  393.             if (latitudeIndex < 0) {
  394.                 return Location.SOUTH;
  395.             } else if (latitudeIndex <= (latitudeRows - 2)) {
  396.                 return Location.HAS_INTERPOLATION_NEIGHBORS;
  397.             } else {
  398.                 return Location.NORTH;
  399.             }
  400.         } else {
  401.             if (latitudeIndex < 0) {
  402.                 return Location.SOUTH_EAST;
  403.             } else if (latitudeIndex <= (latitudeRows - 2)) {
  404.                 return Location.EAST;
  405.             } else {
  406.                 return Location.NORTH_EAST;
  407.             }
  408.         }
  409.     }

  410. }