GeographicLib: Geoid.cpp Source File

GeographicLib 2.7

Loading...
Searching...
No Matches
Geoid.cpp
Go to the documentation of this file.
1/**
2 * \file Geoid.cpp
3 * \brief Implementation for GeographicLib::Geoid class
4 *
5 * Copyright (c) Charles Karney (2009-2020) <karney@alum.mit.edu> and licensed
6 * under the MIT/X11 License. For more information, see
7 * https://geographiclib.sourceforge.io/
8 **********************************************************************/
9
10#include <GeographicLib/Geoid.hpp>
11// For getenv
12#include <cstdlib>
13#include <GeographicLib/Utility.hpp>
14
15#if !defined(GEOGRAPHICLIB_DATA)
16# if defined(_WIN32)
17# define GEOGRAPHICLIB_DATA "C:/ProgramData/GeographicLib"
18# else
19# define GEOGRAPHICLIB_DATA "/usr/local/share/GeographicLib"
20# endif
21#endif
22
23#if !defined(GEOGRAPHICLIB_GEOID_DEFAULT_NAME)
24# define GEOGRAPHICLIB_GEOID_DEFAULT_NAME "egm96-5"
25#endif
26
27#if defined(_MSC_VER)
28// Squelch warnings about unsafe use of getenv
29# pragma warning (disable: 4996)
30#endif
31
32namespace GeographicLib {
33
34 using namespace std;
35
36 // This is the transfer matrix for a 3rd order fit with a 12-point stencil
37 // with weights
38 //
39 // \x -1 0 1 2
40 // y
41 // -1 . 1 1 .
42 // 0 1 2 2 1
43 // 1 1 2 2 1
44 // 2 . 1 1 .
45 //
46 // A algorithm for n-dimensional polynomial fits is described in
47 // F. H. Lesh,
48 // Multi-dimensional least-squares polynomial curve fitting,
49 // CACM 2, 29-30 (1959).
50 // https://doi.org/10.1145/368424.368443
51 //
52 // Here's the Maxima code to generate this matrix:
53 //
54 // /* The stencil and the weights */
55 // xarr:[
56 // 0, 1,
57 // -1, 0, 1, 2,
58 // -1, 0, 1, 2,
59 // 0, 1]$
60 // yarr:[
61 // -1,-1,
62 // 0, 0, 0, 0,
63 // 1, 1, 1, 1,
64 // 2, 2]$
65 // warr:[
66 // 1, 1,
67 // 1, 2, 2, 1,
68 // 1, 2, 2, 1,
69 // 1, 1]$
70 //
71 // /* [x exponent, y exponent] for cubic fit */
72 // pows:[
73 // [0,0],
74 // [1,0],[0,1],
75 // [2,0],[1,1],[0,2],
76 // [3,0],[2,1],[1,2],[0,3]]$
77 //
78 // basisvec(x,y,pows):=map(lambda([ex],(if ex[1]=0 then 1 else x^ex[1])*
79 // (if ex[2]=0 then 1 else y^ex[2])),pows)$
80 // addterm(x,y,f,w,pows):=block([a,b,bb:basisvec(x,y,pows)],
81 // a:w*(transpose(bb).bb),
82 // b:(w*f) * bb,
83 // [a,b])$
84 //
85 // c3row(k):=block([a,b,c,pows:pows,n],
86 // n:length(pows),
87 // a:zeromatrix(n,n),
88 // b:copylist(part(a,1)),
89 // c:[a,b],
90 // for i:1 thru length(xarr) do
91 // c:c+addterm(xarr[i],yarr[i],if i=k then 1 else 0,warr[i],pows),
92 // a:c[1],b:c[2],
93 // part(transpose( a^^-1 . transpose(b)),1))$
94 // c3:[]$
95 // for k:1 thru length(warr) do c3:endcons(c3row(k),c3)$
96 // c3:apply(matrix,c3)$
97 // c0:part(ratsimp(
98 // genmatrix(yc,1,length(warr)).abs(c3).genmatrix(yd,length(pows),1)),2)$
99 // c3:c0*c3$
100
101 const int Geoid::c0_ = 240; // Common denominator
102 const int Geoid::c3_[stencilsize_ * nterms_] = {
103 9, -18, -88, 0, 96, 90, 0, 0, -60, -20,
104 -9, 18, 8, 0, -96, 30, 0, 0, 60, -20,
105 9, -88, -18, 90, 96, 0, -20, -60, 0, 0,
106 186, -42, -42, -150, -96, -150, 60, 60, 60, 60,
107 54, 162, -78, 30, -24, -90, -60, 60, -60, 60,
108 -9, -32, 18, 30, 24, 0, 20, -60, 0, 0,
109 -9, 8, 18, 30, -96, 0, -20, 60, 0, 0,
110 54, -78, 162, -90, -24, 30, 60, -60, 60, -60,
111 -54, 78, 78, 90, 144, 90, -60, -60, -60, -60,
112 9, -8, -18, -30, -24, 0, 20, 60, 0, 0,
113 -9, 18, -32, 0, 24, 30, 0, 0, -60, 20,
114 9, -18, -8, 0, -24, -30, 0, 0, 60, 20,
115 };
116
117 // Like c3, but with the coeffs of x, x^2, and x^3 constrained to be zero.
118 // Use this at the N pole so that the height in independent of the longitude
119 // there.
120 //
121 // Here's the Maxima code to generate this matrix (continued from above).
122 //
123 // /* figure which terms to exclude so that fit is indep of x at y=0 */
124 // mask:part(zeromatrix(1,length(pows)),1)+1$
125 // for i:1 thru length(pows) do
126 // if pows[i][1]>0 and pows[i][2]=0 then mask[i]:0$
127 //
128 // /* Same as c3row but with masked pows. */
129 // c3nrow(k):=block([a,b,c,powsa:[],n,d,e],
130 // for i:1 thru length(mask) do if mask[i]>0 then
131 // powsa:endcons(pows[i],powsa),
132 // n:length(powsa),
133 // a:zeromatrix(n,n),
134 // b:copylist(part(a,1)),
135 // c:[a,b],
136 // for i:1 thru length(xarr) do
137 // c:c+addterm(xarr[i],yarr[i],if i=k then 1 else 0,warr[i],powsa),
138 // a:c[1],b:c[2],
139 // d:part(transpose( a^^-1 . transpose(b)),1),
140 // e:[],
141 // for i:1 thru length(mask) do
142 // if mask[i]>0 then (e:endcons(first(d),e),d:rest(d)) else e:endcons(0,e),
143 // e)$
144 // c3n:[]$
145 // for k:1 thru length(warr) do c3n:endcons(c3nrow(k),c3n)$
146 // c3n:apply(matrix,c3n)$
147 // c0n:part(ratsimp(
148 // genmatrix(yc,1,length(warr)).abs(c3n).genmatrix(yd,length(pows),1)),2)$
149 // c3n:c0n*c3n$
150
151 const int Geoid::c0n_ = 372; // Common denominator
152 const int Geoid::c3n_[stencilsize_ * nterms_] = {
153 0, 0, -131, 0, 138, 144, 0, 0, -102, -31,
154 0, 0, 7, 0, -138, 42, 0, 0, 102, -31,
155 62, 0, -31, 0, 0, -62, 0, 0, 0, 31,
156 124, 0, -62, 0, 0, -124, 0, 0, 0, 62,
157 124, 0, -62, 0, 0, -124, 0, 0, 0, 62,
158 62, 0, -31, 0, 0, -62, 0, 0, 0, 31,
159 0, 0, 45, 0, -183, -9, 0, 93, 18, 0,
160 0, 0, 216, 0, 33, 87, 0, -93, 12, -93,
161 0, 0, 156, 0, 153, 99, 0, -93, -12, -93,
162 0, 0, -45, 0, -3, 9, 0, 93, -18, 0,
163 0, 0, -55, 0, 48, 42, 0, 0, -84, 31,
164 0, 0, -7, 0, -48, -42, 0, 0, 84, 31,
165 };
166
167 // Like c3n, but y -> 1-y so that h is independent of x at y = 1. Use this
168 // at the S pole so that the height in independent of the longitude there.
169 //
170 // Here's the Maxima code to generate this matrix (continued from above).
171 //
172 // /* Transform c3n to c3s by transforming y -> 1-y */
173 // vv:[
174 // v[11],v[12],
175 // v[7],v[8],v[9],v[10],
176 // v[3],v[4],v[5],v[6],
177 // v[1],v[2]]$
178 // poly:expand(vv.(c3n/c0n).transpose(basisvec(x,1-y,pows)))$
179 // c3sf[i,j]:=coeff(coeff(coeff(poly,v[i]),x,pows[j][1]),y,pows[j][2])$
180 // c3s:genmatrix(c3sf,length(vv),length(pows))$
181 // c0s:part(ratsimp(
182 // genmatrix(yc,1,length(warr)).abs(c3s).genmatrix(yd,length(pows),1)),2)$
183 // c3s:c0s*c3s$
184
185 const int Geoid::c0s_ = 372; // Common denominator
186 const int Geoid::c3s_[stencilsize_ * nterms_] = {
187 18, -36, -122, 0, 120, 135, 0, 0, -84, -31,
188 -18, 36, -2, 0, -120, 51, 0, 0, 84, -31,
189 36, -165, -27, 93, 147, -9, 0, -93, 18, 0,
190 210, 45, -111, -93, -57, -192, 0, 93, 12, 93,
191 162, 141, -75, -93, -129, -180, 0, 93, -12, 93,
192 -36, -21, 27, 93, 39, 9, 0, -93, -18, 0,
193 0, 0, 62, 0, 0, 31, 0, 0, 0, -31,
194 0, 0, 124, 0, 0, 62, 0, 0, 0, -62,
195 0, 0, 124, 0, 0, 62, 0, 0, 0, -62,
196 0, 0, 62, 0, 0, 31, 0, 0, 0, -31,
197 -18, 36, -64, 0, 66, 51, 0, 0, -102, 31,
198 18, -36, 2, 0, -66, -51, 0, 0, 102, 31,
199 };
200
201 Geoid::Geoid(const std::string& name, const std::string& path, bool cubic,
202 bool threadsafe)
203 : _name(name)
204 , _dir(path)
205 , _cubic(cubic)
206 , _a( Constants::WGS84_a() )
207 , _e2( (2 - Constants::WGS84_f()) * Constants::WGS84_f() )
208 , _degree( Math::degree() )
209 , _eps( sqrt(numeric_limits<real>::epsilon()) )
210 , _threadsafe(false) // Set after cache is read
211 {
212 static_assert(sizeof(pixel_t) == pixel_size_, "pixel_t has the wrong size");
213 if (_dir.empty())
214 _dir = DefaultGeoidPath();
215 _filename = _dir + "/" + _name + (pixel_size_ != 4 ? ".pgm" : ".pgm4");
216 _file.open(_filename.c_str(), ios::binary);
217 if (!(_file.good()))
218 throw GeographicErr("File not readable " + _filename);
219 string s;
220 if (!(getline(_file, s) && s == "P5"))
221 throw GeographicErr("File not in PGM format " + _filename);
222 _offset = numeric_limits<real>::max();
223 _scale = 0;
224 _maxerror = _rmserror = -1;
225 _description = "NONE";
226 _datetime = "UNKNOWN";
227 while (getline(_file, s)) {
228 if (s.empty())
229 continue;
230 if (s[0] == '#') {
231 istringstream is(s);
232 string commentid, key;
233 if (!(is >> commentid >> key) || commentid != "#")
234 continue;
235 if (key == "Description" || key == "DateTime") {
236 string::size_type p =
237 s.find_first_not_of(" \t", unsigned(is.tellg()));
238 if (p != string::npos)
239 (key == "Description" ? _description : _datetime) = s.substr(p);
240 } else if (key == "Offset") {
241 if (!(is >> _offset))
242 throw GeographicErr("Error reading offset " + _filename);
243 } else if (key == "Scale") {
244 if (!(is >> _scale))
245 throw GeographicErr("Error reading scale " + _filename);
246 } else if (key == (_cubic ? "MaxCubicError" : "MaxBilinearError")) {
247 // It's not an error if the error can't be read
248 is >> _maxerror;
249 } else if (key == (_cubic ? "RMSCubicError" : "RMSBilinearError")) {
250 // It's not an error if the error can't be read
251 is >> _rmserror;
252 }
253 } else {
254 istringstream is(s);
255 if (!(is >> _width >> _height))
256 throw GeographicErr("Error reading raster size " + _filename);
257 break;
258 }
259 }
260 {
261 unsigned maxval;
262 if (!(_file >> maxval))
263 throw GeographicErr("Error reading maxval " + _filename);
264 if (maxval != pixel_max_)
265 throw GeographicErr("Incorrect value of maxval " + _filename);
266 // Add 1 for whitespace after maxval
267 _datastart = (unsigned long long)(_file.tellg()) + 1ULL;
268 _swidth = (unsigned long long)(_width);
269 }
270 if (_offset == numeric_limits<real>::max())
271 throw GeographicErr("Offset not set " + _filename);
272 if (_scale == 0)
273 throw GeographicErr("Scale not set " + _filename);
274 if (_scale < 0)
275 throw GeographicErr("Scale must be positive " + _filename);
276 if (_height < 2 || _width < 2)
277 // Coarsest grid spacing is 180deg.
278 throw GeographicErr("Raster size too small " + _filename);
279 if (_width & 1)
280 // This is so that longitude grids can be extended thru the poles.
281 throw GeographicErr("Raster width is odd " + _filename);
282 if (!(_height & 1))
283 // This is so that latitude grid includes the equator.
284 throw GeographicErr("Raster height is even " + _filename);
285 _file.seekg(0, ios::end);
286 if (!_file.good() ||
287 _datastart + pixel_size_ * _swidth * (unsigned long long)(_height) !=
288 (unsigned long long)(_file.tellg()))
289 // Possibly this test should be "<" because the file contains, e.g., a
290 // second image. However, for now we are more strict.
291 throw GeographicErr("File has the wrong length " + _filename);
292 _rlonres = _width / real(Math::td);
293 _rlatres = (_height - 1) / real(Math::hd);
294 _cache = false;
295 _ix = _width;
296 _iy = _height;
297 // Ensure that file errors throw exceptions
298 _file.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit);
299 if (threadsafe) {
300 CacheAll();
301 _file.close();
302 _threadsafe = true;
303 }
304 }
305
306 Math::real Geoid::height(real lat, real lon) const {
307 lat = Math::LatFix(lat);
308 if (isnan(lat) || isnan(lon)) {
309 return Math::NaN();
310 }
311 lon = Math::AngNormalize(lon);
312 real
313 fx = lon * _rlonres,
314 fy = -lat * _rlatres;
315 int
316 ix = int(floor(fx)),
317 iy = min((_height - 1)/2 - 1, int(floor(fy)));
318 fx -= ix;
319 fy -= iy;
320 iy += (_height - 1)/2;
321 ix += ix < 0 ? _width : (ix >= _width ? -_width : 0);
322 real v00 = 0, v01 = 0, v10 = 0, v11 = 0;
323 real t[nterms_];
324
325 if (_threadsafe || !(ix == _ix && iy == _iy)) {
326 if (!_cubic) {
327 v00 = rawval(ix , iy );
328 v01 = rawval(ix + 1, iy );
329 v10 = rawval(ix , iy + 1);
330 v11 = rawval(ix + 1, iy + 1);
331 } else {
332 real v[stencilsize_];
333 int k = 0;
334 v[k++] = rawval(ix , iy - 1);
335 v[k++] = rawval(ix + 1, iy - 1);
336 v[k++] = rawval(ix - 1, iy );
337 v[k++] = rawval(ix , iy );
338 v[k++] = rawval(ix + 1, iy );
339 v[k++] = rawval(ix + 2, iy );
340 v[k++] = rawval(ix - 1, iy + 1);
341 v[k++] = rawval(ix , iy + 1);
342 v[k++] = rawval(ix + 1, iy + 1);
343 v[k++] = rawval(ix + 2, iy + 1);
344 v[k++] = rawval(ix , iy + 2);
345 v[k++] = rawval(ix + 1, iy + 2);
346
347 const int* c3x = iy == 0 ? c3n_ : (iy == _height - 2 ? c3s_ : c3_);
348 int c0x = iy == 0 ? c0n_ : (iy == _height - 2 ? c0s_ : c0_);
349 for (unsigned i = 0; i < nterms_; ++i) {
350 t[i] = 0;
351 for (unsigned j = 0; j < stencilsize_; ++j)
352 t[i] += v[j] * c3x[nterms_ * j + i];
353 t[i] /= c0x;
354 }
355 }
356 } else { // same cell; used cached coefficients
357 if (!_cubic) {
358 v00 = _v00;
359 v01 = _v01;
360 v10 = _v10;
361 v11 = _v11;
362 } else
363 copy(_t, _t + nterms_, t);
364 }
365 if (!_cubic) {
366 real
367 a = (1 - fx) * v00 + fx * v01,
368 b = (1 - fx) * v10 + fx * v11,
369 c = (1 - fy) * a + fy * b,
370 h = _offset + _scale * c;
371 if (!_threadsafe) {
372 _ix = ix;
373 _iy = iy;
374 _v00 = v00;
375 _v01 = v01;
376 _v10 = v10;
377 _v11 = v11;
378 }
379 return h;
380 } else {
381 real h = t[0] + fx * (t[1] + fx * (t[3] + fx * t[6])) +
382 fy * (t[2] + fx * (t[4] + fx * t[7]) +
383 fy * (t[5] + fx * t[8] + fy * t[9]));
384 h = _offset + _scale * h;
385 if (!_threadsafe) {
386 _ix = ix;
387 _iy = iy;
388 copy(t, t + nterms_, _t);
389 }
390 return h;
391 }
392 }
393
394 void Geoid::CacheClear() const {
395 if (!_threadsafe) {
396 _cache = false;
397 try {
398 _data.clear();
399 // Use swap to release memory back to system
400 vector< vector<pixel_t> >().swap(_data);
401 }
402 catch (const exception&) {
403 }
404 }
405 }
406
407 void Geoid::CacheArea(real south, real west, real north, real east) const {
408 if (_threadsafe)
409 throw GeographicErr("Attempt to change cache of threadsafe Geoid");
410 if (south > north) {
411 CacheClear();
412 return;
413 }
414 south = Math::LatFix(south);
415 north = Math::LatFix(north);
416 west = Math::AngNormalize(west); // west in [-180, 180)
417 east = Math::AngNormalize(east);
418 if (east <= west)
419 east += Math::td; // east - west in (0, 360]
420 int
421 iw = int(floor(west * _rlonres)),
422 ie = int(floor(east * _rlonres)),
423 in = int(floor(-north * _rlatres)) + (_height - 1)/2,
424 is = int(floor(-south * _rlatres)) + (_height - 1)/2;
425 in = max(0, min(_height - 2, in));
426 is = max(0, min(_height - 2, is));
427 is += 1;
428 ie += 1;
429 if (_cubic) {
430 in -= 1;
431 is += 1;
432 iw -= 1;
433 ie += 1;
434 }
435 if (ie - iw >= _width - 1) {
436 // Include entire longitude range
437 iw = 0;
438 ie = _width - 1;
439 } else {
440 ie += iw < 0 ? _width : (iw >= _width ? -_width : 0);
441 iw += iw < 0 ? _width : (iw >= _width ? -_width : 0);
442 }
443 int oysize = int(_data.size());
444 _xsize = ie - iw + 1;
445 _ysize = is - in + 1;
446 _xoffset = iw;
447 _yoffset = in;
448
449 try {
450 _data.resize(_ysize, vector<pixel_t>(_xsize));
451 for (int iy = min(oysize, _ysize); iy--;)
452 _data[iy].resize(_xsize);
453 }
454 catch (const bad_alloc&) {
455 CacheClear();
456 throw GeographicErr("Insufficient memory for caching " + _filename);
457 }
458
459 try {
460 for (int iy = in; iy <= is; ++iy) {
461 int iy1 = iy, iw1 = iw;
462 if (iy < 0 || iy >= _height) {
463 // Allow points "beyond" the poles to support interpolation
464 iy1 = iy1 < 0 ? -iy1 : 2 * (_height - 1) - iy1;
465 iw1 += _width/2;
466 if (iw1 >= _width)
467 iw1 -= _width;
468 }
469 int xs1 = min(_width - iw1, _xsize);
470 filepos(iw1, iy1);
471 Utility::readarray<pixel_t, pixel_t, true>
472 (_file, &(_data[iy - in][0]), xs1);
473 if (xs1 < _xsize) {
474 // Wrap around longitude = 0
475 filepos(0, iy1);
476 Utility::readarray<pixel_t, pixel_t, true>
477 (_file, &(_data[iy - in][xs1]), _xsize - xs1);
478 }
479 }
480 _cache = true;
481 }
482 catch (const exception& e) {
483 CacheClear();
484 throw GeographicErr(string("Error filling cache ") + e.what());
485 }
486 }
487
488 string Geoid::DefaultGeoidPath() {
489 string path;
490 char* geoidpath = getenv("GEOGRAPHICLIB_GEOID_PATH");
491 if (geoidpath)
492 path = string(geoidpath);
493 if (!path.empty())
494 return path;
495 char* datapath = getenv("GEOGRAPHICLIB_DATA");
496 if (datapath)
497 path = string(datapath);
498 return (!path.empty() ? path : string(GEOGRAPHICLIB_DATA)) + "/geoids";
499 }
500
501 string Geoid::DefaultGeoidName() {
502 string name;
503 char* geoidname = getenv("GEOGRAPHICLIB_GEOID_NAME");
504 if (geoidname)
505 name = string(geoidname);
506 return !name.empty() ? name : string(GEOGRAPHICLIB_GEOID_DEFAULT_NAME);
507 }
508
509} // namespace GeographicLib
real
GeographicLib::Math::real real
Definition Geod3Solve.cpp:25
GEOGRAPHICLIB_DATA
#define GEOGRAPHICLIB_DATA
Definition Geoid.cpp:19
GEOGRAPHICLIB_GEOID_DEFAULT_NAME
#define GEOGRAPHICLIB_GEOID_DEFAULT_NAME
Definition Geoid.cpp:24
Geoid.hpp
Header for GeographicLib::Geoid class.
Utility.hpp
Header for GeographicLib::Utility class.
GeographicLib::Constants
Constants needed by GeographicLib
Definition Constants.hpp:84
GeographicLib::GeographicErr
Exception handling for GeographicLib.
Definition Constants.hpp:344
GeographicLib::Geoid::DefaultGeoidPath
static std::string DefaultGeoidPath()
Definition Geoid.cpp:488
GeographicLib::Geoid::CacheAll
void CacheAll() const
Definition Geoid.hpp:258
GeographicLib::Geoid::DefaultGeoidName
static std::string DefaultGeoidName()
Definition Geoid.cpp:501
GeographicLib::Geoid::CacheArea
void CacheArea(real south, real west, real north, real east) const
Definition Geoid.cpp:407
GeographicLib::Geoid::CacheClear
void CacheClear() const
Definition Geoid.cpp:394
GeographicLib::Math
Mathematical functions needed by GeographicLib.
Definition Math.hpp:82
GeographicLib::Math::LatFix
static T LatFix(T x)
Definition Math.hpp:303
GeographicLib::Math::AngNormalize
static T AngNormalize(T x)
Definition Math.cpp:69
GeographicLib::Math::td
static constexpr int td
degrees per turn
Definition Math.hpp:146
GeographicLib::Math::NaN
static T NaN()
Definition Math.cpp:301
GeographicLib::Math::hd
static constexpr int hd
degrees per half turn
Definition Math.hpp:145
GeographicLib::Math::real
double real
Definition Math.hpp:115
GeographicLib::Utility::readarray
static void readarray(std::istream &str, IntT array[], size_t num)
Definition Utility.hpp:333
GeographicLib
Namespace for GeographicLib.
Definition Accumulator.cpp:12
std
Definition NearestNeighbor.hpp:806
std::swap
void swap(GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &a, GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &b)
Definition NearestNeighbor.hpp:819