1   /* Copyright 2013-2025 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  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  
22  import java.io.FileNotFoundException;
23  import java.io.UnsupportedEncodingException;
24  import java.lang.reflect.Field;
25  import java.lang.reflect.Modifier;
26  import java.net.URISyntaxException;
27  import java.util.Map;
28  
29  import org.hipparchus.random.RandomGenerator;
30  import org.hipparchus.random.Well19937a;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.MathUtils;
33  import org.junit.jupiter.api.Assertions;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * @author Luc Maisonobe
38   * @author Guylaine Prat
39   */
40  public class TilesCacheTest {
41  
42      @Test
43      public void testSingleTile() {
44          CountingFactory factory = new CountingFactory();
45          TilesCache<SimpleTile> cache = new TilesCache<SimpleTile>(factory,
46                  new CheckedPatternElevationUpdater(FastMath.toRadians(3.0), 11, 10.0, 20.0), 1000,true);
47          SimpleTile tile = cache.getTile(FastMath.toRadians(-23.2), FastMath.toRadians(137.5));
48          Assertions.assertEquals(1, factory.getCount());
49          Assertions.assertEquals(-24.0, FastMath.toDegrees(tile.getMinimumLatitude()),  1.0e-10);
50          Assertions.assertEquals(135.0, FastMath.toDegrees(tile.getMinimumLongitude()), 1.0e-10);
51          Assertions.assertEquals(  0.3, FastMath.toDegrees(tile.getLatitudeStep()),     1.0e-10);
52          Assertions.assertEquals(  0.3, FastMath.toDegrees(tile.getLongitudeStep()),    1.0e-10);
53          Assertions.assertEquals(10.0, tile.getMinElevation(), 1.0e-10);
54          Assertions.assertEquals(20.0, tile.getMaxElevation(), 1.0e-10);
55      }
56  
57      @Test
58      public void testEviction() {
59          CountingFactory factory = new CountingFactory();
60          TilesCache<SimpleTile> cache = new TilesCache<SimpleTile>(factory,
61                  new CheckedPatternElevationUpdater(FastMath.toRadians(1.0), 11, 10.0, 20.0), 12, true);
62  
63          // fill up the 12 tiles we can keep in cache
64          for (int i = 0; i < 4; ++i) {
65              for (int j = 0; j < 3; ++j) {
66                  cache.getTile(FastMath.toRadians(0.5 + j), FastMath.toRadians(0.5 + i));
67              }
68          }
69          Assertions.assertEquals(12, factory.getCount());
70  
71          // keep using the same tiles for a while
72          RandomGenerator generator = new Well19937a(0xf556baa5977435c5l);
73          for (int i = 0; i < 10000; ++i) {
74              double lat = 3.0 * generator.nextDouble();
75              double lon = 4.0 * generator.nextDouble();
76              cache.getTile(FastMath.toRadians(lat), FastMath.toRadians(lon));
77          }
78          Assertions.assertEquals(12, factory.getCount());
79  
80          // ensure the (0.0, 0.0) tile is the least recently used one
81          for (int i = 0; i < 4; ++i) {
82              for (int j = 0; j < 3; ++j) {
83                  cache.getTile(FastMath.toRadians(0.5 + j), FastMath.toRadians(0.5 + i));
84              }
85          }
86  
87          // ask for one point outside of the covered area, to evict the (0.0, 0.0) tile
88          cache.getTile(FastMath.toRadians(20.5), FastMath.toRadians(30.5));
89          Assertions.assertEquals(13, factory.getCount());
90  
91          // ask again for one point in the evicted tile which must be reallocated
92          cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(0.5));
93          Assertions.assertEquals(14, factory.getCount());
94  
95          // the 13th allocated tile should still be there
96          cache.getTile(FastMath.toRadians(20.5), FastMath.toRadians(30.5));
97          Assertions.assertEquals(14, factory.getCount());
98  
99          // evict all the tiles, going to a completely different zone
100         for (int i = 0; i < 4; ++i) {
101             for (int j = 0; j < 3; ++j) {
102                 cache.getTile(FastMath.toRadians(40.5 + i), FastMath.toRadians(90.5 + j));
103             }
104         }
105         Assertions.assertEquals(26, factory.getCount());
106 
107     }
108 
109     @Test
110     public void testExactEnd() {
111         CountingFactory factory = new CountingFactory();
112         TilesCache<SimpleTile> cache =
113                 new TilesCache<SimpleTile>(factory,
114                                            new CheckedPatternElevationUpdater(0.125, 9, 10.0, 20.0),
115                                            12, true);
116 
117         SimpleTile regularTile = cache.getTile(0.2, 0.6);
118         Assertions.assertEquals(1, factory.getCount());
119         Assertions.assertEquals(0.125,    regularTile.getMinimumLatitude(),  1.0e-10);
120         Assertions.assertEquals(0.5,      regularTile.getMinimumLongitude(), 1.0e-10);
121         Assertions.assertEquals(0.015625, regularTile.getLatitudeStep(),     1.0e-10);
122         Assertions.assertEquals(0.015625, regularTile.getLongitudeStep(),    1.0e-10);
123         Assertions.assertEquals(10.0,     regularTile.getMinElevation(),     1.0e-10);
124         Assertions.assertEquals(20.0,     regularTile.getMaxElevation(),     1.0e-10);
125 
126         SimpleTile tileAtEnd = cache.getTile(0.234375, 0.609375);
127         Assertions.assertEquals(1, factory.getCount());
128         Assertions.assertEquals(0.125,    tileAtEnd.getMinimumLatitude(),  1.0e-10);
129         Assertions.assertEquals(0.5,      tileAtEnd.getMinimumLongitude(), 1.0e-10);
130         Assertions.assertEquals(0.015625, tileAtEnd.getLatitudeStep(),     1.0e-10);
131         Assertions.assertEquals(0.015625, tileAtEnd.getLongitudeStep(),    1.0e-10);
132         Assertions.assertEquals(10.0,     tileAtEnd.getMinElevation(),     1.0e-10);
133         Assertions.assertEquals(20.0,     tileAtEnd.getMaxElevation(),     1.0e-10);
134 
135     }
136 
137     @Test
138     public void testNonContiguousFill() {
139         CountingFactory factory = new CountingFactory();
140         TilesCache<SimpleTile> cache =
141                 new TilesCache<SimpleTile>(factory,
142                                            new CheckedPatternElevationUpdater(FastMath.toRadians(1.0), 11, 10.0, 20.0),
143                                            16, true);
144 
145         cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(0.5));
146         cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(2.5));
147         cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(3.5));
148         cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(3.5));
149         cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(3.5));
150         cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(1.5));
151         cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(1.5));
152         cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(1.5));
153         cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(3.5));
154         cache.getTile(FastMath.toRadians(1.5), FastMath.toRadians(2.5));
155         cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(2.5));
156         cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(2.5));
157         cache.getTile(FastMath.toRadians(3.5), FastMath.toRadians(0.5));
158         cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(1.5));
159         cache.getTile(FastMath.toRadians(2.5), FastMath.toRadians(0.5));
160         cache.getTile(FastMath.toRadians(0.5), FastMath.toRadians(0.5));
161         Assertions.assertEquals(16, factory.getCount());
162 
163         // keep using the same tiles for a while
164         RandomGenerator generator = new Well19937a(0x1c951160de55c9d5l);
165         for (int i = 0; i < 10000; ++i) {
166             double lat = 3.0 * generator.nextDouble();
167             double lon = 4.0 * generator.nextDouble();
168                 cache.getTile(FastMath.toRadians(lat), FastMath.toRadians(lon));
169         }
170         Assertions.assertEquals(16, factory.getCount());
171 
172         cache.getTile(FastMath.toRadians(-30.5), FastMath.toRadians(2.5));
173         Assertions.assertEquals(17, factory.getCount());
174 
175     }
176         
177     @Test
178     public void testDummySRTM() throws URISyntaxException, FileNotFoundException, UnsupportedEncodingException {
179 
180         // Simple SRTM with 2 elevations
181         int rowCols = 1000;
182         DummySRTMsimpleElevationUpdater srtmUpdater = new DummySRTMsimpleElevationUpdater(rowCols, 10.0, 20.0, 3);
183         double tileSizeDeg = srtmUpdater.getTileSizeDeg();
184 
185         CountingFactory factory = new CountingFactory();
186         TilesCache<SimpleTile> cache = new TilesCache<SimpleTile>(factory, srtmUpdater , 5, false);
187 
188         boolean isDifferentStep = false;
189         double epsilonLatLon = 1.e-5;
190         double latTileDeg;
191         double lonTileDeg;
192         double latDeg;
193         double lonDeg;
194         
195         // ##########################################################
196         // Tiles with same resolution
197         // ##########################################################
198 
199         // North-East hemisphere  
200         // =====================
201         latTileDeg = 47.;
202         lonTileDeg = 12.3;
203 
204         SimpleTile tileNEhemisphere = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
205 
206         // Latitude North of the tile
207         latDeg = getNorthernEdgeOfTile(tileNEhemisphere);
208         lonDeg = lonTileDeg;
209         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileNEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
210 
211         // Latitude South of the tile
212         latDeg = getSouthernEdgeOfTile(tileNEhemisphere);
213         lonDeg = lonTileDeg;
214 
215         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileNEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
216 
217         // Longitude West of the tile
218         latDeg = latTileDeg;
219         lonDeg = getWesternEdgeOfTile(tileNEhemisphere);
220 
221         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.WEST, tileNEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
222 
223         // Longitude East of the tile
224         latDeg = latTileDeg;
225         lonDeg = getEasternEdgeOfTile(tileNEhemisphere);
226 
227         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.EAST,tileNEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
228 
229         // Check the 4 corner zipper tiles
230         check4cornersZipperTiles(tileNEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
231 
232         // South-East hemisphere 
233         // =====================
234         latTileDeg = -16.2;
235         lonTileDeg = 22.3;
236         SimpleTile tileSEhemisphere = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
237 
238         // Latitude North of the tile
239         latDeg = getNorthernEdgeOfTile(tileSEhemisphere);
240         lonDeg = lonTileDeg;
241 
242         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileSEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
243 
244         // Latitude South of the tile
245         latDeg = getSouthernEdgeOfTile(tileSEhemisphere);
246         lonDeg = lonTileDeg;
247 
248         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileSEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
249 
250         // Longitude West of the tile
251         latDeg = latTileDeg;
252         lonDeg = getWesternEdgeOfTile(tileSEhemisphere);
253 
254         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.WEST, tileSEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
255 
256         // Longitude East of the tile
257         latDeg = latTileDeg;
258         lonDeg = getEasternEdgeOfTile(tileSEhemisphere);
259         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.EAST, tileSEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
260 
261         // Check the 4 corner zipper tiles
262         check4cornersZipperTiles(tileSEhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
263 
264         // North-West hemisphere 
265         // =====================
266         latTileDeg = 46.8;
267         lonTileDeg = -66.5; 
268 
269         SimpleTile tileNWhemisphere = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
270 
271         // Latitude North of the tile
272         latDeg = getNorthernEdgeOfTile(tileNWhemisphere);
273         lonDeg = lonTileDeg;
274 
275         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileNWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
276 
277         // Latitude South of the tile
278         latDeg = getSouthernEdgeOfTile(tileNWhemisphere);
279         lonDeg = lonTileDeg;
280 
281         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileNWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
282 
283         // Longitude West of the tile
284         latDeg = latTileDeg;
285         lonDeg = getWesternEdgeOfTile(tileNWhemisphere);
286 
287         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.WEST, tileNWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
288 
289         // Longitude East of the tile
290         latDeg = latTileDeg;
291         lonDeg = getEasternEdgeOfTile(tileNWhemisphere);
292 
293         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.EAST, tileNWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
294 
295         // Check the 4 corner zipper tiles
296         check4cornersZipperTiles(tileNWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
297 
298         // South-West hemisphere 
299         // =====================
300         latTileDeg = -28.8  ;
301         lonTileDeg = -58.4; 
302         SimpleTile tileSWhemisphere = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
303 
304         // Latitude North of the tile
305         latDeg = getNorthernEdgeOfTile(tileSWhemisphere);
306         lonDeg = lonTileDeg;
307 
308         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileSWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
309 
310         // Latitude South of the tile
311         latDeg = getSouthernEdgeOfTile(tileSWhemisphere);
312         lonDeg = lonTileDeg;
313 
314         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileSWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
315 
316         // Longitude West of the tile
317         latDeg = latTileDeg;
318         lonDeg = getWesternEdgeOfTile(tileSWhemisphere);
319 
320         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.WEST, tileSWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
321 
322         // Longitude East of the tile
323         latDeg = latTileDeg;
324         lonDeg = getEasternEdgeOfTile(tileSWhemisphere);
325 
326         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.EAST, tileSWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
327 
328         // Check the 4 corner zipper tiles
329         check4cornersZipperTiles(tileSWhemisphere, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
330         
331         // Anti meridians  (180 degrees W/E)
332         // =====================
333         // tile SRTM 72/16: 175 - 180 East / 15 - 20 South  
334         latTileDeg = -18.;
335         lonTileDeg = 178.;
336         SimpleTile tileAntiMeridianEast = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
337 
338         // Longitude East of the tile
339         latDeg = latTileDeg;
340         lonDeg = getEasternEdgeOfTile(tileAntiMeridianEast);
341 
342         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.EAST,tileAntiMeridianEast, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
343 
344         // tile SRTM 01/16: 175 - 180 West / 15 - 20 South  
345         latTileDeg = -18.;
346         lonTileDeg = -178.;
347         SimpleTile tileAntiMeridianWest = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
348 
349         // Longitude West of the tile
350         latDeg = latTileDeg;
351         lonDeg = getWesternEdgeOfTile(tileAntiMeridianWest);
352 
353         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.WEST,tileAntiMeridianWest, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
354         
355         // ##########################################################
356         // Tiles around the resolution change (around +/- 60 degrees)
357         // ##########################################################
358 
359         // Cleanup
360         clearFactoryMaps(CountingFactory.class);
361         
362         rowCols = 10;
363         srtmUpdater = new DummySRTMsimpleElevationUpdater(rowCols, 10.0, 20.0, 2);
364         tileSizeDeg = srtmUpdater.getTileSizeDeg();
365 
366         factory = new CountingFactory();
367         cache = new TilesCache<SimpleTile>(factory, srtmUpdater , 5, false);
368 
369         // Above 60 degrees  
370         // ================
371         
372         // Current tile is below 60 degrees
373         // --------------------------------
374         latTileDeg = 59.;
375         lonTileDeg = 12.3;
376 
377         SimpleTile tileAround60deg = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
378         
379         // Latitude North of the tile
380         latDeg = getNorthernEdgeOfTile(tileAround60deg);
381         lonDeg = lonTileDeg;
382 
383         isDifferentStep = true;
384         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileAround60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
385         isDifferentStep = false;
386       
387         // Check the 4 corner zipper tiles
388         isDifferentStep = true;
389         check4cornersZipperTiles(tileAround60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
390         isDifferentStep = false;
391         
392         // Current tile is above 60 degrees
393         // --------------------------------
394         latTileDeg = 61.;
395         lonTileDeg = 12.3;
396 
397         tileAround60deg = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
398         
399         // Latitude South of the tile
400         latDeg = getSouthernEdgeOfTile(tileAround60deg);
401         lonDeg = lonTileDeg;
402 
403         isDifferentStep = true;
404         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileAround60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
405         isDifferentStep = false;
406 
407         // Check the 4 corner zipper tiles
408         isDifferentStep = true;
409         check4cornersZipperTiles(tileAround60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
410         isDifferentStep = false;
411 
412         // Below -60 degrees  
413         // =================
414         
415         // Current tile is above -60 degrees
416         // --------------------------------
417         latTileDeg = -59.;
418         lonTileDeg = 12.3;
419 
420         SimpleTile tileAroundMinus60deg = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
421 
422         // Latitude South of the tile
423         latDeg = getSouthernEdgeOfTile(tileAroundMinus60deg);
424         lonDeg = lonTileDeg;
425         
426         isDifferentStep = true;
427         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.SOUTH, tileAroundMinus60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
428         isDifferentStep = false;
429         
430         // Check the 4 corner zipper tiles
431         isDifferentStep = true;
432         check4cornersZipperTiles(tileAroundMinus60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
433         isDifferentStep = false;
434         
435         // Current tile is below -60 degrees
436         // --------------------------------
437         latTileDeg = -61.;
438         lonTileDeg = 12.3;
439 
440         tileAroundMinus60deg = cache.getTile(FastMath.toRadians(latTileDeg), FastMath.toRadians(lonTileDeg));
441 
442         // Latitude North of the tile
443         latDeg = getNorthernEdgeOfTile(tileAroundMinus60deg);
444         lonDeg = lonTileDeg;
445         
446         isDifferentStep = true;
447         searchAndVerifyZipperTile(latDeg, lonDeg, Tile.Location.NORTH, tileAroundMinus60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
448         isDifferentStep = false;
449         
450         // Check the 4 corner zipper tiles
451         isDifferentStep = true;
452         check4cornersZipperTiles(tileAroundMinus60deg, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
453         isDifferentStep = false;
454 
455     }
456 
457     /**
458      * Check the computed zipper tile 
459      * @param latDeg latitude belonging to the zipper tile (deg)
460      * @param lonDeg longitude belonging to the zipper tile (deg)
461      * @param zipperLocation location of the zipper tile according to the current tile 
462      * @param tile the current tile 
463      * @param tileSizeDeg the current tile size (deg)
464      * @param cache the tiles cache
465      * @param epsilonLatLon epsilon to compare latitude and longitude
466      * @param isDifferentStep flag to tell if zipper is at the edge of tiles with different steps
467      * @return the zipper tile
468      */
469     private SimpleTile searchAndVerifyZipperTile(double latDeg, double lonDeg, Tile.Location zipperLocation, SimpleTile tile, final double tileSizeDeg,
470             TilesCache<SimpleTile> cache, double epsilonLatLon, boolean isDifferentStep) {
471  
472         SimpleTile zipperTile = cache.getTile(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg));
473         
474         checkGeodeticPointBelongsToZipper(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg), zipperTile);
475         
476         if (!isDifferentStep) {
477             checkZipperTile(zipperTile, zipperLocation, tile, tileSizeDeg, cache, epsilonLatLon);
478         } else {
479             checkZipperTileDifferentStep(zipperTile, zipperLocation, tile, tileSizeDeg, cache, epsilonLatLon);
480         }
481         
482         return zipperTile;
483     }
484 
485     private void check4cornersZipperTiles(SimpleTile tile, final double tileSizeDeg,
486                                           TilesCache<SimpleTile> cache, double epsilonLatLon,
487                                           boolean isDifferentStep) {
488         double latDeg;
489         double lonDeg;
490         
491         SimpleTile cornerZipperTile;
492         
493         // Latitude/Longitude corner NW
494         latDeg = getNorthernEdgeOfTile(tile);
495         lonDeg = getWesternEdgeOfTile(tile);
496         cornerZipperTile = cache.getTile(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg));
497         
498         checkGeodeticPointBelongsToZipper(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg), cornerZipperTile);
499         checkCornerZipperTile(cornerZipperTile, Tile.Location.NORTH_WEST, tile, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
500         
501         // Latitude/Longitude corner SW
502         latDeg = getSouthernEdgeOfTile(tile);
503         lonDeg = getWesternEdgeOfTile(tile);
504         cornerZipperTile = cache.getTile(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg));
505 
506         checkGeodeticPointBelongsToZipper(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg), cornerZipperTile);
507         checkCornerZipperTile(cornerZipperTile, Tile.Location.SOUTH_WEST, tile, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
508 
509         // Latitude/Longitude corner NE
510         latDeg = getNorthernEdgeOfTile(tile);
511         lonDeg = getEasternEdgeOfTile(tile);
512         cornerZipperTile = cache.getTile(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg));
513 
514         checkGeodeticPointBelongsToZipper(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg), cornerZipperTile);
515         checkCornerZipperTile(cornerZipperTile, Tile.Location.NORTH_EAST, tile, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
516 
517         // Latitude/Longitude corner SE
518         latDeg = getSouthernEdgeOfTile(tile);
519         lonDeg = getEasternEdgeOfTile(tile);
520         cornerZipperTile = cache.getTile(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg));
521 
522         checkGeodeticPointBelongsToZipper(FastMath.toRadians(latDeg), FastMath.toRadians(lonDeg), cornerZipperTile);
523         checkCornerZipperTile(cornerZipperTile, Tile.Location.SOUTH_EAST, tile, tileSizeDeg, cache, epsilonLatLon, isDifferentStep);
524     }
525 
526     private void checkGeodeticPointBelongsToZipper(double latitude, double longitude, SimpleTile cornerZipperTile) {
527         
528         double latMin = cornerZipperTile.getMinimumLatitude();
529         double lonMin = cornerZipperTile.getMinimumLongitude();
530         double latMax = cornerZipperTile.getLatitudeAtIndex(cornerZipperTile.getLatitudeRows() - 1);
531         double lonMax = cornerZipperTile.getLongitudeAtIndex(cornerZipperTile.getLongitudeColumns() - 1);
532         
533         // this test purpose is to ensure the geodetic points belongs to the zipper tile
534         assertTrue((latMin <= latitude) && (latitude <= latMax));
535         assertTrue((lonMin <= longitude) && (longitude <= lonMax));
536         
537     }
538     private void checkCornerZipperTile(SimpleTile cornerZipperTile, Tile.Location location, SimpleTile originTile,
539             double tileSizeDeg, TilesCache<SimpleTile> cache, double epsilonLatLon, boolean isDifferentStep) {
540     
541         SurroundingTiles surroundingTiles = new SurroundingTiles(originTile, tileSizeDeg, cache);
542 
543         if (location == Tile.Location.SOUTH_WEST) {
544 
545             SimpleTile tileBelow = surroundingTiles.getTileBelow();
546             SimpleTile tileLeft = surroundingTiles.getTileLeft();
547             
548             if (! isDifferentStep) {
549                 // The 2 following tiles are the same if the tiles step are the same 
550                 SimpleTile belowLeftOfCurrent = new SurroundingTiles(tileLeft, tileSizeDeg, cache).getTileBelow();
551                 SimpleTile leftBelowOfCurrent = new SurroundingTiles(tileBelow, tileSizeDeg, cache).getTileLeft();
552 
553                 SimpleTile belowRight = tileBelow;
554                 SimpleTile belowLeft1  = belowLeftOfCurrent;
555                 SimpleTile belowLeft2  = leftBelowOfCurrent;
556                 SimpleTile aboveRight = originTile;
557                 SimpleTile aboveLeft = tileLeft;
558 
559                 checkCornerElevations(cornerZipperTile, originTile, belowLeft1, belowRight, aboveLeft, aboveRight, epsilonLatLon);
560                 checkCornerElevations(cornerZipperTile, originTile, belowLeft2, belowRight, aboveLeft, aboveRight, epsilonLatLon);
561                 
562             } else {
563 
564                 // Tiles at same latitude have same resolutions
565                 SimpleTile leftBelowOfCurrent = new SurroundingTiles(tileBelow, tileSizeDeg, cache).getTileLeft();
566 
567                 SimpleTile belowRight = tileBelow;
568                 SimpleTile belowLeft  = leftBelowOfCurrent;
569                 SimpleTile aboveRight = originTile;
570                 SimpleTile aboveLeft = tileLeft;
571                 
572                checkCornerElevationsDifferentStep(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight, epsilonLatLon);
573             }
574         } else if (location == Tile.Location.NORTH_WEST) {
575 
576             SimpleTile tileAbove = surroundingTiles.getTileAbove();
577             SimpleTile tileLeft = surroundingTiles.getTileLeft();
578             
579             if (! isDifferentStep) {
580 
581                 // The 2 following tiles are the same if the tiles step are the same 
582                 SimpleTile aboveLeftOfCurrent = new SurroundingTiles(tileLeft, tileSizeDeg, cache).getTileAbove();
583                 SimpleTile leftAboveOfCurrent = new SurroundingTiles(tileAbove, tileSizeDeg, cache).getTileLeft();
584 
585                 SimpleTile belowRight = originTile;
586                 SimpleTile belowLeft = tileLeft;
587                 SimpleTile aboveRight = tileAbove;
588                 SimpleTile aboveLeft1 = aboveLeftOfCurrent;
589                 SimpleTile aboveLeft2 = leftAboveOfCurrent;
590 
591                 checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft1, aboveRight, epsilonLatLon);
592                 checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft2, aboveRight, epsilonLatLon);
593                 
594             } else {
595                 
596                 // Tiles at same latitude have same resolutions
597                 SimpleTile leftAboveOfCurrent = new SurroundingTiles(tileAbove, tileSizeDeg, cache).getTileLeft();
598 
599                 SimpleTile belowRight = originTile;
600                 SimpleTile belowLeft = tileLeft;
601                 SimpleTile aboveRight = tileAbove;
602                 SimpleTile aboveLeft = leftAboveOfCurrent;
603                 
604                 checkCornerElevationsDifferentStep(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight, epsilonLatLon);
605             }
606             
607 
608         } else if (location == Tile.Location.SOUTH_EAST) {
609             
610             SimpleTile tileBelow = surroundingTiles.getTileBelow();
611             SimpleTile tileRight = surroundingTiles.getTileRight();
612             if (! isDifferentStep) {
613 
614                 // The 2 following tiles are the same if the tiles step are the same 
615                 SimpleTile belowRightOfCurrent = new SurroundingTiles(tileRight, tileSizeDeg, cache).getTileBelow();
616                 SimpleTile rightBelowOfCurrent = new SurroundingTiles(tileBelow, tileSizeDeg, cache).getTileRight();
617 
618                 SimpleTile belowRight1  = belowRightOfCurrent;
619                 SimpleTile belowRight2  = rightBelowOfCurrent;
620                 SimpleTile belowLeft   = tileBelow;
621                 SimpleTile aboveRight  = tileRight;
622                 SimpleTile aboveLeft   = originTile;
623 
624                 checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight1, aboveLeft, aboveRight, epsilonLatLon);
625                 checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight2, aboveLeft, aboveRight, epsilonLatLon);
626                 
627             } else {
628                 
629                 // Tiles at same latitude have same resolutions
630                 SimpleTile belowRightOfCurrent = new SurroundingTiles(tileRight, tileSizeDeg, cache).getTileBelow();
631                 
632                 SimpleTile belowRight  = belowRightOfCurrent;
633                 SimpleTile belowLeft   = tileBelow;
634                 SimpleTile aboveRight  = tileRight;
635                 SimpleTile aboveLeft   = originTile;
636                 
637                 checkCornerElevationsDifferentStep(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight, epsilonLatLon);
638             }
639         } else if (location == Tile.Location.NORTH_EAST) {
640 
641             SimpleTile tileAbove = surroundingTiles.getTileAbove();
642             SimpleTile tileRight = surroundingTiles.getTileRight();
643             
644             if (! isDifferentStep) {
645 
646             // The 2 following tiles are the same if the tiles step are the same 
647             SimpleTile aboveRightOfCurrent = new SurroundingTiles(tileRight, tileSizeDeg, cache).getTileAbove();
648             SimpleTile rightAboveOfCurrent = new SurroundingTiles(tileAbove, tileSizeDeg, cache).getTileRight();
649           
650             SimpleTile belowRight = tileRight;
651             SimpleTile belowLeft = originTile;
652             SimpleTile aboveRight1 = aboveRightOfCurrent;
653             SimpleTile aboveRight2 = rightAboveOfCurrent;
654             SimpleTile aboveLeft = tileAbove;
655             
656             checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight1, epsilonLatLon);
657             checkCornerElevations(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight2, epsilonLatLon);
658             } else {
659                 
660                 // Tiles at same latitude have same resolutions
661                 SimpleTile aboveRightOfCurrent = new SurroundingTiles(tileRight, tileSizeDeg, cache).getTileAbove();
662                 
663                 SimpleTile belowRight = tileRight;
664                 SimpleTile belowLeft = originTile;
665                 SimpleTile aboveRight = aboveRightOfCurrent;
666                 SimpleTile aboveLeft = tileAbove;
667                 
668                 checkCornerElevationsDifferentStep(cornerZipperTile, originTile, belowLeft, belowRight, aboveLeft, aboveRight, epsilonLatLon);
669             }
670         }
671     }
672     private void checkCornerElevations(SimpleTile cornerZipperTile, SimpleTile originTile, 
673                                        SimpleTile belowLeft, SimpleTile belowRight, SimpleTile aboveLeft, SimpleTile aboveRight, 
674                                        double epsilonLatLon) {
675 
676         // row 0 of zipper 
677         double cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 0);
678         double belowLeftElevation = belowLeft.getElevationAtIndices(belowLeft.getLatitudeRows() - 2, belowLeft.getLongitudeColumns() - 2);
679         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
680 
681         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 1);
682         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeft.getLatitudeRows() - 2, belowLeft.getLongitudeColumns() - 1);
683         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
684 
685         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 2);
686         double belowRightElevation = belowRight.getElevationAtIndices(belowRight.getLatitudeRows() - 2, 0);
687         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
688 
689         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 3);
690         belowRightElevation = belowRight.getElevationAtIndices(belowRight.getLatitudeRows() - 2, 1);
691         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
692 
693 
694         // row 1 of zipper 
695         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 0);
696         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeft.getLatitudeRows() - 1, belowLeft.getLongitudeColumns() - 2);
697         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
698 
699         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 1);
700         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeft.getLatitudeRows() - 1, belowLeft.getLongitudeColumns() - 1);
701         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
702 
703         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 2);
704         belowRightElevation = belowRight.getElevationAtIndices(belowRight.getLatitudeRows() - 1, 0);
705         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
706 
707         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 3);
708         belowRightElevation = belowRight.getElevationAtIndices(belowRight.getLatitudeRows() - 1, 1);
709         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
710 
711         // row 2 of zipper 
712         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 0);
713         double aboveLeftELevation = aboveLeft.getElevationAtIndices(0, aboveLeft.getLongitudeColumns() - 2);
714         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
715         //double leftAboveOfCurrentELevation = leftAboveOfCurrent.getElevationAtIndices(0, leftAboveOfCurrent.getLongitudeColumns() - 2);
716         //assertEquals(leftAboveOfCurrentELevation, cornerZipperElevation, epsilonLatLon);
717 
718         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 1);
719         aboveLeftELevation = aboveLeft.getElevationAtIndices(0, aboveLeft.getLongitudeColumns() - 1);
720         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
721         //leftAboveOfCurrentELevation = leftAboveOfCurrent.getElevationAtIndices(0, leftAboveOfCurrent.getLongitudeColumns() - 1);
722         //assertEquals(leftAboveOfCurrentELevation, cornerZipperElevation, epsilonLatLon);
723 
724         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 2);
725         double aboveRightElevation = aboveRight.getElevationAtIndices(0, 0);
726         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
727 
728         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 3);
729         aboveRightElevation = aboveRight.getElevationAtIndices(0, 1);
730         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
731 
732         // row 3 of zipper
733         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 0);
734         aboveLeftELevation = aboveLeft.getElevationAtIndices(1, aboveLeft.getLongitudeColumns() - 2);
735         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
736         //leftAboveOfCurrentELevation = leftAboveOfCurrent.getElevationAtIndices(1, leftAboveOfCurrent.getLongitudeColumns() - 2);
737         //assertEquals(leftAboveOfCurrentELevation, cornerZipperElevation, epsilonLatLon);
738 
739         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 1);
740         aboveLeftELevation = aboveLeft.getElevationAtIndices(1, aboveLeft.getLongitudeColumns() - 1);
741         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
742         //leftAboveOfCurrentELevation = leftAboveOfCurrent.getElevationAtIndices(1, leftAboveOfCurrent.getLongitudeColumns() - 1);
743         //assertEquals(leftAboveOfCurrentELevation, cornerZipperElevation, epsilonLatLon);
744 
745         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 2);
746         aboveRightElevation = aboveRight.getElevationAtIndices(1, 0);
747         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
748 
749         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 3);
750         aboveRightElevation = aboveRight.getElevationAtIndices(1, 1);
751         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
752     }
753     
754     private void checkCornerElevationsDifferentStep(SimpleTile cornerZipperTile, SimpleTile originTile, 
755                                                     SimpleTile belowLeft, SimpleTile belowRight, SimpleTile aboveLeft, SimpleTile aboveRight, 
756                                                     double epsilonLatLon) {
757         
758         
759         // We assure to be inside a cell by adding a delta step (to avoid inappropriate index computation)
760         double deltaLon = 0.1*cornerZipperTile.getLongitudeStep();
761         double zipperLongitude0 = cornerZipperTile.getLongitudeAtIndex(0) + deltaLon;
762         double zipperLongitude1 = cornerZipperTile.getLongitudeAtIndex(1) + deltaLon;
763         double zipperLongitude2 = cornerZipperTile.getLongitudeAtIndex(2) + deltaLon;
764         double zipperLongitude3 = cornerZipperTile.getLongitudeAtIndex(3) + deltaLon;
765         
766         double deltaLat = 0.1*cornerZipperTile.getLatitudeStep();
767         double zipperLatitude0 = cornerZipperTile.getLatitudeAtIndex(0) + deltaLat;
768         double zipperLatitude1 = cornerZipperTile.getLatitudeAtIndex(1) + deltaLat;
769         double zipperLatitude2 = cornerZipperTile.getLatitudeAtIndex(2) + deltaLat;
770         double zipperLatitude3 = cornerZipperTile.getLatitudeAtIndex(3) + deltaLat;
771 
772         // Longitudes for column 0 of corner zipper
773         double aboveLeftDoubleLongitudeIndex0 = computeLongitudeIndexDifferentStep(zipperLongitude0, aboveLeft);
774         int aboveLeftLongitudeIndex0 = FastMath.max(0, FastMath.min(aboveLeft.getLongitudeColumns() - 1, (int) FastMath.floor(aboveLeftDoubleLongitudeIndex0)));
775 
776         double belowLeftDoubleLongitudeIndex0 = computeLongitudeIndexDifferentStep(zipperLongitude0, belowLeft);
777         int belowLeftLongitudeIndex0 = FastMath.max(0, FastMath.min(belowLeft.getLongitudeColumns() - 1, (int) FastMath.floor(belowLeftDoubleLongitudeIndex0)));
778 
779         // Longitudes for column 1 of corner zipper
780         double aboveLeftDoubleLongitudeIndex1 = computeLongitudeIndexDifferentStep(zipperLongitude1, aboveLeft);
781         int aboveLeftLongitudeIndex1 = FastMath.max(0, FastMath.min(aboveLeft.getLongitudeColumns() - 1, (int) FastMath.floor(aboveLeftDoubleLongitudeIndex1)));
782 
783         double belowLeftDoubleLongitudeIndex1 = computeLongitudeIndexDifferentStep(zipperLongitude1, belowLeft);
784         int belowLeftLongitudeIndex1 = FastMath.max(0, FastMath.min(belowLeft.getLongitudeColumns() - 1, (int) FastMath.floor(belowLeftDoubleLongitudeIndex1)));
785 
786         // Longitudes for column 2 of corner zipper
787         double aboveRightDoubleLongitudeIndex2 = computeLongitudeIndexDifferentStep(zipperLongitude2, aboveRight);
788         int aboveRightLongitudeIndex2 = FastMath.max(0, FastMath.min(aboveRight.getLongitudeColumns() - 1, (int) FastMath.floor(aboveRightDoubleLongitudeIndex2)));
789 
790         double belowRightDoubleLongitudeIndex2 = computeLongitudeIndexDifferentStep(zipperLongitude2, belowRight);
791         int belowRightLongitudeIndex2 = FastMath.max(0, FastMath.min(belowRight.getLongitudeColumns() - 1, (int) FastMath.floor(belowRightDoubleLongitudeIndex2)));
792 
793         // Longitudes for column 3 of corner zipper
794         double aboveRightDoubleLongitudeIndex3 = computeLongitudeIndexDifferentStep(zipperLongitude3, aboveRight);
795         int aboveRightLongitudeIndex3 = FastMath.max(0, FastMath.min(aboveRight.getLongitudeColumns() - 1, (int) FastMath.floor(aboveRightDoubleLongitudeIndex3)));
796 
797         double belowRightDoubleLongitudeIndex3 = computeLongitudeIndexDifferentStep(zipperLongitude3, belowRight);
798         int belowRightLongitudeIndex3 = FastMath.max(0, FastMath.min(belowRight.getLongitudeColumns() - 1, (int) FastMath.floor(belowRightDoubleLongitudeIndex3)));
799 
800         
801         // row 0 of zipper 
802         double belowLeftDoubleLatitudeIndex0 = computeLatitudeIndexDifferentStep(zipperLatitude0, belowLeft);
803         int belowLeftLatitudeIndex0 = FastMath.max(0, FastMath.min(belowLeft.getLatitudeRows() - 1, (int) FastMath.floor(belowLeftDoubleLatitudeIndex0)));
804         
805         double belowLeftElevation = belowLeft.getElevationAtIndices(belowLeftLatitudeIndex0, belowLeftLongitudeIndex0);
806         double cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 0);
807         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
808 
809         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeftLatitudeIndex0, belowLeftLongitudeIndex1);
810         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 1);
811         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
812 
813         
814         double belowRightDoubleLatitudeIndex0 = computeLatitudeIndexDifferentStep(zipperLatitude0, belowRight);
815         int belowRightLatitudeIndex0 = FastMath.max(0, FastMath.min(belowRight.getLatitudeRows() - 1, (int) FastMath.floor(belowRightDoubleLatitudeIndex0)));
816 
817         double belowRightElevation = belowRight.getElevationAtIndices(belowRightLatitudeIndex0, belowRightLongitudeIndex2);
818         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 2);
819         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
820 
821         belowRightElevation = belowRight.getElevationAtIndices(belowRightLatitudeIndex0, belowRightLongitudeIndex3);
822         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(0, 3);
823         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
824 
825 
826         // row 1 of zipper 
827         double belowLeftDoubleLatitudeIndex1 = computeLatitudeIndexDifferentStep(zipperLatitude1, belowLeft);
828         int belowLeftLatitudeIndex1 = FastMath.max(0, FastMath.min(belowLeft.getLatitudeRows() - 1, (int) FastMath.floor(belowLeftDoubleLatitudeIndex1)));
829         
830         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeftLatitudeIndex1, belowLeftLongitudeIndex0);
831         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 0);
832         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
833 
834         belowLeftElevation = belowLeft.getElevationAtIndices(belowLeftLatitudeIndex1, belowLeftLongitudeIndex1);
835         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 1);
836         assertEquals(belowLeftElevation, cornerZipperElevation, epsilonLatLon);
837         
838 
839         double belowRightDoubleLatitudeIndex1 = computeLatitudeIndexDifferentStep(zipperLatitude1, belowRight);
840         int belowRightLatitudeIndex1 = FastMath.max(0, FastMath.min(belowRight.getLatitudeRows() - 1, (int) FastMath.floor(belowRightDoubleLatitudeIndex1)));
841 
842         belowRightElevation = belowRight.getElevationAtIndices(belowRightLatitudeIndex1, belowRightLongitudeIndex2);
843         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 2);
844         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
845 
846         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(1, 3);
847         belowRightElevation = belowRight.getElevationAtIndices(belowRightLatitudeIndex1, belowRightLongitudeIndex3);
848         assertEquals(belowRightElevation, cornerZipperElevation, epsilonLatLon);
849 
850         // row 2 of zipper 
851         double aboveLeftDoubleLatitudeIndex2 = computeLatitudeIndexDifferentStep(zipperLatitude2, aboveLeft);
852         int aboveLeftLatitudeIndex2 = FastMath.max(0, FastMath.min(aboveLeft.getLatitudeRows() - 1, (int) FastMath.floor(aboveLeftDoubleLatitudeIndex2)));
853         
854         double aboveLeftELevation = aboveLeft.getElevationAtIndices(aboveLeftLatitudeIndex2, aboveLeftLongitudeIndex0);
855         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 0);
856         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
857 
858         aboveLeftELevation = aboveLeft.getElevationAtIndices(aboveLeftLatitudeIndex2, aboveLeftLongitudeIndex1);
859         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 1);
860         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
861 
862         
863         double aboveRightDoubleLatitudeIndex2 = computeLatitudeIndexDifferentStep(zipperLatitude2, aboveRight);
864         int aboveRightLatitudeIndex2 = FastMath.max(0, FastMath.min(aboveRight.getLatitudeRows() - 1, (int) FastMath.floor(aboveRightDoubleLatitudeIndex2)));
865 
866         double aboveRightElevation = aboveRight.getElevationAtIndices(aboveRightLatitudeIndex2, aboveRightLongitudeIndex2);
867         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 2);
868         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
869          
870         aboveRightElevation = aboveRight.getElevationAtIndices(aboveRightLatitudeIndex2, aboveRightLongitudeIndex3);
871         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(2, 3);
872         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
873 
874         // row 3 of zipper
875         double aboveLeftDoubleLatitudeIndex3 = computeLatitudeIndexDifferentStep(zipperLatitude3, aboveLeft);
876         int aboveLeftLatitudeIndex3 = FastMath.max(0, FastMath.min(aboveLeft.getLatitudeRows() - 1, (int) FastMath.floor(aboveLeftDoubleLatitudeIndex3)));
877         
878         aboveLeftELevation = aboveLeft.getElevationAtIndices(aboveLeftLatitudeIndex3, aboveLeftLongitudeIndex0);
879         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 0);
880         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
881 
882         aboveLeftELevation = aboveLeft.getElevationAtIndices(aboveLeftLatitudeIndex3, aboveLeftLongitudeIndex1);
883         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 1);
884         assertEquals(aboveLeftELevation, cornerZipperElevation, epsilonLatLon);
885 
886         double aboveRightDoubleLatitudeIndex3 = computeLatitudeIndexDifferentStep(zipperLatitude3, aboveRight);
887         int aboveRightLatitudeIndex3 = FastMath.max(0, FastMath.min(aboveRight.getLatitudeRows() - 1, (int) FastMath.floor(aboveRightDoubleLatitudeIndex3)));
888 
889         aboveRightElevation = aboveRight.getElevationAtIndices(aboveRightLatitudeIndex3, aboveRightLongitudeIndex2);
890         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 2);
891         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
892          
893         aboveRightElevation = aboveRight.getElevationAtIndices(aboveRightLatitudeIndex3, aboveRightLongitudeIndex3);
894         cornerZipperElevation = cornerZipperTile.getElevationAtIndices(3, 3);
895         assertEquals(aboveRightElevation, cornerZipperElevation, epsilonLatLon);
896     }
897 
898 
899     private void checkZipperTile(SimpleTile zipperTile, Tile.Location zipperLocation, 
900                                  SimpleTile originTile, double tileSizeDeg, 
901                                  TilesCache<SimpleTile> cache, double epsilonLatLon) {
902 
903     	SurroundingTiles surroundingTiles = new SurroundingTiles(originTile, tileSizeDeg, cache);
904     	
905         if (zipperLocation == Tile.Location.SOUTH) {
906         	SimpleTile tileBelow = surroundingTiles.getTileBelow();
907             for (int jLon = 0; jLon < zipperTile.getLongitudeColumns(); jLon++) {
908                 // row 0 of zipper 
909                 double zipperElevation = zipperTile.getElevationAtIndices(0, jLon);
910                 double belowELevation = tileBelow.getElevationAtIndices(tileBelow.getLatitudeRows() - 2, jLon);
911                 assertEquals(belowELevation, zipperElevation, epsilonLatLon);
912    
913                 // row 1 of zipper 
914                 zipperElevation = zipperTile.getElevationAtIndices(1, jLon);
915                 belowELevation = tileBelow.getElevationAtIndices(tileBelow.getLatitudeRows() - 1, jLon);
916                 assertEquals(belowELevation, zipperElevation, epsilonLatLon);
917                 
918                 // row 2 of zipper 
919                 zipperElevation = zipperTile.getElevationAtIndices(2, jLon);
920                 double originELevation = originTile.getElevationAtIndices(0, jLon);
921                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
922 
923                 // row 3 of zipper 
924                 zipperElevation = zipperTile.getElevationAtIndices(3, jLon);
925                 originELevation = originTile.getElevationAtIndices(1, jLon);
926                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
927             }
928         } else if (zipperLocation == Tile.Location.NORTH) {
929         	SimpleTile tileAbove = surroundingTiles.getTileAbove();
930             for (int jLon = 0; jLon < zipperTile.getLongitudeColumns(); jLon++) {
931                 // row 0 of zipper 
932                 double zipperElevation = zipperTile.getElevationAtIndices(0, jLon);
933                 double originELevation = originTile.getElevationAtIndices(originTile.getLatitudeRows() - 2, jLon);
934                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
935    
936                 // row 1 of zipper 
937                 zipperElevation = zipperTile.getElevationAtIndices(1, jLon);
938                 originELevation = originTile.getElevationAtIndices(originTile.getLatitudeRows() - 1, jLon);
939                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
940                 
941                 // row 2 of zipper 
942                 zipperElevation = zipperTile.getElevationAtIndices(2, jLon);
943                 double aboveELevation = tileAbove.getElevationAtIndices(0, jLon);
944                 assertEquals(aboveELevation, zipperElevation, epsilonLatLon);
945 
946                 // row 3 of zipper 
947                 zipperElevation = zipperTile.getElevationAtIndices(3, jLon);
948                 aboveELevation = tileAbove.getElevationAtIndices(1, jLon);
949                 assertEquals(aboveELevation, zipperElevation, epsilonLatLon);
950             }
951         } else if (zipperLocation == Tile.Location.WEST) {
952         	SimpleTile tileLeft = surroundingTiles.getTileLeft();
953             for (int iLat = 0; iLat < zipperTile.getLatitudeRows(); iLat++) {
954                 // col 0 of zipper 
955                 double zipperElevation = zipperTile.getElevationAtIndices(iLat, 0);
956                 double leftELevation = tileLeft.getElevationAtIndices(iLat, tileLeft.getLongitudeColumns() - 2);
957                 assertEquals(leftELevation, zipperElevation, epsilonLatLon);
958    
959                 // col 1 of zipper 
960                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 1);
961                 leftELevation = tileLeft.getElevationAtIndices(iLat, tileLeft.getLongitudeColumns() - 1);
962                 assertEquals(leftELevation, zipperElevation, epsilonLatLon);
963                 
964                 // col 2 of zipper 
965                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 2);
966                 double originELevation = originTile.getElevationAtIndices(iLat, 0);
967                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
968 
969                 // col 3 of zipper 
970                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 3);
971                 originELevation = originTile.getElevationAtIndices(iLat, 1);
972                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
973             }
974 
975         } else if (zipperLocation == Tile.Location.EAST) {
976         	SimpleTile tileRight = surroundingTiles.getTileRight();
977             for (int iLat = 0; iLat < zipperTile.getLatitudeRows(); iLat++) {
978                 // col 0 of zipper 
979                 double zipperElevation = zipperTile.getElevationAtIndices(iLat, 0);
980                 double originELevation = originTile.getElevationAtIndices(iLat, originTile.getLongitudeColumns() - 2);
981                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
982    
983                 // col 1 of zipper 
984                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 1);
985                 originELevation = originTile.getElevationAtIndices(iLat, originTile.getLongitudeColumns() - 1);
986                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
987                 
988                 // col 2 of zipper 
989                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 2);
990                 double rightELevation = tileRight.getElevationAtIndices(iLat, 0);
991                 assertEquals(rightELevation, zipperElevation, epsilonLatLon);
992 
993                 // col 3 of zipper 
994                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 3);
995                 rightELevation = tileRight.getElevationAtIndices(iLat, 1);
996                 assertEquals(rightELevation, zipperElevation, epsilonLatLon);
997             }
998         }
999     }
1000     
1001     private void checkZipperTileDifferentStep(SimpleTile zipperTile, Tile.Location zipperLocation, 
1002                                               SimpleTile originTile, double tileSizeDeg, 
1003                                               TilesCache<SimpleTile> cache, double epsilonLatLon) {
1004 
1005         SurroundingTiles surroundingTiles = new SurroundingTiles(originTile, tileSizeDeg, cache);
1006 
1007         if (zipperLocation == Tile.Location.SOUTH) {
1008             
1009             SimpleTile tileBelow = surroundingTiles.getTileBelow();
1010             
1011             for (int jLon = 0; jLon < zipperTile.getLongitudeColumns(); jLon++) {
1012                 
1013                 // We assure to be inside a cell by adding a delta step (to avoid inappropriate index computation)
1014                 double deltaLon = 0.1*zipperTile.getLongitudeStep();
1015                 double zipperLongitude = zipperTile.getLongitudeAtIndex(jLon) + deltaLon;
1016                 
1017                 double originTileDoubleLongitudeIndex = computeLongitudeIndexDifferentStep(zipperLongitude, originTile);
1018                 int originTileLongitudeIndex = FastMath.max(0, FastMath.min(originTile.getLongitudeColumns() - 1, (int) FastMath.floor(originTileDoubleLongitudeIndex)));
1019 
1020                 double belowTileDoubleLongitudeIndex = computeLongitudeIndexDifferentStep(zipperLongitude, tileBelow);
1021                 int belowTileLongitudeIndex = FastMath.max(0, FastMath.min(tileBelow.getLongitudeColumns() - 1, (int) FastMath.floor(belowTileDoubleLongitudeIndex)));
1022 
1023                 // row 0 of zipper
1024                 double zipperLat0 = zipperTile.getMinimumLatitude() + 0.1*zipperTile.getLatitudeStep();
1025                 double belowTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat0, tileBelow);
1026                 int belowTileLatitudeIndex0 = FastMath.max(0, FastMath.min(tileBelow.getLatitudeRows() - 1, (int) FastMath.floor(belowTileDoubleLatitudeIndex)));
1027 
1028                 double zipperElevation = zipperTile.getElevationAtIndices(0, jLon);
1029                 double belowELevation = tileBelow.getElevationAtIndices(belowTileLatitudeIndex0, belowTileLongitudeIndex);
1030                 assertEquals(belowELevation, zipperElevation, epsilonLatLon);
1031 
1032                 // row 1 of zipper
1033                 double zipperLat1 = zipperLat0 + zipperTile.getLatitudeStep();
1034                 belowTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat1, tileBelow);
1035                 int originTileLatitudeIndex1 = FastMath.max(0, FastMath.min(tileBelow.getLatitudeRows() - 1, (int) FastMath.floor(belowTileDoubleLatitudeIndex)));
1036 
1037                 zipperElevation = zipperTile.getElevationAtIndices(1, jLon);
1038                 belowELevation = tileBelow.getElevationAtIndices(originTileLatitudeIndex1, belowTileLongitudeIndex);
1039                 assertEquals(belowELevation, zipperElevation, epsilonLatLon);
1040 
1041                 // row 2 of zipper 
1042                 double zipperLat2 = zipperLat0 + 2*zipperTile.getLatitudeStep();
1043                 double originTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat2, originTile);
1044                 int originTileLatitudeIndex2 = FastMath.max(0, FastMath.min(originTile.getLatitudeRows() - 1, (int) FastMath.floor(originTileDoubleLatitudeIndex)));
1045                
1046                 zipperElevation = zipperTile.getElevationAtIndices(2, jLon);
1047                 double originELevation = originTile.getElevationAtIndices(originTileLatitudeIndex2, originTileLongitudeIndex);
1048                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1049 
1050                 // row 3 of zipper 
1051                 double zipperLat3 = zipperLat0 + 3*zipperTile.getLatitudeStep();
1052                 originTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat3, originTile);
1053                 int originTileLatitudeIndex3 = FastMath.max(0, FastMath.min(originTile.getLatitudeRows() - 1, (int) FastMath.floor(originTileDoubleLatitudeIndex)));
1054 
1055                 zipperElevation = zipperTile.getElevationAtIndices(3, jLon);
1056                 originELevation = originTile.getElevationAtIndices(originTileLatitudeIndex3, originTileLongitudeIndex);
1057                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1058             }
1059         } else if (zipperLocation == Tile.Location.NORTH) {
1060             
1061             SimpleTile tileAbove = surroundingTiles.getTileAbove();
1062                         
1063             for (int jLon = 0; jLon < zipperTile.getLongitudeColumns(); jLon++) {
1064                 
1065                 // We assure to be inside a cell by adding a delta step (to avoid inappropriate index computation)
1066                 double deltaLon = 0.1*zipperTile.getLongitudeStep();
1067                 double zipperLongitude = zipperTile.getLongitudeAtIndex(jLon) + deltaLon;
1068                 
1069                 double originTileDoubleLongitudeIndex = computeLongitudeIndexDifferentStep(zipperLongitude,originTile);
1070                 int originTileLongitudeIndex = FastMath.max(0, FastMath.min(originTile.getLongitudeColumns() - 1, (int) FastMath.floor(originTileDoubleLongitudeIndex)));
1071 
1072                 double aboveTileDoubleLongitudeIndex = computeLongitudeIndexDifferentStep(zipperLongitude, tileAbove);
1073                 int aboveTileLongitudeIndex = FastMath.max(0, FastMath.min(tileAbove.getLongitudeColumns() - 1, (int) FastMath.floor(aboveTileDoubleLongitudeIndex)));
1074 
1075                 // row 0 of zipper
1076                 double zipperLat0 = zipperTile.getMinimumLatitude() + 0.1*zipperTile.getLatitudeStep();
1077                 double originTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat0, originTile);
1078                 int originTileLatitudeIndex0 = FastMath.max(0, FastMath.min(originTile.getLatitudeRows() - 1, (int) FastMath.floor(originTileDoubleLatitudeIndex)));
1079 
1080                 double zipperElevation = zipperTile.getElevationAtIndices(0, jLon);
1081                 double originELevation = originTile.getElevationAtIndices(originTileLatitudeIndex0, originTileLongitudeIndex);
1082                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1083 
1084                 // row 1 of zipper 
1085                 double zipperLat1 = zipperLat0 + zipperTile.getLatitudeStep();
1086                 originTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat1, originTile);
1087                 int originTileLatitudeIndex1 = FastMath.max(0, FastMath.min(originTile.getLatitudeRows() - 1, (int) FastMath.floor(originTileDoubleLatitudeIndex)));
1088 
1089                 zipperElevation = zipperTile.getElevationAtIndices(1, jLon);
1090                 originELevation = originTile.getElevationAtIndices(originTileLatitudeIndex1, originTileLongitudeIndex);
1091                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1092 
1093                 // row 2 of zipper 
1094                 double zipperLat2 = zipperLat0 + 2*zipperTile.getLatitudeStep();
1095                 double aboveTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat2, tileAbove);
1096                 int aboveTileLatitudeIndex2 = FastMath.max(0, FastMath.min(tileAbove.getLatitudeRows() - 1, (int) FastMath.floor(aboveTileDoubleLatitudeIndex)));
1097 
1098                 zipperElevation = zipperTile.getElevationAtIndices(2, jLon);
1099                 double aboveELevation = tileAbove.getElevationAtIndices(aboveTileLatitudeIndex2, aboveTileLongitudeIndex);
1100                 assertEquals(aboveELevation, zipperElevation, epsilonLatLon);
1101 
1102                 // row 3 of zipper 
1103                 double zipperLat3 = zipperLat0 + 3*zipperTile.getLatitudeStep();
1104                 aboveTileDoubleLatitudeIndex =  computeLatitudeIndexDifferentStep(zipperLat3, tileAbove);
1105                 int aboveTileLatitudeIndex3 = FastMath.max(0, FastMath.min(tileAbove.getLatitudeRows() - 1, (int) FastMath.floor(aboveTileDoubleLatitudeIndex)));
1106 
1107                 zipperElevation = zipperTile.getElevationAtIndices(3, jLon);
1108                 aboveELevation = tileAbove.getElevationAtIndices(aboveTileLatitudeIndex3, aboveTileLongitudeIndex);
1109                 assertEquals(aboveELevation, zipperElevation, epsilonLatLon);
1110             }
1111         } else if (zipperLocation == Tile.Location.WEST) {
1112             SimpleTile tileLeft = surroundingTiles.getTileLeft();
1113             for (int iLat = 0; iLat < zipperTile.getLatitudeRows(); iLat++) {
1114                 // col 0 of zipper 
1115                 double zipperElevation = zipperTile.getElevationAtIndices(iLat, 0);
1116                 double leftELevation = tileLeft.getElevationAtIndices(iLat, tileLeft.getLongitudeColumns() - 2);
1117                 assertEquals(leftELevation, zipperElevation, epsilonLatLon);
1118 
1119                 // col 1 of zipper 
1120                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 1);
1121                 leftELevation = tileLeft.getElevationAtIndices(iLat, tileLeft.getLongitudeColumns() - 1);
1122                 assertEquals(leftELevation, zipperElevation, epsilonLatLon);
1123 
1124                 // col 2 of zipper 
1125                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 2);
1126                 double originELevation = originTile.getElevationAtIndices(iLat, 0);
1127                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1128 
1129                 // col 3 of zipper 
1130                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 3);
1131                 originELevation = originTile.getElevationAtIndices(iLat, 1);
1132                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1133             }
1134 
1135         } else if (zipperLocation == Tile.Location.EAST) {
1136             SimpleTile tileRight = surroundingTiles.getTileRight();
1137             for (int iLat = 0; iLat < zipperTile.getLatitudeRows(); iLat++) {
1138                 // col 0 of zipper 
1139                 double zipperElevation = zipperTile.getElevationAtIndices(iLat, 0);
1140                 double originELevation = originTile.getElevationAtIndices(iLat, originTile.getLongitudeColumns() - 2);
1141                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1142 
1143                 // col 1 of zipper 
1144                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 1);
1145                 originELevation = originTile.getElevationAtIndices(iLat, originTile.getLongitudeColumns() - 1);
1146                 assertEquals(originELevation, zipperElevation, epsilonLatLon);
1147 
1148                 // col 2 of zipper 
1149                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 2);
1150                 double rightELevation = tileRight.getElevationAtIndices(iLat, 0);
1151                 assertEquals(rightELevation, zipperElevation, epsilonLatLon);
1152 
1153                 // col 3 of zipper 
1154                 zipperElevation = zipperTile.getElevationAtIndices(iLat, 3);
1155                 rightELevation = tileRight.getElevationAtIndices(iLat, 1);
1156                 assertEquals(rightELevation, zipperElevation, epsilonLatLon);
1157             }
1158         }
1159     }
1160     
1161     private double computeLatitudeIndexDifferentStep(double latitude, SimpleTile tileToSearchLatitudeIndex) {
1162         // Formula different from code 
1163         return 0.5 + latitude/ tileToSearchLatitudeIndex.getLatitudeStep() - tileToSearchLatitudeIndex.getMinimumLatitude() / tileToSearchLatitudeIndex.getLatitudeStep();
1164     }
1165 
1166     private double computeLongitudeIndexDifferentStep(double longitude, SimpleTile tileToSearchLongitudeIndex) {
1167         // Formula different from code 
1168         return 0.5 + longitude/ tileToSearchLongitudeIndex.getLongitudeStep() - tileToSearchLongitudeIndex.getMinimumLongitude() / tileToSearchLongitudeIndex.getLongitudeStep();
1169     }
1170 
1171     private final static double getNorthernEdgeOfTile(SimpleTile tile) {
1172         double northernLatitudeForTile = getNorthernLatitudeForTile(tile);
1173         // Inside the northern row of latitude
1174         return northernLatitudeForTile - 0.1*FastMath.toDegrees(tile.getLatitudeStep());
1175     }
1176     
1177     private final static double getSouthernEdgeOfTile(SimpleTile tile) {
1178       double southernLatitudeForTile = getSouthernLatitudeForTile(tile);
1179       // Inside the southern row of latitude
1180       return southernLatitudeForTile + 0.1*FastMath.toDegrees(tile.getLatitudeStep());
1181     }
1182 
1183     private final static double getWesternEdgeOfTile(SimpleTile tile) {
1184         double westernLongitudeForTile = getWesternLongitudeForTile(tile);
1185         // Inside the western column of longitude
1186         return westernLongitudeForTile + 0.1*FastMath.toDegrees(tile.getLongitudeStep());
1187     }
1188 
1189     private final static double getEasternEdgeOfTile(SimpleTile tile) {
1190         double easternLongitudeForTile = getEasternLongitudeForTile(tile);
1191         // Inside the eastern column of longitude
1192         return easternLongitudeForTile - 0.1*FastMath.toDegrees(tile.getLongitudeStep());
1193     }
1194     
1195     private final static double getNorthernLatitudeForTile(SimpleTile tile) {
1196         return FastMath.toDegrees(tile.getMaximumLatitude()) + 0.5*FastMath.toDegrees(tile.getLatitudeStep());
1197     }
1198     
1199     private final static double getSouthernLatitudeForTile(SimpleTile tile) {
1200         return FastMath.toDegrees(tile.getMinimumLatitude()) - 0.5*FastMath.toDegrees(tile.getLatitudeStep());
1201     }
1202 
1203     private final static double getWesternLongitudeForTile(SimpleTile tile) {
1204         return FastMath.toDegrees(tile.getMinimumLongitude()) - 0.5*FastMath.toDegrees(tile.getLongitudeStep());
1205     }
1206 
1207     private final static double getEasternLongitudeForTile(SimpleTile tile) {
1208         return FastMath.toDegrees(tile.getMaximumLongitude()) + 0.5*FastMath.toDegrees(tile.getLongitudeStep());
1209     }
1210 
1211     final static double epsilonElevation = 1.e-6;
1212 
1213     final static java.text.DecimalFormatSymbols symbols = new java.text.DecimalFormatSymbols(java.util.Locale.US);
1214     final static java.text.DecimalFormat dfs = new java.text.DecimalFormat("#.#####",symbols);
1215 
1216     /** Clean up of factory map
1217      * @param factoryClass
1218      */
1219     private static void clearFactoryMaps(Class<?> factoryClass) {
1220         try {
1221             
1222             for (Field field : factoryClass.getDeclaredFields()) {
1223                 if (Modifier.isStatic(field.getModifiers()) &&
1224                     Map.class.isAssignableFrom(field.getType())) {
1225                     field.setAccessible(true);
1226                     ((Map<?, ?>) field.get(null)).clear();
1227                 }
1228             }
1229         } catch (IllegalAccessException iae) {
1230             Assertions.fail(iae.getMessage());
1231         }
1232     }
1233 }
1234 
1235 class SurroundingTiles {
1236 
1237     private double latDeg;
1238     private double lonDeg;
1239     private TilesCache<SimpleTile> cache;
1240     private double tileSizeDeg;
1241 	
1242 	SurroundingTiles(SimpleTile originTile, double tileSizeDeg, TilesCache<SimpleTile> cache) {
1243 
1244 	    this.cache = cache;
1245 	    this.tileSizeDeg = tileSizeDeg;
1246 	    
1247 		int latRows = originTile.getLatitudeRows();
1248 		int loncols = originTile.getLongitudeColumns();
1249 		
1250 		// Get a middle point of the tile
1251 		this.latDeg = FastMath.toDegrees(originTile.getLatitudeAtIndex(latRows/2));
1252 		this.lonDeg = FastMath.toDegrees(originTile.getLongitudeAtIndex(loncols/2));
1253 	}
1254 
1255 	public SimpleTile getTileAbove() {
1256 	    
1257 	    // get tile above ...
1258         double latAbove =  latDeg + tileSizeDeg;
1259         return cache.getTile(FastMath.toRadians(latAbove), FastMath.toRadians(lonDeg));
1260 	}
1261 
1262 	public SimpleTile getTileBelow() {
1263 	    
1264         // get tile below
1265         double latBelow = latDeg - tileSizeDeg;
1266         return cache.getTile(FastMath.toRadians(latBelow), FastMath.toRadians(lonDeg));
1267 	}
1268 
1269 	public SimpleTile getTileLeft() {
1270 	    
1271 	    // get tile left
1272 	    double lonLeftRad = FastMath.toRadians(lonDeg - tileSizeDeg);
1273         // Assure that the longitude belongs to [-180, + 180]
1274         double lonLeftNorm = MathUtils.normalizeAngle(lonLeftRad, 0.0);
1275         return cache.getTile(FastMath.toRadians(latDeg), lonLeftNorm);
1276 	}
1277 
1278 	public SimpleTile getTileRight() {
1279 	    
1280         // get tile right
1281 	    double lonRightRad = FastMath.toRadians(lonDeg + tileSizeDeg);
1282         // Assure that the longitude belongs to [-180, + 180]
1283         double lonRightNorm = MathUtils.normalizeAngle(lonRightRad, 0.0);
1284         return cache.getTile(FastMath.toRadians(latDeg), lonRightNorm);
1285 	}
1286 }
1287 
1288