1   /* Copyright 2013-2019 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.raster;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.util.FastMath;
21  import org.hipparchus.util.Precision;
22  import java.util.Arrays;
23  
24  import org.orekit.bodies.GeodeticPoint;
25  import org.orekit.rugged.errors.DumpManager;
26  import org.orekit.rugged.errors.RuggedException;
27  import org.orekit.rugged.errors.RuggedMessages;
28  import org.orekit.rugged.utils.MaxSelector;
29  import org.orekit.rugged.utils.MinSelector;
30  import org.orekit.rugged.utils.NormalizedGeodeticPoint;
31  
32  
33  /** Simple implementation of a {@link Tile}.
34   * @see SimpleTileFactory
35   * @author Luc Maisonobe
36   * @author Guylaine Prat
37   */
38  public class SimpleTile implements Tile {
39  
40      /** Tolerance used to interpolate points slightly out of tile (in cells). */
41      private static final double TOLERANCE = 1.0 / 8.0;
42  
43      /** Minimum latitude. */
44      private double minLatitude;
45  
46      /** Minimum longitude. */
47      private double minLongitude;
48  
49      /** Step in latitude (size of one raster element). */
50      private double latitudeStep;
51  
52      /** Step in longitude (size of one raster element). */
53      private double longitudeStep;
54  
55      /** Number of latitude rows. */
56      private int latitudeRows;
57  
58      /** Number of longitude columns. */
59      private int longitudeColumns;
60  
61      /** Minimum elevation. */
62      private double minElevation;
63  
64      /** Latitude index of min elevation. */
65      private int minElevationLatitudeIndex;
66  
67      /** Longitude index of min elevation. */
68      private int minElevationLongitudeIndex;
69  
70      /** Maximum elevation. */
71      private double maxElevation;
72  
73      /** Latitude index of max elevation. */
74      private int maxElevationLatitudeIndex;
75  
76      /** Longitude index of max elevation. */
77      private int maxElevationLongitudeIndex;
78  
79      /** Elevation array. */
80      private double[] elevations;
81  
82      /** Simple constructor.
83       * <p>
84       * Creates an empty tile.
85       * </p>
86       */
87      protected SimpleTile() {
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public void setGeometry(final double newMinLatitude, final double newMinLongitude,
93                              final double newLatitudeStep, final double newLongitudeStep,
94                              final int newLatitudeRows, final int newLongitudeColumns) {
95          this.minLatitude                = newMinLatitude;
96          this.minLongitude               = newMinLongitude;
97          this.latitudeStep               = newLatitudeStep;
98          this.longitudeStep              = newLongitudeStep;
99          this.latitudeRows               = newLatitudeRows;
100         this.longitudeColumns           = newLongitudeColumns;
101         this.minElevation               = Double.POSITIVE_INFINITY;
102         this.minElevationLatitudeIndex  = -1;
103         this.minElevationLongitudeIndex = -1;
104         this.maxElevation               = Double.NEGATIVE_INFINITY;
105         this.maxElevationLatitudeIndex  = -1;
106         this.maxElevationLongitudeIndex = -1;
107 
108         if (newLatitudeRows < 1 || newLongitudeColumns < 1) {
109             throw new RuggedException(RuggedMessages.EMPTY_TILE, newLatitudeRows, newLongitudeColumns);
110         }
111         this.elevations = new double[newLatitudeRows * newLongitudeColumns];
112         Arrays.fill(elevations, Double.NaN);
113 
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     public void tileUpdateCompleted() {
119         processUpdatedElevation(elevations);
120     }
121 
122     /** Process elevation array at completion.
123      * <p>
124      * This method is called at tile update completion, it is
125      * expected to be overridden by subclasses. The default
126      * implementation does nothing.
127      * </p>
128      * @param elevationsArray elevations array
129      */
130     protected void processUpdatedElevation(final double[] elevationsArray) {
131         // do nothing by default
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public double getMinimumLatitude() {
137         return getLatitudeAtIndex(0);
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public double getLatitudeAtIndex(final int latitudeIndex) {
143         return minLatitude + latitudeStep * latitudeIndex;
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public double getMaximumLatitude() {
149         return getLatitudeAtIndex(latitudeRows - 1);
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public double getMinimumLongitude() {
155         return getLongitudeAtIndex(0);
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public double getLongitudeAtIndex(final int longitudeIndex) {
161         return minLongitude + longitudeStep * longitudeIndex;
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public double getMaximumLongitude() {
167         return getLongitudeAtIndex(longitudeColumns - 1);
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public double getLatitudeStep() {
173         return latitudeStep;
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public double getLongitudeStep() {
179         return longitudeStep;
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     public int getLatitudeRows() {
185         return latitudeRows;
186     }
187 
188     /** {@inheritDoc} */
189     @Override
190     public int getLongitudeColumns() {
191         return longitudeColumns;
192     }
193 
194     /** {@inheritDoc} */
195     @Override
196     public double getMinElevation() {
197         return minElevation;
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public int getMinElevationLatitudeIndex() {
203         return minElevationLatitudeIndex;
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public int getMinElevationLongitudeIndex() {
209         return minElevationLongitudeIndex;
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     public double getMaxElevation() {
215         return maxElevation;
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public int getMaxElevationLatitudeIndex() {
221         return maxElevationLatitudeIndex;
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public int getMaxElevationLongitudeIndex() {
227         return maxElevationLongitudeIndex;
228     }
229 
230     /** {@inheritDoc} */
231     @Override
232     public void setElevation(final int latitudeIndex, final int longitudeIndex, final double elevation) {
233 
234         if (latitudeIndex  < 0 || latitudeIndex  > (latitudeRows - 1) ||
235             longitudeIndex < 0 || longitudeIndex > (longitudeColumns - 1)) {
236             throw new RuggedException(RuggedMessages.OUT_OF_TILE_INDICES,
237                                       latitudeIndex, longitudeIndex,
238                                       latitudeRows - 1, longitudeColumns - 1);
239         }
240         if (MinSelector.getInstance().selectFirst(elevation, minElevation)) {
241             minElevation               = elevation;
242             minElevationLatitudeIndex  = latitudeIndex;
243             minElevationLongitudeIndex = longitudeIndex;
244         }
245         if (MaxSelector.getInstance().selectFirst(elevation, maxElevation)) {
246             maxElevation               = elevation;
247             maxElevationLatitudeIndex  = latitudeIndex;
248             maxElevationLongitudeIndex = longitudeIndex;
249         }
250         elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex] = elevation;
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public double getElevationAtIndices(final int latitudeIndex, final int longitudeIndex) {
256         final double elevation = elevations[latitudeIndex * getLongitudeColumns() + longitudeIndex];
257         DumpManager.dumpTileCell(this, latitudeIndex, longitudeIndex, elevation);
258         return elevation;
259     }
260 
261     /** {@inheritDoc}
262      * <p>
263      * This classes uses an arbitrary 1/8 cell tolerance for interpolating
264      * slightly out of tile points.
265      * </p>
266      */
267     @Override
268     public double interpolateElevation(final double latitude, final double longitude) {
269 
270         final double doubleLatitudeIndex  = getDoubleLatitudeIndex(latitude);
271         final double doubleLongitudeIndex = getDoubleLontitudeIndex(longitude);
272         if (doubleLatitudeIndex  < -TOLERANCE || doubleLatitudeIndex  >= (latitudeRows - 1 + TOLERANCE) ||
273             doubleLongitudeIndex < -TOLERANCE || doubleLongitudeIndex >= (longitudeColumns - 1 + TOLERANCE)) {
274             throw new RuggedException(RuggedMessages.OUT_OF_TILE_ANGLES,
275                                       FastMath.toDegrees(latitude),
276                                       FastMath.toDegrees(longitude),
277                                       FastMath.toDegrees(getMinimumLatitude()),
278                                       FastMath.toDegrees(getMaximumLatitude()),
279                                       FastMath.toDegrees(getMinimumLongitude()),
280                                       FastMath.toDegrees(getMaximumLongitude()));
281         }
282 
283         final int latitudeIndex  = FastMath.max(0,
284                                                 FastMath.min(latitudeRows - 2,
285                                                              (int) FastMath.floor(doubleLatitudeIndex)));
286         final int longitudeIndex = FastMath.max(0,
287                                                 FastMath.min(longitudeColumns - 2,
288                                                              (int) FastMath.floor(doubleLongitudeIndex)));
289 
290         // bi-linear interpolation
291         final double dLat = doubleLatitudeIndex  - latitudeIndex;
292         final double dLon = doubleLongitudeIndex - longitudeIndex;
293         final double e00  = getElevationAtIndices(latitudeIndex,     longitudeIndex);
294         final double e10  = getElevationAtIndices(latitudeIndex,     longitudeIndex + 1);
295         final double e01  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex);
296         final double e11  = getElevationAtIndices(latitudeIndex + 1, longitudeIndex + 1);
297 
298         return (e00 * (1.0 - dLon) + dLon * e10) * (1.0 - dLat) +
299                (e01 * (1.0 - dLon) + dLon * e11) * dLat;
300 
301     }
302 
303     /** {@inheritDoc} */
304     @Override
305     public NormalizedGeodeticPoint cellIntersection(final GeodeticPoint p, final Vector3D los,
306                                                     final int latitudeIndex, final int longitudeIndex) {
307 
308         // ensure neighboring cells to not fall out of tile
309         final int iLat  = FastMath.max(0, FastMath.min(latitudeRows     - 2, latitudeIndex));
310         final int jLong = FastMath.max(0, FastMath.min(longitudeColumns - 2, longitudeIndex));
311 
312         // Digital Elevation Mode coordinates at cell vertices
313         final double x00 = getLongitudeAtIndex(jLong);
314         final double y00 = getLatitudeAtIndex(iLat);
315         final double z00 = getElevationAtIndices(iLat,     jLong);
316         final double z01 = getElevationAtIndices(iLat + 1, jLong);
317         final double z10 = getElevationAtIndices(iLat,     jLong + 1);
318         final double z11 = getElevationAtIndices(iLat + 1, jLong + 1);
319 
320         // line-of-sight coordinates at close points
321         final double dxA = (p.getLongitude() - x00) / longitudeStep;
322         final double dyA = (p.getLatitude()  - y00) / latitudeStep;
323         final double dzA = p.getAltitude();
324         final double dxB = dxA + los.getX() / longitudeStep;
325         final double dyB = dyA + los.getY() / latitudeStep;
326         final double dzB = dzA + los.getZ();
327 
328         // points along line-of-sight can be defined as a linear progression
329         // along the line depending on free variable t: p(t) = p + t * los.
330         // As the point latitude and longitude are linear with respect to t,
331         // and as Digital Elevation Model is quadratic with respect to latitude
332         // and longitude, the altitude of DEM at same horizontal position as
333         // point is quadratic in t:
334         // z_DEM(t) = u t² + v t + w
335         final double u = (dxA - dxB) * (dyA - dyB) * (z00 - z10 - z01 + z11);
336         final double v = ((dxA - dxB) * (1 - dyA) + (dyA - dyB) * (1 - dxA)) * z00 +
337                          (dxA * (dyA - dyB) - (dxA - dxB) * (1 - dyA)) * z10 +
338                          (dyA * (dxA - dxB) - (dyA - dyB) * (1 - dxA)) * z01 +
339                          ((dxB - dxA) * dyA + (dyB - dyA) * dxA) * z11;
340         final double w = (1 - dxA) * ((1 - dyA) * z00 + dyA * z01) +
341                          dxA       * ((1 - dyA) * z10 + dyA * z11);
342 
343         // subtract linear z from line-of-sight
344         // z_DEM(t) - z_LOS(t) = a t² + b t + c
345         final double a = u;
346         final double b = v + dzA - dzB;
347         final double c = w - dzA;
348 
349         // solve the equation
350         final double t1;
351         final double t2;
352         if (FastMath.abs(a) <= Precision.EPSILON * FastMath.abs(c)) {
353             // the equation degenerates to a linear (or constant) equation
354             final double t = -c / b;
355             t1 = Double.isNaN(t) ? 0.0 : t;
356             t2 = Double.POSITIVE_INFINITY;
357         } else {
358             // the equation is quadratic
359             final double b2  = b * b;
360             final double fac = 4 * a * c;
361             if (b2 < fac) {
362                 // no intersection at all
363                 return null;
364             }
365             final double s = FastMath.sqrt(b2 - fac);
366             t1 = (b < 0) ? (s - b) / (2 * a) : -2 * c / (b + s);
367             t2 = c / (a * t1);
368 
369         }
370 
371         final NormalizedGeodeticPoint p1 = interpolate(t1, p, dxA, dyA, los, x00);
372         final NormalizedGeodeticPoint p2 = interpolate(t2, p, dxA, dyA, los, x00);
373 
374         // select the first point along line-of-sight
375         if (p1 == null) {
376             return p2;
377         } else if (p2 == null) {
378             return p1;
379         } else {
380             return t1 <= t2 ? p1 : p2;
381         }
382 
383     }
384 
385     /** Interpolate point along a line.
386      * @param t abscissa along the line
387      * @param p start point
388      * @param dxP relative coordinate of the start point with respect to current cell
389      * @param dyP relative coordinate of the start point with respect to current cell
390      * @param los direction of the line-of-sight, in geodetic space
391      * @param centralLongitude reference longitude lc such that the point longitude will
392      * be normalized between lc-π and lc+π
393      * @return interpolated point along the line
394      */
395     private NormalizedGeodeticPoint interpolate(final double t, final GeodeticPoint p,
396                                                 final double dxP, final double dyP,
397                                                 final Vector3D los, final double centralLongitude) {
398 
399         if (Double.isInfinite(t)) {
400             return null;
401         }
402 
403         final double dx = dxP + t * los.getX() / longitudeStep;
404         final double dy = dyP + t * los.getY() / latitudeStep;
405         if (dx >= -TOLERANCE && dx <= 1 + TOLERANCE && dy >= -TOLERANCE && dy <= 1 + TOLERANCE) {
406             return new NormalizedGeodeticPoint(p.getLatitude()  + t * los.getY(),
407                                                p.getLongitude() + t * los.getX(),
408                                                p.getAltitude()  + t * los.getZ(),
409                                                centralLongitude);
410         } else {
411             return null;
412         }
413 
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     public int getFloorLatitudeIndex(final double latitude) {
419         return (int) FastMath.floor(getDoubleLatitudeIndex(latitude));
420     }
421 
422     /** {@inheritDoc} */
423     @Override
424     public int getFloorLongitudeIndex(final double longitude) {
425         return (int) FastMath.floor(getDoubleLontitudeIndex(longitude));
426     }
427 
428     /** Get the latitude index of a point.
429      * @param latitude geodetic latitude (rad)
430      * @return latitude index (it may lie outside of the tile!)
431      */
432     private double getDoubleLatitudeIndex(final double latitude) {
433         return (latitude  - minLatitude)  / latitudeStep;
434     }
435 
436     /** Get the longitude index of a point.
437      * @param longitude geodetic longitude (rad)
438      * @return longitude index (it may lie outside of the tile!)
439      */
440     private double getDoubleLontitudeIndex(final double longitude) {
441         return (longitude - minLongitude) / longitudeStep;
442     }
443 
444     /** {@inheritDoc} */
445     @Override
446     public Location getLocation(final double latitude, final double longitude) {
447         final int latitudeIndex  = getFloorLatitudeIndex(latitude);
448         final int longitudeIndex = getFloorLongitudeIndex(longitude);
449         if (longitudeIndex < 0) {
450             if (latitudeIndex < 0) {
451                 return Location.SOUTH_WEST;
452             } else if (latitudeIndex <= (latitudeRows - 2)) {
453                 return Location.WEST;
454             } else {
455                 return Location.NORTH_WEST;
456             }
457         } else if (longitudeIndex <= (longitudeColumns - 2)) {
458             if (latitudeIndex < 0) {
459                 return Location.SOUTH;
460             } else if (latitudeIndex <= (latitudeRows - 2)) {
461                 return Location.HAS_INTERPOLATION_NEIGHBORS;
462             } else {
463                 return Location.NORTH;
464             }
465         } else {
466             if (latitudeIndex < 0) {
467                 return Location.SOUTH_EAST;
468             } else if (latitudeIndex <= (latitudeRows - 2)) {
469                 return Location.EAST;
470             } else {
471                 return Location.NORTH_EAST;
472             }
473         }
474     }
475 
476 }