UNCLASSIFIED

GeographicTranslator
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
GeoidLibrary.cpp
Go to the documentation of this file.
1 // CLASSIFICATION: UNCLASSIFIED
2 
3 /***************************************************************************/
4 /* RSC IDENTIFIER: Geoid Library
5  *
6  * ABSTRACT
7  *
8  * The purpose of GEOID is to support conversions between WGS84 ellipsoid
9  * heights and WGS84 geoid heights.
10  *
11  *
12  * ERROR HANDLING
13  *
14  * This component checks parameters for valid values. If an invalid value
15  * is found, the error code is combined with the current error code using
16  * the bitwise or. This combining allows multiple error codes to be
17  * returned. The possible error codes are:
18  *
19  * GEOID_NO_ERROR : No errors occured in function
20  * GEOID_FILE_OPEN_ERROR : Geoid file opening error
21  * GEOID_INITIALIZE_ERROR : Geoid seoaration database can not initialize
22  * GEOID_LAT_ERROR : Latitude out of valid range
23  * (-90 to 90 degrees)
24  * GEOID_LON_ERROR : Longitude out of valid range
25  * (-180 to 360 degrees)
26  *
27  * REUSE NOTES
28  *
29  * GeoidLibrary is intended for reuse by any application that requires conversion
30  * between WGS84 ellipsoid heights and WGS84, EGM96, or EGM2008 orthometric heights.
31  *
32  * Environment variable EGM2008_GRID_USAGE should be set to either
33  * "FULL" or "AOI" before using this software to compute EGM2008 geoid
34  * separations. If the environment variable is set to "FULL", then the
35  * EGM2008 geoid separation interpolator uses its FULL-GRID software, which
36  * first loads NGA's worldwide EGM2008 grid before beginning any interpolations:
37  * the FULL-GRID algorithm draws all local interpolation windows' post points directly
38  * from this worldwide grid. On the other hand, if the environment variable is set to
39  * "AOI", then the EGM2008 geoid height interpolator uses its AOI-GRID sofware, which
40  * loads Area of Interst subregions from NGA's EGM2008 worldwide grid. Area of Interest
41  * support grids cover user-selected regions having N/S and E/W extents of about 125
42  * nautical miles. The AOI-grid interpolator draws local interpolation windows' support
43  * points from this intermediate grid. The worldwide grid resides only on disk, and the
44  * AOI-GRID algorithm never loads the entire worldwide grid into memory at any one time.
45  * The AOI-GRID algorithm allows small-system users to interpolate geoid separations without
46  * populating large amounts of high-speed computer memory with geoid separations having no
47  * immediate use. The AOI-GRID algorithm automatically repopulates these intermediate
48  * support grids when users' Areas of Interest shift from the 125 nm -by- 125 nm region
49  * currently loaded into their computer system's high speed memory.
50  *
51  * If environment variable EGM2008_GRID_USAGE is not set, or if it
52  * is set to something other that "FULL" or "AOI", then GeoidLibrary will
53  * interpolate EGM2008 geoid separations using its Area of Interest algorithm.
54  * This algorithm may not be fast enough for users needing to quickly compute
55  * very large numbers of geoid separations at widely dispersed horizontal locations.
56  * The AOI-GRID algorithm is intended for users needing to interpolate
57  * many geoid separations in fairly-localized (125 nm -by- 125 nm) areas.
58  *
59  * REFERENCES
60  *
61  * The Geoid software originated from:
62  *
63  * U.S. Army Topographic Engineering Center
64  * Geospatial Information Division
65  * 7701 Telegraph Road
66  * Alexandria, VA 22310-3864
67  *
68  * Further information on Geoid can be found in the Reuse Manual.
69  *
70  * LICENSES
71  *
72  * None apply to this component.
73  *
74  * RESTRICTIONS
75  *
76  * Geoid has no restrictions.
77  *
78  * ENVIRONMENT
79  *
80  * Geoid was tested and certified in the following environments
81  *
82  * 1. Solaris 2.5 with GCC 2.8.1
83  * 2. MS Windows XP with MS Visual C++ 6.0
84  *
85  *
86  * MODIFICATIONS
87  *
88  * Date Description
89  * ---- -----------
90  * 24-May-99 Original Code
91  * 09-Jan-06 Added new geoid height interpolation methods
92  * 03-14-07 Original C++ Code
93  * 05-12-10 S. Gillis, BAEts26542, MSP TS MSL-HAE conversion
94  * should use CCS
95  * 06-11-10 S. Gillis, BAEts26724, Fixed memory error problem
96  * when MSPCCS_DATA is not set
97  * 06-16-10 S. Gillis, BAEts27144, Fixed memory error problem
98  * when MSPCCS_DATA is not set
99  * 07-07-10 K.Lam, BAEts27269, Replace C functions in threads.h
100  * with C++ methods in classes CCSThreadMutex
101  * 12-17-10 RD Craig modified function loadGeoids()
102  * to handle the EGM2008 geoid (BAEts26267).
103  * 05-17-11 T. Thompson, BAEts27393, inform user if problem is
104  * due to undefined MSPCCS_DATA
105  *
106  */
107 
108 
109 /***************************************************************************/
110 /*
111  * INCLUDES
112  */
113 
114 #include <string.h>
115 #include <stdlib.h>
116 #include <stdio.h>
117 #include "GeoidLibrary.h"
119 #include "ErrorMessages.h"
120 #include "CCSThreadMutex.h"
121 #include "CCSThreadLock.h"
122 
123 #include "egm2008_geoid_grid.h"
126 
127 #include <vector>
128 
129 /*
130  * string.h - standard C string handling library
131  * stdio.h - standard C input/output library
132  * stdlib.h - standard C general utilities library
133  * GeoidLibrary.h - prototype error checking and error codes
134  * threads.h - used for thread safety
135  * CoordinateConversionException.h - Exception handler
136  * ErrorMessages.h - Contains exception messages
137  */
138 
139 
140 using namespace MSP::CCS;
141 using MSP::CCSThreadMutex;
142 using MSP::CCSThreadLock;
143 
144 
145 /***************************************************************************/
146 /* DEFINES
147  *
148  */
149 
150 const double PI = 3.14159265358979323e0; /* PI */
151 const double PI_OVER_2 = PI / 2.0;
152 const double TWO_PI = 2.0 * PI;
153 const double _180_OVER_PI = (180.0 / PI);
154 const int EGM96_COLS = 1441; /* 360 degrees of longitude at 15 minute spacing */
155 const int EGM96_ROWS = 721; /* 180 degrees of latitude at 15 minute spacing */
156 const int EGM84_COLS = 37; /* 360 degrees of longitude at 10 degree spacing */
157 const int EGM84_ROWS = 19; /* 180 degrees of latitude at 10 degree spacing */
158 const int EGM84_30_MIN_COLS = 721; /* 360 degrees of longitude at 30 minute spacing */
159 const int EGM84_30_MIN_ROWS = 361; /* 180 degrees of latitude at 30 minute spacing */
160 const int EGM96_HEADER_ITEMS = 6; /* min, max lat, min, max long, lat, long spacing*/
161 const double SCALE_FACTOR_15_MINUTES = .25; /* 4 grid cells per degree at 15 minute spacing */
162 const double SCALE_FACTOR_10_DEGREES = 10; /* 1 / 10.0 grid cells per degree at 10 degree spacing */
163 const double SCALE_FACTOR_30_MINUTES = .5; /* 2 grid cells per degree at 30 minute spacing */
164 const double SCALE_FACTOR_1_DEGREE = 1; /* 1 grid cell per degree at 1 degree spacing */
165 const double SCALE_FACTOR_2_DEGREES = 2; /* 1 / 2 grid cells per degree at 2 degree spacing */
169 const int EGM96_INSET_AREAS = 53;
170 
171 
172 /* defines the egm96 variable grid */
174 {
175  double min_lat; /* Min cell latitude */
176  double max_lat; /* Max cell latitude */
177  double min_lon; /* Min cell longitude */
178  double max_lon; /* Max cell latitude */
179 };
180 
181 /* Table of EGM96 variable grid 30 minute inset areas */
183  {{74.5, 75.5, 273.5, 280.0},
184  {66.5, 67.5, 293.5, 295.0},
185  {62.5, 64.0, 133.0, 136.5},
186  {60.5, 61.5, 208.5, 210.0},
187  {60.5, 61.0, 219.0, 220.5},
188  {51.0, 53.0, 172.0, 174.5},
189  {52.0, 53.0, 192.5, 194.0},
190  {51.0, 52.0, 188.5, 191.0},
191  {50.0, 52.0, 178.0, 182.5},
192  {43.0, 46.0, 148.0, 153.5},
193  {43.0, 45.0, 84.0, 89.5},
194  {40.0, 41.0, 70.5, 72.0},
195  {36.5, 37.0, 78.5, 79.0},
196  {36.0, 37.0, 348.0, 349.5},
197  {35.0, 36.0, 171.0, 172.5},
198  {34.0, 35.0, 140.5, 142.0},
199  {29.5, 31.0, 78.5, 81.0},
200  {28.5, 30.0, 81.5, 83.0},
201  {26.5, 30.0, 142.0, 143.5},
202  {26.0, 29.0, 91.5, 96.0},
203  {27.5, 29.0, 84.0, 86.5},
204  {28.0, 29.0, 342.5, 344.0},
205  {26.5, 28.0, 88.5, 90.0},
206  {25.0, 26.0, 189.0, 190.5},
207  {23.0, 24.0, 195.0, 196.5},
208  {21.0, 21.5, 204.0, 204.5},
209  {20.0, 21.0, 283.5, 288.0},
210  {18.5, 20.5, 204.0, 205.5},
211  {18.0, 20.0, 291.0, 296.5},
212  {17.0, 18.0, 298.0, 299.5},
213  {15.0, 16.0, 122.0, 123.5},
214  {12.0, 14.0, 144.5, 147.0},
215  {11.0, 12.0, 141.5, 144.0},
216  {9.5, 11.5, 125.0, 127.5},
217  {10.0, 11.0, 286.0, 287.5},
218  {6.0, 9.5, 287.0, 289.5},
219  {5.0, 7.0, 124.0, 128.5},
220  {-1.0, 1.0, 125.0, 128.5},
221  {-3.0, -1.5, 281.0, 282.5},
222  {-7.0, -5.0, 150.5, 155.0},
223  {-8.0, -7.0, 107.0, 108.5},
224  {-9.0, -7.0, 147.0, 149.5},
225  {-11.0, -10.0, 161.5, 163.0},
226  {-14.5, -13.5, 166.0, 167.5},
227  {-18.5, -17.0, 186.5, 188.0},
228  {-20.5, -20.0, 168.0, 169.5},
229  {-23.0, -20.0, 184.5, 187.0},
230  {-27.0, -24.0, 288.0, 290.5},
231  {-53.0, -52.0, 312.0, 313.5},
232  {-56.0, -55.0, 333.0, 334.5},
233  {-61.5, -60.0, 312.5, 317.0},
234  {-61.5, -60.5, 300.5, 303.0},
235  {-73.0, -72.0, 24.5, 26.0}};
236 
237 
238 /************************************************************************/
239 /* LOCAL FUNCTIONS
240  *
241  */
242 
244  void *buffer,
245  size_t size,
246  size_t count)
247 {
248  char *b = (char *) buffer;
249  char temp;
250  for (size_t c = 0; c < count; c ++)
251  {
252  for (size_t s = 0; s < size / 2; s ++)
253  {
254  temp = b[c*size + s];
255  b[c*size + s] = b[c*size + size - s - 1];
256  b[c*size + size - s - 1] = temp;
257  }
258  }
259 }
260 
261 
262 size_t readBinary(
263  void *buffer,
264  size_t size,
265  size_t count,
266  FILE *stream )
267 {
268  size_t actual_count = fread(buffer, size, count, stream);
269 
270  if(ferror(stream) || actual_count < count )
271  {
272  char message[256] = "";
273  strcpy( message, "Error reading binary file." );
274  throw CoordinateConversionException( message );
275  }
276 
277 #ifdef LITTLE_ENDIAN
278  swapBytes(buffer, size, count);
279 #endif
280 
281  return actual_count;
282 }
283 
284 /************************************************************************/
285 /* FUNCTIONS
286  *
287  */
288 
289 /* This class is a safeguard to make sure the singleton gets deleted
290  * when the application exits
291  */
292 namespace MSP
293 {
294  namespace CCS
295  {
297  {
298  public:
299 
301  {
302  CCSThreadLock lock(&GeoidLibrary::mutex);
303  GeoidLibrary::deleteInstance();
304  }
305 
307  }
308 }
309 
310 
311 // Make this class a singleton, so the data files are only initialized once
312 CCSThreadMutex GeoidLibrary::mutex;
313 GeoidLibrary* GeoidLibrary::instance = 0;
314 int GeoidLibrary::instanceCount = 0;
315 
316 
318 {
319  CCSThreadLock lock(&mutex);
320  if( instance == 0 )
321  instance = new GeoidLibrary;
322 
323  instanceCount++;
324 
325  return instance;
326 }
327 
328 
330 {
331 /*
332  * The function removeInstance removes this GeoidLibrary instance from the
333  * total number of instances.
334  */
335  CCSThreadLock lock(&mutex);
336  if ( --instanceCount < 1 )
337  {
338  deleteInstance();
339  }
340 }
341 
342 
343 void GeoidLibrary::deleteInstance()
344 {
345 /*
346  * Delete the singleton.
347  */
348 
349  if( instance != 0 )
350  {
351  delete instance;
352  instance = 0;
353  }
354 }
355 
356 
358 {
359  loadGeoids();
360 }
361 
362 
364 {
365  *this = gl; // OK only if new object is re-initialized before use, RDC
366 }
367 
368 
370 {
371  delete [] egm96GeoidList;
372  delete [] egm84GeoidList;
373  delete [] egm84ThirtyMinGeoidList;
374 
375  delete egm2008Geoid;
376 }
377 
378 
380 {
381  if ( &gl == this )
382  return *this;
383 
384  egm96GeoidList = new float[EGM96_ELEVATIONS];
385  egm84GeoidList = new float[EGM84_ELEVATIONS];
386  egm84ThirtyMinGeoidList = new double[EGM84_30_MIN_ELEVATIONS];
387 
388  for( int i = 0; i < EGM96_ELEVATIONS; i++ )
389  {
390  egm96GeoidList[i] = gl.egm96GeoidList[i];
391  }
392 
393  for( int j = 0; j < EGM84_ELEVATIONS; j++ )
394  {
395  egm84GeoidList[j] = gl.egm84GeoidList[j];
396  }
397 
398  for( int k = 0; k < EGM84_30_MIN_ELEVATIONS; k++ )
399  {
400  egm84ThirtyMinGeoidList[k] = gl.egm84ThirtyMinGeoidList[k];
401  }
402 
403  *( this->egm2008Geoid ) = *( gl.egm2008Geoid ); // Assign EGM 2008 object
404 
405  return *this;
406 }
407 
408 
409 void GeoidLibrary::loadGeoids()
410 {
411 /*
412  * The function loadGeoids reads geoid separation data from a file in
413  * the current directory and builds the geoid separation table from it.
414  * If the separation file can not be found or accessed, an error code of
415  * GEOID_FILE_OPEN_ERROR is returned, If the separation file is incomplete
416  * or improperly formatted, an error code of GEOID_INITIALIZE_ERROR is returned,
417  * otherwise GEOID_NO_ERROR is returned. This function must be called before
418  * any of the other functions in this component.
419  *
420  * If one or more geoids can be initialized, the function returns successfully.
421  */
422 
423  egm96GeoidList = NULL;
424  egm84GeoidList = NULL;
425  egm84ThirtyMinGeoidList = NULL;
426  egm2008Geoid = NULL;
427 
428  // Legacy geoids .....
429 
430  try
431  {
432  initializeEGM96Geoid();
433  }
435  {
436  delete egm96GeoidList;
437  egm96GeoidList = NULL;
438  }
439  try
440  {
441  initializeEGM84Geoid();
442  }
444  {
445  delete egm84GeoidList;
446  egm84GeoidList = NULL;
447  }
448  try
449  {
450  initializeEGM84ThirtyMinGeoid();
451  }
453  {
454  delete egm84ThirtyMinGeoidList;
455  egm84ThirtyMinGeoidList = NULL;
456  }
457 
458  // EGM2008 geoid .....
459 
460  try
461  {
462  initializeEGM2008Geoid();
463  }
465  {
466  delete egm2008Geoid;
467  egm2008Geoid = NULL;
468 
469  // if nothing worked, throw an exception
470  if (egm96GeoidList == NULL &&
471  egm84GeoidList == NULL &&
472  egm84ThirtyMinGeoidList == NULL)
473  {
475  "Error: GeoidLibrary::LoadGeoids: All geoid height buffer initialization failed.");
476  }
477  }
478 
479 } // End of function loadGeoids()
480 
481 
483  double longitude,
484  double latitude,
485  double ellipsoidHeight,
486  double *geoidHeight )
487 {
488 /*
489  * The function convertEllipsoidToEGM96FifteenMinBilinearGeoidHeight converts the specified WGS84
490  * ellipsoid height at the specified geodetic coordinates to the equivalent
491  * geoid height, using the EGM96 gravity model and the bilinear interpolation method.
492  *
493  * longitude : Geodetic longitude in radians (input)
494  * latitude : Geodetic latitude in radians (input)
495  * ellipsoidHeight : Ellipsoid height, in meters (input)
496  * geoidHeight : Geoid height, in meters. (output)
497  *
498  */
499 
500  if (egm96GeoidList == NULL)
501  {
503  "Error: EGM96 Geoid height buffer is NULL");
504  }
505 
506  double delta_height;
507 
508  bilinearInterpolate(
509  longitude, latitude,
511  &delta_height );
512 
513  *geoidHeight = ellipsoidHeight - delta_height;
514 }
515 
516 
518  double longitude,
519  double latitude,
520  double ellipsoidHeight,
521  double *geoidHeight )
522 {
523 /*
524  * The function convertEllipsoidToEGM96VariableNaturalSplineHeight converts the specified WGS84
525  * ellipsoid height at the specified geodetic coordinates to the equivalent
526  * geoid height, using the EGM96 gravity model and the natural spline interpolation method..
527  *
528  * longitude : Geodetic longitude in radians (input)
529  * latitude : Geodetic latitude in radians (input)
530  * ellipsoidHeight : Ellipsoid height, in meters (input)
531  * geoidHeight : Geoid height, in meters. (output)
532  *
533  */
534 
535  if (egm96GeoidList == NULL)
536  {
538  "Error: EGM96 Geoid height buffer is NULL");
539  }
540 
541  int i = 0;
542  int num_cols = EGM96_COLS;
543  int num_rows = EGM96_ROWS;
544  double latitude_degrees = latitude * _180_OVER_PI;
545  double longitude_degrees = longitude * _180_OVER_PI;
546  double scale_factor = SCALE_FACTOR_15_MINUTES;
547  double delta_height;
548  long found = 0;
549 
550  if( longitude_degrees < 0.0 )
551  longitude_degrees += 360.0;
552 
553  while( !found && i < EGM96_INSET_AREAS )
554  {
555  if(( latitude_degrees >= EGM96_Variable_Grid_Table[i].min_lat ) &&
556  ( longitude_degrees >= EGM96_Variable_Grid_Table[i].min_lon ) &&
557  ( latitude_degrees < EGM96_Variable_Grid_Table[i].max_lat ) &&
558  ( longitude_degrees < EGM96_Variable_Grid_Table[i].max_lon ) )
559  {
560  scale_factor = SCALE_FACTOR_30_MINUTES; // use 30 minute by 30 minute grid
561  num_cols = 721;
562  num_rows = 361;
563  found = 1;
564  }
565 
566  i++;
567  }
568 
569  if( !found )
570  {
571  if( latitude_degrees >= -60.0 && latitude_degrees < 60.0 )
572  {
573  scale_factor = SCALE_FACTOR_1_DEGREE; // use 1 degree by 1 degree grid
574  num_cols = 361;
575  num_rows = 181;
576  }
577  else
578  {
579  scale_factor = SCALE_FACTOR_2_DEGREES; // use 2 degree by 2 degree grid
580  num_cols = 181;
581  num_rows = 91;
582  }
583  }
584 
585  naturalSplineInterpolate(
586  longitude, latitude,
587  scale_factor, num_cols, num_rows, EGM96_ELEVATIONS-1, egm96GeoidList,
588  &delta_height );
589 
590  *geoidHeight = ellipsoidHeight - delta_height;
591 }
592 
593 
595  double longitude,
596  double latitude,
597  double ellipsoidHeight,
598  double *geoidHeight )
599 {
600 /*
601  * The function convertEllipsoidToEGM84TenDegBilinearHeight converts the specified WGS84
602  * ellipsoid height at the specified geodetic coordinates to the equivalent
603  * geoid height, using the EGM84 gravity model and the bilinear interpolation method.
604  *
605  * longitude : Geodetic longitude in radians (input)
606  * latitude : Geodetic latitude in radians (input)
607  * ellipsoidHeight : Ellipsoid height, in meters (input)
608  * geoidHeight : Geoid height, in meters. (output)
609  *
610  */
611 
612  if (egm84GeoidList == NULL)
613  {
615  "Error: EGM84 Geoid height buffer is NULL");
616  }
617 
618  double delta_height;
619 
620  bilinearInterpolate(
621  longitude, latitude,
623  &delta_height );
624 
625  *geoidHeight = ellipsoidHeight - delta_height;
626 }
627 
628 
630  double longitude,
631  double latitude,
632  double ellipsoidHeight,
633  double *geoidHeight )
634 {
635 /*
636  * The function convertEllipsoidToEGM84TenDegNaturalSplineHeight converts the specified WGS84
637  * ellipsoid height at the specified geodetic coordinates to the equivalent
638  * geoid height, using the EGM84 gravity model and the natural spline interpolation method..
639  *
640  * longitude : Geodetic longitude in radians (input)
641  * latitude : Geodetic latitude in radians (input)
642  * ellipsoidHeight : Ellipsoid height, in meters (input)
643  * geoidHeight : Geoid height, in meters. (output)
644  *
645  */
646 
647  if (egm84GeoidList == NULL)
648  {
650  "Error: EGM84 Geoid height buffer is NULL");
651  }
652 
653  double delta_height;
654 
655  naturalSplineInterpolate(
656  longitude, latitude,
658  egm84GeoidList, &delta_height );
659 
660  *geoidHeight = ellipsoidHeight - delta_height;
661 }
662 
663 
665  double longitude, double latitude, double ellipsoidHeight,
666  double *geoidHeight )
667 {
668 /*
669  * The function convertEllipsoidToEGM84ThirtyMinBiLinearHeight converts the
670  * specified WGS84 ellipsoid height at the specified geodetic coordinates to the
671  * equivalent geoid height, using the EGM84 gravity model and the bilinear
672  * interpolation method..
673  *
674  * longitude : Geodetic longitude in radians (input)
675  * latitude : Geodetic latitude in radians (input)
676  * ellipsoidHeight : Ellipsoid height, in meters (input)
677  * geoidHeight : Geoid height, in meters. (output)
678  *
679  */
680 
681  if (egm84GeoidList == NULL)
682  {
684  "Error: EGM84 Geoid height buffer is NULL");
685  }
686 
687  double delta_height;
688 
689  bilinearInterpolateDoubleHeights(
690  longitude, latitude,
692  egm84ThirtyMinGeoidList, &delta_height );
693 
694  *geoidHeight = ellipsoidHeight - delta_height;
695 }
696 
697 
699  double longitude,
700  double latitude,
701  double ellipsoidHeight,
702  double *geoidHeight )
703 {
704 /*
705  * The function convertEllipsoidHeightToEGM2008GeoidHeight
706  * converts the specified WGS84 ellipsoid height at the specified
707  * geodetic coordinates to the equivalent height above the geoid. This
708  * function uses the EGM2008 gravity model, plus bicubic spline interpolation.
709  *
710  * longitude : Geodetic longitude in radians (input)
711  * latitude : Geodetic latitude in radians (input)
712  * ellipsoidHeight : Height above the ellipsoid, meters. (input)
713  * geoidHeight : Height above the geoid, meters. (output)
714  */
715 
716  // Both the Egm2008FullGrid and Egm2008AoiGrid interpolators
717  // have functions named "geoidHeight", and both of these functions
718  // interpolate a geoid separation from surrounding geoid-separation posts.
719  // These two functions have identical software signatures, so there is no need
720  // for two EGM2008 GeoidLibrary ellisoid-height -to- height-above-geoid functions.
721 
722  if (this->egm2008Geoid == NULL)
723  {
725  "Error: EGM2008 geoid buffer is NULL" );
726  }
727 
728  try
729  {
730  const int WSIZE = 6; // Always use local 6x6 interpolation window
731 
732  int error;
733 
734  double geoidSeparation;
735 
736  error =
737  this->egm2008Geoid->geoidHeight
738  ( WSIZE, latitude, longitude, geoidSeparation );
739 
740  if ( error != 0 ) throw;
741 
742  *geoidHeight = ellipsoidHeight - geoidSeparation;
743 
744  } // End of exceptions' try block
745 
746  catch( ... )
747  {
749  "Error: Could not convert ellipsoid height to EGM2008 geoid height" );
750  }
751 
752 } // End of function convertEGM2008GeoidHeightToEllipsoidHeight()
753 
754 
755 void GeoidLibrary::convertEGM96FifteenMinBilinearGeoidToEllipsoidHeight( double longitude, double latitude, double geoidHeight, double *ellipsoidHeight )
756 {
757 /*
758  * The function convertEGM96FifteenMinBilinearGeoidToEllipsoidHeight converts the specified WGS84
759  * geoid height at the specified geodetic coordinates to the equivalent
760  * ellipsoid height, using the EGM96 gravity model and the bilinear interpolation method.
761  *
762  * longitude : Geodetic longitude in radians (input)
763  * latitude : Geodetic latitude in radians (input)
764  * geoidHeight : Geoid height, in meters. (input)
765  * ellipsoidHeight : Ellipsoid height, in meters (output)
766  *
767  */
768 
769  if (egm96GeoidList == NULL)
770  {
772  "Error: EGM96 Geoid height buffer is NULL");
773  }
774 
775  double delta_height;
776 
777  bilinearInterpolate(
778  longitude, latitude,
780  &delta_height );
781 
782  *ellipsoidHeight = geoidHeight + delta_height;
783 }
784 
785 
787  double longitude,
788  double latitude,
789  double geoidHeight,
790  double *ellipsoidHeight )
791 {
792 /*
793  * The function convertEGM96VariableNaturalSplineToEllipsoidHeight converts the specified WGS84
794  * geoid height at the specified geodetic coordinates to the equivalent
795  * ellipsoid height, using the EGM96 gravity model and the natural spline interpolation method.
796  *
797  * longitude : Geodetic longitude in radians (input)
798  * latitude : Geodetic latitude in radians (input)
799  * geoidHeight : Geoid height, in meters. (input)
800  * ellipsoidHeight : Ellipsoid height, in meters (output)
801  *
802  */
803 
804  if (egm96GeoidList == NULL)
805  {
807  "Error: EGM96 Geoid height buffer is NULL");
808  }
809 
810  int i = 0;
811  int num_cols = EGM96_COLS;
812  int num_rows = EGM96_ROWS;
813  double latitude_degrees = latitude * _180_OVER_PI;
814  double longitude_degrees = longitude * _180_OVER_PI;
815  double scale_factor = SCALE_FACTOR_15_MINUTES;
816  double delta_height;
817  long found = 0;
818 
819  if( longitude_degrees < 0.0 )
820  longitude_degrees += 360.0;
821 
822  while( !found && i < EGM96_INSET_AREAS )
823  {
824  if(( latitude_degrees >= EGM96_Variable_Grid_Table[i].min_lat ) &&
825  ( longitude_degrees >= EGM96_Variable_Grid_Table[i].min_lon ) &&
826  ( latitude_degrees < EGM96_Variable_Grid_Table[i].max_lat ) &&
827  ( longitude_degrees < EGM96_Variable_Grid_Table[i].max_lon ) )
828  {
829  scale_factor = SCALE_FACTOR_30_MINUTES; // use 30 minute by 30 minute grid
830  num_cols = 721;
831  num_rows = 361;
832  found = 1;
833  }
834 
835  i++;
836  }
837 
838  if( !found )
839  {
840  if( latitude_degrees >= -60.0 && latitude_degrees < 60.0 )
841  {
842  scale_factor = SCALE_FACTOR_1_DEGREE; // use 1 degree by 1 degree grid
843  num_cols = 361;
844  num_rows = 181;
845  }
846  else
847  {
848  scale_factor = SCALE_FACTOR_2_DEGREES; // use 2 degree by 2 degree grid
849  num_cols = 181;
850  num_rows = 91;
851  }
852  }
853 
854  naturalSplineInterpolate(
855  longitude, latitude,
856  scale_factor, num_cols, num_rows, EGM96_ELEVATIONS-1, egm96GeoidList,
857  &delta_height );
858 
859  *ellipsoidHeight = geoidHeight + delta_height;
860 }
861 
862 
863 void GeoidLibrary::convertEGM84TenDegBilinearToEllipsoidHeight( double longitude, double latitude, double geoidHeight, double *ellipsoidHeight )
864 {
865 /*
866  * The function convertEGM84TenDegBilinearToEllipsoidHeight converts the specified WGS84
867  * geoid height at the specified geodetic coordinates to the equivalent
868  * ellipsoid height, using the EGM84 gravity model and the bilinear interpolation method.
869  *
870  * longitude : Geodetic longitude in radians (input)
871  * latitude : Geodetic latitude in radians (input)
872  * geoidHeight : Geoid height, in meters. (input)
873  * ellipsoidHeight : Ellipsoid height, in meters (output)
874  *
875  */
876 
877  if (egm84GeoidList == NULL)
878  {
880  "Error: EGM84 Geoid height buffer is NULL");
881  }
882 
883  double delta_height;
884 
885  bilinearInterpolate(
886  longitude, latitude,
888  &delta_height );
889 
890  *ellipsoidHeight = geoidHeight + delta_height;
891 }
892 
893 
895  double longitude,
896  double latitude,
897  double geoidHeight,
898  double *ellipsoidHeight )
899 {
900 /*
901  * The function convertEGM84TenDegNaturalSplineToEllipsoidHeight converts the specified WGS84
902  * geoid height at the specified geodetic coordinates to the equivalent
903  * ellipsoid height, using the EGM84 gravity model and the natural spline interpolation method.
904  *
905  * longitude : Geodetic longitude in radians (input)
906  * latitude : Geodetic latitude in radians (input)
907  * geoidHeight : Geoid height, in meters. (input)
908  * ellipsoidHeight : Ellipsoid height, in meters (output)
909  *
910  */
911 
912  if (egm84GeoidList == NULL)
913  {
915  "Error: EGM84 Geoid height buffer is NULL");
916  }
917 
918  double delta_height;
919 
920  naturalSplineInterpolate(
921  longitude, latitude, SCALE_FACTOR_10_DEGREES,
923  egm84GeoidList, &delta_height );
924 
925  *ellipsoidHeight = geoidHeight + delta_height;
926 }
927 
928 
930  double longitude, double latitude, double geoidHeight,
931  double *ellipsoidHeight )
932 {
933 /*
934  * The function convertEGM84ThirtyMinBiLinearToEllipsoidHeight converts the
935  * specified WGS84 geoid height at the specified geodetic coordinates to the
936  * equivalent ellipsoid height, using the EGM84 gravity model and the bilinear
937  * interpolation method.
938  *
939  * longitude : Geodetic longitude in radians (input)
940  * latitude : Geodetic latitude in radians (input)
941  * geoidHeight : Geoid height, in meters. (input)
942  * ellipsoidHeight : Ellipsoid height, in meters (output)
943  *
944  */
945 
946  if (egm84GeoidList == NULL)
947  {
949  "Error: EGM84 Geoid height buffer is NULL");
950  }
951 
952  double delta_height;
953 
954  bilinearInterpolateDoubleHeights( longitude, latitude, SCALE_FACTOR_30_MINUTES,
955  EGM84_30_MIN_COLS, EGM84_30_MIN_ROWS, egm84ThirtyMinGeoidList,
956  &delta_height );
957  *ellipsoidHeight = geoidHeight + delta_height;
958 }
959 
960 
962  double longitude,
963  double latitude,
964  double geoidHeight,
965  double *ellipsoidHeight )
966 {
967 /*
968  * The function convertEGM2008GeoidHeightToEllipsoidHeight
969  * converts the specified EGM2008 height above the geoid at the specified
970  * geodetic coordinates to the equivalent height above the earth ellipsoid.
971  * This function uses the EGM2008 gravity model, plus bicubic spline interpolation.
972  *
973  * longitude : Geodetic longitude in radians (input)
974  * latitude : Geodetic latitude in radians (input)
975  * geoidHeight : Height above the geoid, meters. (input)
976  * ellipsoidHeight : Height above the ellipsoid, meters. (output)
977  */
978 
979  // Both the Egm2008FullGrid and Egm2008AoiGrid interpolators
980  // have functions named "geoidHeight", and both of these functions
981  // interpolate a geoid separation from surrounding geoid-separation posts.
982  // These two functions have identical software signatures, so there is no need
983  // for two EGM2008 GeoidLibrary height-above-geoid -to- ellipsoid_height functions.
984 
985  if (this->egm2008Geoid == NULL)
986  {
988  "Error: EGM2008 geoid buffer is NULL");
989  }
990 
991  try
992  {
993  const int WSIZE = 6; // Always use local 6x6 interpolation window
994 
995  int error;
996 
997  double geoidSeparation;
998 
999  error =
1000  this->egm2008Geoid->geoidHeight
1001  ( WSIZE, latitude, longitude, geoidSeparation );
1002 
1003  if ( error != 0 ) throw;
1004 
1005  *ellipsoidHeight = geoidHeight + geoidSeparation;
1006 
1007  } // End of exceptions' try block
1008 
1009  catch( ... )
1010  {
1012  "Error: Could not convert EGM2008 geoid height to ellipsoid height" );
1013  }
1014 
1015 } // End of function convertEGM2008GeoidHeightToEllipsoidHeight()
1016 
1017 
1018 /************************************************************************/
1019 /* PRIVATE FUNCTIONS
1020  *
1021  */
1022 
1023 void GeoidLibrary::initializeEGM96Geoid()
1024 {
1025 /*
1026  * The function initializeEGM96Geoid reads geoid separation data from the egm96.grd
1027  * file in the current directory and builds a geoid separation table from it.
1028  * If the separation file can not be found or accessed, an error code of
1029  * GEOID_FILE_OPEN_ERROR is returned, If the separation file is incomplete
1030  * or improperly formatted, an error code of GEOID_INITIALIZE_ERROR is returned,
1031  * otherwise GEOID_NO_ERROR is returned.
1032  */
1033 
1034  int items_read = 0;
1035  char* file_name = 0;
1036  char* path_name = NULL;
1037  long elevations_read = 0;
1038  long items_discarded = 0;
1039  long num = 0;
1040  FILE* geoid_height_file;
1041 
1042  CCSThreadLock lock(&mutex);
1043 
1044 /* Check the environment for a user provided path, else current directory; */
1045 /* Build a File Name, including specified or default path: */
1046 
1047 #ifdef NDK_BUILD
1048  path_name = "/data/data/com.baesystems.msp.geotrans/lib/";
1049  file_name = new char[ 80 ];
1050  strcpy( file_name, path_name );
1051  strcat( file_name, "libegm96grd.so" );
1052 #else
1053  path_name = getenv( "MSPCCS_DATA" );;
1054  if (path_name != NULL)
1055  {
1056  file_name = new char[ strlen( path_name ) + 11 ];
1057  strcpy( file_name, path_name );
1058  strcat( file_name, "/" );
1059  }
1060  else
1061  {
1062  file_name = new char[ 21 ];
1063  strcpy( file_name, "../../data/" );
1064  }
1065  strcat( file_name, "egm96.grd" );
1066 #endif
1067 
1068 /* Open the File READONLY, or Return Error Condition: */
1069 
1070  if ( ( geoid_height_file = fopen( file_name, "rb" ) ) == NULL )
1071  {
1072  delete [] file_name;
1073  file_name = 0;
1074 
1075  char message[256] = "";
1076  if (NULL == path_name)
1077  {
1078  strcpy( message, "Environment variable undefined: MSPCCS_DATA." );
1079  }
1080  else
1081  {
1082  strcpy( message, ErrorMessages::geoidFileOpenError );
1083  strcat( message, ": egm96.grd\n" );
1084  }
1085  throw CoordinateConversionException( message );
1086  }
1087 
1088 /* Skip the Header Line: */
1089 
1090  float egm96GeoidHeaderList[EGM96_HEADER_ITEMS];
1091  items_discarded = readBinary(
1092  egm96GeoidHeaderList, 4, EGM96_HEADER_ITEMS, geoid_height_file );
1093 
1094 /* Determine if header read properly, or NOT: */
1095 
1096  if( egm96GeoidHeaderList[0] != -90.0 ||
1097  egm96GeoidHeaderList[1] != 90.0 ||
1098  egm96GeoidHeaderList[2] != 0.0 ||
1099  egm96GeoidHeaderList[3] != 360.0 ||
1100  egm96GeoidHeaderList[4] != SCALE_FACTOR_15_MINUTES ||
1101  egm96GeoidHeaderList[5] != SCALE_FACTOR_15_MINUTES ||
1102  items_discarded != EGM96_HEADER_ITEMS )
1103  {
1104  fclose( geoid_height_file );
1105  delete [] file_name;
1106  file_name = 0;
1107 
1108  char message[256] = "";
1109  strcpy( message, ErrorMessages::geoidFileParseError );
1110  strcat( message, ": egm96.grd\n" );
1111  throw CoordinateConversionException( message );
1112  }
1113 
1114 /* Extract elements from the file: */
1115  egm96GeoidList = new float[EGM96_ELEVATIONS];
1116  elevations_read = readBinary(
1117  egm96GeoidList, 4, EGM96_ELEVATIONS, geoid_height_file );
1118 
1119  fclose( geoid_height_file );
1120  geoid_height_file = 0;
1121 
1122  delete [] file_name;
1123  file_name = 0;
1124 }
1125 
1126 
1127 void GeoidLibrary::initializeEGM84Geoid()
1128 {
1129 /*
1130  * The function initializeEGM84Geoid reads geoid separation data from a file in
1131  * the current directory and builds the geoid separation table from it.
1132  * If the separation file can not be found or accessed, an error code of
1133  * GEOID_FILE_OPEN_ERROR is returned, If the separation file is incomplete
1134  * or improperly formatted, an error code of GEOID_INITIALIZE_ERROR is returned,
1135  * otherwise GEOID_NO_ERROR is returned.
1136  */
1137 
1138  int items_read = 0;
1139  char* file_name = 0;
1140  char* path_name = NULL;
1141  long elevations_read = 0;
1142  long num = 0;
1143  FILE* geoid_height_file;
1144 
1145  CCSThreadLock lock(&mutex);
1146 
1147 /* Check the environment for a user provided path, else current directory; */
1148 /* Build a File Name, including specified or default path: */
1149 
1150 #ifdef NDK_BUILD
1151  path_name = "/data/data/com.baesystems.msp.geotrans/lib/";
1152  file_name = new char[ 80 ];
1153  strcpy( file_name, path_name );
1154  strcat( file_name, "libegm84grd.so" );
1155 #else
1156  path_name = getenv( "MSPCCS_DATA" );
1157  if (path_name != NULL)
1158  {
1159  file_name = new char[ strlen( path_name ) + 11 ];
1160  strcpy( file_name, path_name );
1161  strcat( file_name, "/" );
1162  }
1163  else
1164  {
1165  file_name =new char[ 21 ];
1166  strcpy( file_name, "../../data/" );
1167  }
1168  strcat( file_name, "egm84.grd" );
1169 #endif
1170 
1171 /* Open the File READONLY, or Return Error Condition: */
1172 
1173  if( ( geoid_height_file = fopen( file_name, "rb" ) ) == NULL )
1174  {
1175  delete [] file_name;
1176  file_name = 0;
1177 
1178  char message[256] = "";
1179  if (NULL == path_name)
1180  {
1181  strcpy( message, "Environment variable undefined: MSPCCS_DATA." );
1182  }
1183  else
1184  {
1185  strcpy( message, ErrorMessages::geoidFileOpenError );
1186  strcat( message, ": egm84.grd\n" );
1187  }
1188  throw CoordinateConversionException( message );
1189  }
1190 
1191 
1192  /* Extract elements from the file: */
1193  egm84GeoidList = new float[EGM84_ELEVATIONS];
1194  elevations_read = readBinary(
1195  egm84GeoidList, 4, EGM84_ELEVATIONS, geoid_height_file );
1196 
1197  fclose( geoid_height_file );
1198 
1199  delete [] file_name;
1200  file_name = 0;
1201 }
1202 
1203 void GeoidLibrary::initializeEGM84ThirtyMinGeoid()
1204 {
1205 /*
1206  * The function initializeEGM84ThirtyMinGeoid reads geoid separation data from
1207  * a file in the current directory and builds the geoid separation table from it.
1208  * If the separation file can not be found or accessed, an error code of
1209  * GEOID_FILE_OPEN_ERROR is returned, If the separation file is incomplete
1210  * or improperly formatted, an error code of GEOID_INITIALIZE_ERROR is returned,
1211  * otherwise GEOID_NO_ERROR is returned.
1212  */
1213 
1214  int items_read = 0;
1215  char* file_name = 0;
1216  char* path_name = NULL;
1217  long elevations_read = 0;
1218  long num = 0;
1219  FILE* geoid_height_file;
1220 
1221  CCSThreadLock lock(&mutex);
1222 
1223 /* Check the environment for a user provided path, else current directory; */
1224 /* Build a File Name, including specified or default path: */
1225 
1226 #ifdef NDK_BUILD
1227  path_name = "/data/data/com.baesystems.msp.geotrans/lib/";
1228  file_name = new char[ 80 ];
1229  strcpy( file_name, path_name );
1230  strcat( file_name, "libwwgridbin.so" );
1231 #else
1232  path_name = getenv( "MSPCCS_DATA" );
1233  if (path_name != NULL)
1234  {
1235  file_name = new char[ strlen( path_name ) + 12 ];
1236  strcpy( file_name, path_name );
1237  strcat( file_name, "/" );
1238  }
1239  else
1240  {
1241  file_name =new char[ 22 ];
1242  strcpy( file_name, "../../data/" );
1243  }
1244  strcat( file_name, "wwgrid.bin" );
1245 #endif
1246 
1247 /* Open the File READONLY, or Return Error Condition: */
1248 
1249  if( ( geoid_height_file = fopen( file_name, "rb" ) ) == NULL )
1250  {
1251  delete [] file_name;
1252  file_name = 0;
1253 
1254  char message[256] = "";
1255  if (NULL == path_name)
1256  {
1257  strcpy( message, "Environment variable undefined: MSPCCS_DATA." );
1258  }
1259  else
1260  {
1261  strcpy( message, ErrorMessages::geoidFileOpenError );
1262  strcat( message, ": wwgrid.bin\n" );
1263  }
1264  throw CoordinateConversionException( message );
1265  }
1266 
1267 
1268 /* Extract elements from the file: */
1269 
1270  egm84ThirtyMinGeoidList = new double[EGM84_30_MIN_ELEVATIONS];
1271  elevations_read = readBinary(
1272  egm84ThirtyMinGeoidList, 8, EGM84_30_MIN_ELEVATIONS, geoid_height_file );
1273 
1274  fclose( geoid_height_file );
1275 
1276  delete [] file_name;
1277  file_name = 0;
1278 }
1279 
1280 
1281 void GeoidLibrary::initializeEGM2008Geoid( void )
1282 {
1283  // December 17, 2010
1284 
1285  // This function initializes one of two
1286  // EGM2008 geoid separation interpolators.
1287 
1288  // The FULL_GRID interpolator reads the entire EGM2008
1289  // geoid-separation grid into memory upon its instantiation.
1290  // The AOI_GRID interpolator only reads the grid file's
1291  // header into memory upon instantiation. Area of Interest
1292  // grids are read into memory later as needed for interpolation.
1293 
1294  // Most EGM2008 initialization functionality resides
1295  // in the Egm2008FullGrid and Egm2008AoiGrid classes.
1296  // Based on an environment variable, the following
1297  // logic instantiates the appropriate grid interpolator)
1298 
1299 //#ifdef NDK_BUILD
1300 //#else
1301  char message[256] = "";
1302  char* gridUsage = NULL;
1303 
1304  gridUsage = getenv( "EGM2008_GRID_USAGE" );
1305 
1306  if ( NULL == gridUsage )
1307  {
1308  // Environment variable not defined, so
1309  // instantiate the Egm2008AoiGrid interpolator;
1310  // object's constructor only reads grid file header here .....
1311 
1312  this->egm2008Geoid = new Egm2008AoiGrid;
1313  }
1314  else
1315  {
1316  if ( strcmp( gridUsage, "FULL" ) == 0 )
1317  {
1318  // Environment variable set to "FULL", so
1319  // instantiate the Egm2008FullGrid interpolator;
1320  // object's constructor reads the full grid file here .....
1321 
1322  this->egm2008Geoid = new Egm2008FullGrid;
1323  }
1324  else
1325  {
1326  // Environment variable set, but not to "FULL",
1327  // so instantiate the Egm2008AoiGrid interpolator;
1328  // object's constructor only reads grid file header here .....
1329 
1330  this->egm2008Geoid = new Egm2008AoiGrid;
1331  }
1332  }
1333 //#endif
1334 } // End of function initializeEGM2008Geoid()
1335 
1336 
1337 void GeoidLibrary::bilinearInterpolateDoubleHeights(
1338  double longitude,
1339  double latitude,
1340  double scale_factor,
1341  int num_cols,
1342  int num_rows,
1343  double *height_buffer,
1344  double *delta_height )
1345 {
1346 /*
1347  * The private function bilinearInterpolateDoubleHeights returns the height of the
1348  * WGS84 geoid above or below the WGS84 ellipsoid, at the
1349  * specified geodetic coordinates, using a grid of height adjustments
1350  * and the bilinear interpolation method.
1351  *
1352  * longitude : Geodetic longitude in radians (input)
1353  * latitude : Geodetic latitude in radians (input)
1354  * scale_factor : Grid scale factor (input)
1355  * num_cols : Number of columns in grid (input)
1356  * num_rows : Number of rows in grid (input)
1357  * height_buffer : Grid of height adjustments, doubles (input)
1358  * delta_height : Height Adjustment, in meters. (output)
1359  *
1360  */
1361 
1362  int index;
1363  int post_x, post_y;
1364  double offset_x, offset_y;
1365  double delta_x, delta_y;
1366  double _1_minus_delta_x, _1_minus_delta_y;
1367  double latitude_dd, longitude_dd;
1368  double height_se, height_sw, height_ne, height_nw;
1369  double w_sw, w_se, w_ne, w_nw;
1370  double south_lat, west_lon;
1371  int end_index = 0;
1372  int max_index = num_rows * num_cols - 1;
1373 
1374  if( ( latitude < -PI_OVER_2 ) || ( latitude > PI_OVER_2 ) )
1375  { /* Latitude out of range */
1377  }
1378  if( ( longitude < -PI ) || ( longitude > TWO_PI ) )
1379  { /* Longitude out of range */
1381  }
1382 
1383  latitude_dd = latitude * _180_OVER_PI;
1384  longitude_dd = longitude * _180_OVER_PI;
1385 
1386  /* Compute X and Y Offsets into Geoid Height Array: */
1387 
1388  if( longitude_dd < 0.0 )
1389  {
1390  offset_x = ( longitude_dd + 360.0 ) / scale_factor;
1391  }
1392  else
1393  {
1394  offset_x = longitude_dd / scale_factor;
1395  }
1396  offset_y = ( 90 - latitude_dd ) / scale_factor;
1397 
1398  /* Find Four Nearest Geoid Height Cells for specified latitude, longitude; */
1399  /* Assumes that (0,0) of Geoid Height Array is at Northwest corner: */
1400 
1401  post_x = ( int )( offset_x );
1402  if( ( post_x + 1 ) == num_cols )
1403  post_x--;
1404  post_y = ( int )( offset_y + 1.0e-11 );
1405  if( ( post_y + 1 ) == num_rows )
1406  post_y--;
1407 
1408  // NW Height
1409  index = post_y * num_cols + post_x;
1410  if( index < 0 )
1411  height_nw = height_buffer[ 0 ];
1412  else if( index > max_index )
1413  height_nw = height_buffer[ max_index ];
1414  else
1415  height_nw = height_buffer[ index ];
1416  // NE Height
1417  end_index = index + 1;
1418  if( end_index > max_index )
1419  height_ne = height_buffer[ max_index ];
1420  else
1421  height_ne = height_buffer[ end_index ];
1422 
1423  // SW Height
1424  index = ( post_y + 1 ) * num_cols + post_x;
1425  if( index < 0 )
1426  height_sw = height_buffer[ 0 ];
1427  else if( index > max_index )
1428  height_sw = height_buffer[ max_index ];
1429  else
1430  height_sw = height_buffer[ index ];
1431 
1432  // SE Height
1433  end_index = index + 1;
1434  if( end_index > max_index )
1435  height_se = height_buffer[ max_index ];
1436  else
1437  height_se = height_buffer[ end_index ];
1438 
1439  west_lon = post_x * scale_factor;
1440 
1441  // North latitude - scale_factor
1442  south_lat = ( 90 - ( post_y * scale_factor ) ) - scale_factor;
1443 
1444  /* Perform Bi-Linear Interpolation to compute Height above Ellipsoid: */
1445 
1446  if( longitude_dd < 0.0 )
1447  delta_x = ( longitude_dd + 360.0 - west_lon ) / scale_factor;
1448  else
1449  delta_x = ( longitude_dd - west_lon ) / scale_factor;
1450  delta_y = ( latitude_dd - south_lat ) / scale_factor;
1451 
1452  _1_minus_delta_x = 1 - delta_x;
1453  _1_minus_delta_y = 1 - delta_y;
1454 
1455  w_sw = _1_minus_delta_x * _1_minus_delta_y;
1456  w_se = delta_x * _1_minus_delta_y;
1457  w_ne = delta_x * delta_y;
1458  w_nw = _1_minus_delta_x * delta_y;
1459 
1460  *delta_height =
1461  height_sw * w_sw + height_se * w_se + height_ne * w_ne + height_nw * w_nw;
1462 }
1463 
1464 
1465 void GeoidLibrary::bilinearInterpolate(
1466  double longitude,
1467  double latitude,
1468  double scale_factor,
1469  int num_cols,
1470  int num_rows,
1471  float *height_buffer,
1472  double *delta_height )
1473 {
1474 /*
1475  * The private function bilinearInterpolate returns the height of the
1476  * WGS84 geoid above or below the WGS84 ellipsoid, at the
1477  * specified geodetic coordinates, using a grid of height adjustments
1478  * and the bilinear interpolation method.
1479  *
1480  * longitude : Geodetic longitude in radians (input)
1481  * latitude : Geodetic latitude in radians (input)
1482  * scale_factor : Grid scale factor (input)
1483  * num_cols : Number of columns in grid (input)
1484  * num_rows : Number of rows in grid (input)
1485  * height_buffer : Grid of height adjustments, floats (input)
1486  * delta_height : Height Adjustment, in meters. (output)
1487  *
1488  */
1489 
1490  int index;
1491  int post_x, post_y;
1492  double offset_x, offset_y;
1493  double delta_x, delta_y;
1494  double _1_minus_delta_x, _1_minus_delta_y;
1495  double latitude_dd, longitude_dd;
1496  double height_se, height_sw, height_ne, height_nw;
1497  double w_sw, w_se, w_ne, w_nw;
1498  double south_lat, west_lon;
1499  int end_index = 0;
1500  int max_index = num_rows * num_cols - 1;
1501 
1502  if( ( latitude < -PI_OVER_2 ) || ( latitude > PI_OVER_2 ) )
1503  { /* Latitude out of range */
1505  }
1506  if( ( longitude < -PI ) || ( longitude > TWO_PI ) )
1507  { /* Longitude out of range */
1509  }
1510 
1511  latitude_dd = latitude * _180_OVER_PI;
1512  longitude_dd = longitude * _180_OVER_PI;
1513 
1514  /* Compute X and Y Offsets into Geoid Height Array: */
1515  if( longitude_dd < 0.0 )
1516  {
1517  offset_x = ( longitude_dd + 360.0 ) / scale_factor;
1518  }
1519  else
1520  {
1521  offset_x = longitude_dd / scale_factor;
1522  }
1523  offset_y = ( 90 - latitude_dd ) / scale_factor;
1524 
1525  /* Find Four Nearest Geoid Height Cells for specified latitude, longitude; */
1526  /* Assumes that (0,0) of Geoid Height Array is at Northwest corner: */
1527 
1528  post_x = ( int )( offset_x );
1529  if( ( post_x + 1 ) == num_cols )
1530  post_x--;
1531  post_y = ( int )( offset_y + 1.0e-11 );
1532  if( ( post_y + 1 ) == num_rows )
1533  post_y--;
1534 
1535  // NW Height
1536  index = post_y * num_cols + post_x;
1537  if( index < 0 )
1538  height_nw = height_buffer[ 0 ];
1539  else if( index > max_index )
1540  height_nw = height_buffer[ max_index ];
1541  else
1542  height_nw = height_buffer[ index ];
1543  // NE Height
1544  end_index = index + 1;
1545  if( end_index > max_index )
1546  height_ne = height_buffer[ max_index ];
1547  else
1548  height_ne = height_buffer[ end_index ];
1549 
1550  // SW Height
1551  index = ( post_y + 1 ) * num_cols + post_x;
1552  if( index < 0 )
1553  height_sw = height_buffer[ 0 ];
1554  else if( index > max_index )
1555  height_sw = height_buffer[ max_index ];
1556  else
1557  height_sw = height_buffer[ index ];
1558 
1559  // SE Height
1560  end_index = index + 1;
1561  if( end_index > max_index )
1562  height_se = height_buffer[ max_index ];
1563  else
1564  height_se = height_buffer[ end_index ];
1565 
1566  west_lon = post_x * scale_factor;
1567 
1568  // North latitude - scale_factor
1569  south_lat = ( 90 - ( post_y * scale_factor ) ) - scale_factor;
1570 
1571  /* Perform Bi-Linear Interpolation to compute Height above Ellipsoid: */
1572 
1573  if( longitude_dd < 0.0 )
1574  delta_x = ( longitude_dd + 360.0 - west_lon ) / scale_factor;
1575  else
1576  delta_x = ( longitude_dd - west_lon ) / scale_factor;
1577  delta_y = ( latitude_dd - south_lat ) / scale_factor;
1578 
1579  _1_minus_delta_x = 1 - delta_x;
1580  _1_minus_delta_y = 1 - delta_y;
1581 
1582  w_sw = _1_minus_delta_x * _1_minus_delta_y;
1583  w_se = delta_x * _1_minus_delta_y;
1584  w_ne = delta_x * delta_y;
1585  w_nw = _1_minus_delta_x * delta_y;
1586 
1587  *delta_height =
1588  height_sw * w_sw + height_se * w_se + height_ne * w_ne + height_nw * w_nw;
1589 }
1590 
1591 
1592 void GeoidLibrary::naturalSplineInterpolate(
1593  double longitude,
1594  double latitude,
1595  double scale_factor,
1596  int num_cols,
1597  int num_rows,
1598  int max_index,
1599  float *height_buffer,
1600  double *delta_height )
1601 {
1602 /*
1603  * The private function naturalSplineInterpolate returns the height of the
1604  * WGS84 geoid above or below the WGS84 ellipsoid, at the
1605  * specified geodetic coordinates, using a grid of height adjustments
1606  * and the natural spline interpolation method.
1607  *
1608  * longitude : Geodetic longitude in radians (input)
1609  * latitude : Geodetic latitude in radians (input)
1610  * scale_factor : Grid scale factor (input)
1611  * num_cols : Number of columns in grid (input)
1612  * num_rows : Number of rows in grid (input)
1613  * height_buffer : Grid of height adjustments (input)
1614  * DeltaHeight : Height Adjustment, in meters. (output)
1615  *
1616  */
1617 
1618  int index;
1619  int post_x, post_y;
1620  int temp_offset_x, temp_offset_y;
1621  double offset_x, offset_y;
1622  double delta_x, delta_y;
1623  double delta_x2, delta_y2;
1624  double _1_minus_delta_x, _1_minus_delta_y;
1625  double _1_minus_delta_x2, _1_minus_delta_y2;
1626  double _3_minus_2_times_1_minus_delta_x, _3_minus_2_times_1_minus_delta_y;
1627  double _3_minus_2_times_delta_x, _3_minus_2_times_delta_y;
1628  double latitude_dd, longitude_dd;
1629  double height_se, height_sw, height_ne, height_nw;
1630  double w_sw, w_se, w_ne, w_nw;
1631  double south_lat, west_lon;
1632  int end_index = 0;
1633  double skip_factor = 1.0;
1634 
1635  if( ( latitude < -PI_OVER_2 ) || ( latitude > PI_OVER_2 ) )
1636  { /* latitude out of range */
1638  }
1639  if( ( longitude < -PI ) || ( longitude > TWO_PI ) )
1640  { /* longitude out of range */
1642  }
1643 
1644  latitude_dd = latitude * _180_OVER_PI;
1645  longitude_dd = longitude * _180_OVER_PI;
1646 
1647  /* Compute X and Y Offsets into Geoid Height Array: */
1648 
1649  if( longitude_dd < 0.0 )
1650  {
1651  offset_x = ( longitude_dd + 360.0 ) / scale_factor;
1652  }
1653  else
1654  {
1655  offset_x = longitude_dd / scale_factor;
1656  }
1657  offset_y = ( 90.0 - latitude_dd ) / scale_factor;
1658 
1659  /* Find Four Nearest Geoid Height Cells for specified latitude, longitude; */
1660  /* Assumes that (0,0) of Geoid Height Array is at Northwest corner: */
1661 
1662  post_x = ( int ) offset_x;
1663  if ( ( post_x + 1 ) == num_cols)
1664  post_x--;
1665  post_y = ( int )( offset_y + 1.0e-11 );
1666  if ( ( post_y + 1 ) == num_rows)
1667  post_y--;
1668 
1669  if( scale_factor == SCALE_FACTOR_30_MINUTES )
1670  {
1671  skip_factor = 2.0;
1672  num_rows = EGM96_ROWS;
1673  num_cols = EGM96_COLS;
1674  }
1675  else if( scale_factor == SCALE_FACTOR_1_DEGREE )
1676  {
1677  skip_factor = 4.0;
1678  num_rows = EGM96_ROWS;
1679  num_cols = EGM96_COLS;
1680  }
1681  else if( scale_factor == SCALE_FACTOR_2_DEGREES )
1682  {
1683  skip_factor = 8.0;
1684  num_rows = EGM96_ROWS;
1685  num_cols = EGM96_COLS;
1686  }
1687 
1688  temp_offset_x = ( int )( post_x * skip_factor );
1689  temp_offset_y = ( int )( post_y * skip_factor + 1.0e-11 );
1690  if ( ( temp_offset_x + 1 ) == num_cols )
1691  temp_offset_x--;
1692  if ( ( temp_offset_y + 1 ) == num_rows )
1693  temp_offset_y--;
1694 
1695  // NW Height
1696  index = ( int )( temp_offset_y * num_cols + temp_offset_x );
1697  if( index < 0 )
1698  height_nw = height_buffer[ 0 ];
1699  else if( index > max_index )
1700  height_nw = height_buffer[ max_index ];
1701  else
1702  height_nw = height_buffer[ index ];
1703  // NE Height
1704  end_index = index + (int)skip_factor;
1705  if( end_index < 0 )
1706  height_ne = height_buffer[ 0 ];
1707  else if( end_index > max_index )
1708  height_ne = height_buffer[ max_index ];
1709  else
1710  height_ne = height_buffer[ end_index ];
1711 
1712  // SW Height
1713  index = ( int )( ( temp_offset_y + skip_factor ) * num_cols + temp_offset_x );
1714  if( index < 0 )
1715  height_sw = height_buffer[ 0 ];
1716  else if( index > max_index )
1717  height_sw = height_buffer[ max_index ];
1718  else
1719  height_sw = height_buffer[ index ];
1720  // SE Height
1721  end_index = index + (int)skip_factor;
1722  if( end_index < 0 )
1723  height_se = height_buffer[ 0 ];
1724  else if( end_index > max_index )
1725  height_se = height_buffer[ max_index ];
1726  else
1727  height_se = height_buffer[ end_index ];
1728 
1729  west_lon = post_x * scale_factor;
1730 
1731  // North latitude - scale_factor
1732  south_lat = ( 90 - ( post_y * scale_factor ) ) - scale_factor;
1733 
1734  /* Perform Non-Linear Interpolation to compute Height above Ellipsoid: */
1735 
1736  if( longitude_dd < 0.0 )
1737  delta_x = ( longitude_dd + 360.0 - west_lon ) / scale_factor;
1738  else
1739  delta_x = ( longitude_dd - west_lon ) / scale_factor;
1740  delta_y = ( latitude_dd - south_lat ) / scale_factor;
1741 
1742  delta_x2 = delta_x * delta_x;
1743  delta_y2 = delta_y * delta_y;
1744 
1745  _1_minus_delta_x = 1 - delta_x;
1746  _1_minus_delta_y = 1 - delta_y;
1747 
1748  _1_minus_delta_x2 = _1_minus_delta_x * _1_minus_delta_x;
1749  _1_minus_delta_y2 = _1_minus_delta_y * _1_minus_delta_y;
1750 
1751  _3_minus_2_times_1_minus_delta_x = 3 - 2 * _1_minus_delta_x;
1752  _3_minus_2_times_1_minus_delta_y = 3 - 2 * _1_minus_delta_y;
1753 
1754  _3_minus_2_times_delta_x = 3 - 2 * delta_x;
1755  _3_minus_2_times_delta_y = 3 - 2 * delta_y;
1756 
1757  w_sw = _1_minus_delta_x2 * _1_minus_delta_y2 *
1758  ( _3_minus_2_times_1_minus_delta_x * _3_minus_2_times_1_minus_delta_y );
1759 
1760  w_se = delta_x2 * _1_minus_delta_y2 *
1761  ( _3_minus_2_times_delta_x * _3_minus_2_times_1_minus_delta_y );
1762 
1763  w_ne = delta_x2 * delta_y2 *
1764  ( _3_minus_2_times_delta_x * _3_minus_2_times_delta_y );
1765 
1766  w_nw = _1_minus_delta_x2 * delta_y2 *
1767  ( _3_minus_2_times_1_minus_delta_x * _3_minus_2_times_delta_y );
1768 
1769  *delta_height =
1770  height_sw * w_sw + height_se * w_se + height_ne * w_ne + height_nw * w_nw;
1771 }
1772 
1773 // CLASSIFICATION: UNCLASSIFIED