terrain/Geoid.tstypescript
/* This file is mostly a straight translation of
* GeographicLib/src/Geoid.cpp from C++ to JavaScript
* by Kim Vandry <vandry@TZoNE.ORG>
*
* @license
* **
* * \file Geoid.cpp
* * \brief Implementation for GeographicLib::Geoid class
* *
* * Copyright (c) Charles Karney (2009) <charles@karney.com>
* * and licensed under the LGPL. For more information, see
* * http://geographiclib.sourceforge.net/
* **********************************************************************
*
* Geoid height grade not supported
* The files can be downloaded from here:
* http://geographiclib.sourceforge.net/1.18/geoid.html
*/
//window.myGeoid = new Geoid();
//Geoid.loadModel("./res/egm84-30.pgm")
// .then(function (model) {
// myGeoid.setModel(model);
// globe.planet.renderer.events.on("lclick", (e) => {
// let c = globe.planet.getLonLatFromPixelTerrain(e);
// let h = myGeoid.getHeight(c.lon, c.lat);
// console.log(`${c.lat} ${c.lon} h=${h}`);
// });
// })
// .catch(function (err) {
// // If we get here, the model failed to load
// console.log(err);
// });
import { LonLat } from "../LonLat";
interface IGeoidParams {
model?: GeoidModel;
src?: string | null;
}
export type GeoidModel = {
scale: number;
offset: number;
width: number;
height: number;
rlonres: number;
rlatres: number;
i: number;
rawfile: Uint8Array;
};
class Geoid {
public model: GeoidModel | null;
public src: string | null;
protected _cached_ix: number;
protected _cached_iy: number;
protected _v00: number;
protected _v01: number;
protected _v10: number;
protected _v11: number;
protected _t: number;
constructor(options: IGeoidParams = {}) {
this.model = options.model || null;
this.src = options.src || null;
this._cached_ix = 0;
this._cached_iy = 0;
this._v00 = 0;
this._v01 = 0;
this._v10 = 0;
this._v11 = 0;
this._t = 0;
}
static loadModel(url?: string | null): Promise<GeoidModel | null> {
if (!url) {
return new Promise((resolve: any) => {
resolve(null);
});
} else
return fetch(url, {})
.then((r: Response) => {
if (!r.ok) {
throw Error("Geoid model file: HTTP error " + r.status);
}
return r.arrayBuffer();
})
.then((r: ArrayBuffer) => {
if (r) {
return new Uint8Array(r);
} else {
throw Error("Geoid model file: no data from " + url);
}
})
.then(function (rawfile: Uint8Array) {
if (!(
rawfile[0] === 80 &&
rawfile[1] === 53 &&
((rawfile[2] === 13 && rawfile[3] === 10) || rawfile[2] === 10)
)) {
throw new Error("Geoid model file: no PGM header");
}
var i: number = rawfile[2] === 13 ? 4 : 3;
var offset: number | null = null;
var scale: number | null = null;
function getline(): string {
let start = i;
for (var j = i; ; j++) {
if (j >= rawfile.length) {
throw new Error("Geoid model file: missing newline in header");
}
if (rawfile[j] === 10) {
i = j + 1;
break;
}
}
if (j > start && rawfile[j - 1] === 13) j--;
return String.fromCharCode.apply(null, rawfile.slice(start, j) as any);
}
var m, s;
for (;;) {
s = getline();
if (s[0] !== "#") break;
m = s.match(/^# Offset (.*)$/);
if (m) {
offset = parseInt(m[1], 10);
if (!isFinite(offset)) {
throw new Error("Geoid model file: bad offset " + m[1]);
}
} else {
m = s.match(/^# Scale (.*)$/);
if (m) {
scale = parseFloat(m[1]);
if (!isFinite(scale)) {
throw new Error("Geoid model file: bad scale " + m[1]);
}
}
}
}
m = s.match(/^\s*(\d+)\s+(\d+)\s*$/);
let width: number = 0;
let height: number = 0;
if (m) {
width = parseInt(m[1], 10);
height = parseInt(m[2], 10);
}
if (!(m && width >= 0 && height >= 0)) {
throw new Error("Geoid model file: bad PGM width&height line");
}
let levels = parseInt(getline());
if (levels != 65535) {
throw new Error("Geoid model file: PGM file must have 65535 gray levels");
}
if (offset === null) {
throw new Error("Geoid model file: PGM file does not contain offset");
}
if (scale === null) {
throw new Error("Geoid model file: PGM file does not contain scale");
}
if (width < 2 || height < 2) {
throw new Error("Geoid model file: Raster size too small");
}
let payload_len = rawfile.length - i;
if (payload_len !== width * height * 2) {
throw new Error("Geoid model file: File has the wrong length");
}
return {
scale: scale,
offset: offset,
width: width,
height: height,
rlonres: width / 360,
rlatres: (height - 1) / 180,
i: i,
rawfile: rawfile
};
});
}
setModel(model: GeoidModel | null) {
this.model = model;
}
protected _rawval(ix: number, iy: number): number {
let model = this.model!;
if (iy < 0) {
iy = -iy;
ix += model.width / 2;
} else if (iy >= model.height) {
iy = 2 * (model.height - 1) - iy;
ix += model.width / 2;
}
if (ix < 0) {
ix += model.width;
} else if (ix >= model.width) {
ix -= model.width;
}
let k = (iy * model.width + ix) * 2 + model.i;
return (model.rawfile[k] << 8) | model.rawfile[k + 1];
}
public getHeightLonLat(lonlat: LonLat): number {
return this.getHeight(lonlat.lon, lonlat.lat);
}
public getHeight(lon: number, lat: number): number {
if (!this.model) return 0;
let model = this.model;
if (lon < 0) lon += 360.0;
let fy = (90 - lat) * model.rlatres;
let fx = lon * model.rlonres;
let iy = Math.floor(fy);
let ix = Math.floor(fx);
fx -= ix;
fy -= iy;
if (iy === model.height - 1) {
iy--;
}
if (this._cached_ix !== ix || this._cached_iy !== iy) {
this._cached_ix = ix;
this._cached_iy = iy;
this._v00 = this._rawval(ix, iy);
this._v01 = this._rawval(ix + 1, iy);
this._v10 = this._rawval(ix, iy + 1);
this._v11 = this._rawval(ix + 1, iy + 1);
}
let a = (1 - fx) * this._v00 + fx * this._v01;
let b = (1 - fx) * this._v10 + fx * this._v11;
let h = (1 - fy) * a + fy * b;
return model.offset + model.scale * h;
}
}
export { Geoid };