Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get geoid as javascript function #720

Merged
merged 13 commits into from
Jul 29, 2024
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"deploy": "npm run dist && npm run prefirebase && cd public && firebase use dstreet-305604 && firebase deploy --only hosting:app3dstreet",
"deploy:staging": "npm run dist:staging && npm run prefirebase && cd public && firebase use dev-3dstreet && firebase deploy --only hosting:dev-3dstreet",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"emulator": "npm run dist:staging && npm run prefirebase && cd public && firebase emulators:start"
},
"repository": "3dstreet/3dstreet",
"author": "[email protected]",
Expand Down
17 changes: 16 additions & 1 deletion public/firestore.indexes.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
{
"indexes": [],
"indexes": [
{
"collectionGroup": "scenes",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "author",
"order": "ASCENDING"
},
{
"fieldPath": "updateTimestamp",
"order": "DESCENDING"
}
]
}
],
"fieldOverrides": []
}
Binary file added public/functions/EGM96-15.pgm
Binary file not shown.
292 changes: 292 additions & 0 deletions public/functions/geoid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/* via https://github.com/vandry/geoidheight/blob/master/geoid.js
* This file is mostly a straight translation of
* GeographicLib/src/Geoid.cpp from C++ to JavaScript
* by Kim Vandry <[email protected]>
*
* @license
* **
* * \file Geoid.cpp
* * \brief Implementation for GeographicLib::Geoid class
* *
* * Copyright (c) Charles Karney (2009) <[email protected]>
* * and licensed under the LGPL. For more information, see
* * http://geographiclib.sourceforge.net/
* **********************************************************************
*
* Geoid height grade not supported
*/

/* Usage: as an AMD module
*
* require(['geoid'], function(Geoid) {
*
* // The AMD module object is a function that loads a geoid
* // model data file and returns a Promise which resolves
* // once that file has been downloaded and initialized.
* // The files can be downloaded from here:
* // http://geographiclib.sourceforge.net/1.18/geoid.html
*
* //Geoid("/geoids/egm2008-1.pgm") // much too slow in browser
* Geoid("/geoids/egm2008-5.pgm").then(function(get_geoid_height) {
*
* // get_geoid_height is a function which will return
* // the height of the geoid above the ellipsoid given...
*
* var geoid_height = get_geoid_height(
* // ...the latitude in degrees,
* 45.517405998,
* // the longitude in degrees
* -73.4086170327,
* // the whether to use cubic interpolation
* true
* );
* }, function(err) {
* // If we get here, the model failed to load
* console.log(err);
* });
*
* }
*/

const fs = require('fs');

const c0 = 240;
const c3 = [
[9, -18, -88, 0, 96, 90, 0, 0, -60, -20],
[-9, 18, 8, 0, -96, 30, 0, 0, 60, -20],
[9, -88, -18, 90, 96, 0, -20, -60, 0, 0],
[186, -42, -42, -150, -96, -150, 60, 60, 60, 60],
[54, 162, -78, 30, -24, -90, -60, 60, -60, 60],
[-9, -32, 18, 30, 24, 0, 20, -60, 0, 0],
[-9, 8, 18, 30, -96, 0, -20, 60, 0, 0],
[54, -78, 162, -90, -24, 30, 60, -60, 60, -60],
[-54, 78, 78, 90, 144, 90, -60, -60, -60, -60],
[9, -8, -18, -30, -24, 0, 20, 60, 0, 0],
[-9, 18, -32, 0, 24, 30, 0, 0, -60, 20],
[9, -18, -8, 0, -24, -30, 0, 0, 60, 20],
];

const c0n = 372;
const c3n = [
[0, 0, -131, 0, 138, 144, 0, 0, -102, -31],
[0, 0, 7, 0, -138, 42, 0, 0, 102, -31],
[62, 0, -31, 0, 0, -62, 0, 0, 0, 31],
[124, 0, -62, 0, 0, -124, 0, 0, 0, 62],
[124, 0, -62, 0, 0, -124, 0, 0, 0, 62],
[62, 0, -31, 0, 0, -62, 0, 0, 0, 31],
[0, 0, 45, 0, -183, -9, 0, 93, 18, 0],
[0, 0, 216, 0, 33, 87, 0, -93, 12, -93],
[0, 0, 156, 0, 153, 99, 0, -93, -12, -93],
[0, 0, -45, 0, -3, 9, 0, 93, -18, 0],
[0, 0, -55, 0, 48, 42, 0, 0, -84, 31],
[0, 0, -7, 0, -48, -42, 0, 0, 84, 31],
];

const c0s = 372;
const c3s = [
[18, -36, -122, 0, 120, 135, 0, 0, -84, -31],
[-18, 36, -2, 0, -120, 51, 0, 0, 84, -31],
[36, -165, -27, 93, 147, -9, 0, -93, 18, 0],
[210, 45, -111, -93, -57, -192, 0, 93, 12, 93],
[162, 141, -75, -93, -129, -180, 0, 93, -12, 93],
[-36, -21, 27, 93, 39, 9, 0, -93, -18, 0],
[0, 0, 62, 0, 0, 31, 0, 0, 0, -31],
[0, 0, 124, 0, 0, 62, 0, 0, 0, -62],
[0, 0, 124, 0, 0, 62, 0, 0, 0, -62],
[0, 0, 62, 0, 0, 31, 0, 0, 0, -31],
[-18, 36, -64, 0, 66, 51, 0, 0, -102, 31],
[18, -36, 2, 0, -66, -51, 0, 0, 102, 31],
];

function loadGeoidModel(filepath) {
return new Promise(function (resolve, reject) {
fs.readFile(filepath, (err, data) => {
if (err) {
return reject(new Error(`Geoid model file: File read error ${err}`));
}
resolve(new Uint8Array(data));
});
}).then(function (rawfile) {
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 = (rawfile[2] === 13) ? 4 : 3;
var offset = null;
var scale = null;
function getline() {
var 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));
}
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*$/);
var width = null;
var height = null;
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");
}
var 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");
}

var 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,
rawval: function (ix, iy) {
if (iy < 0) {
iy = -iy;
ix += width / 2;
} else if (iy >= height) {
iy = 2 * (height - 1) - iy;
ix += width / 2;
}
if (ix < 0) {
ix += width;
} else if (ix >= width) {
ix -= width;
}
var k = (iy * width + ix) * 2 + i;
return (rawfile[k] << 8) | rawfile[k + 1];
},
};
}).then(function (model) {
var cached_ix = null;
var cached_iy = null;
var v00 = null;
var v01 = null;
var v10 = null;
var v11 = null;
var t = null;

return function (lat, lon, cubic) {
if (lon < 0) lon += 360;
var fy = (90 - lat) * model.rlatres;
var fx = lon * model.rlonres;
var iy = Math.floor(fy);
var ix = Math.floor(fx);
fx -= ix;
fy -= iy;
if (iy === (model.height - 1)) {
iy--;
}

if ((cached_ix !== ix) || (cached_iy !== iy)) {
cached_ix = ix;
cached_iy = iy;
if (cubic) {
var c3x = c3;
var c0x = c0;
if (iy === 0) {
c3x = c3n;
c0x = c0n;
} else if (iy === (model.height - 2)) {
c3x = c3s;
c0x = c0s;
}
var v = [
model.rawval(ix, iy - 1),
model.rawval(ix + 1, iy - 1),
model.rawval(ix - 1, iy),
model.rawval(ix, iy),
model.rawval(ix + 1, iy),
model.rawval(ix + 2, iy),
model.rawval(ix - 1, iy + 1),
model.rawval(ix, iy + 1),
model.rawval(ix + 1, iy + 1),
model.rawval(ix + 2, iy + 1),
model.rawval(ix, iy + 2),
model.rawval(ix + 1, iy + 2)
];
t = Array.apply(null, Array(10)).map(function (_, i, arr) {
return v.reduce(function (acc, vj, j, arr) {
return acc + vj * c3x[j][i];
}, 0) / c0x;
});
} else {
v00 = model.rawval(ix, iy);
v01 = model.rawval(ix + 1, iy);
v10 = model.rawval(ix, iy + 1);
v11 = model.rawval(ix + 1, iy + 1);
}
}

var h = null;
if (cubic) {
h = t[0] +
fx * (t[1] + fx * (t[3] + fx * t[6])) +
fy * (
t[2] + fx * (t[4] + fx * t[7]) +
fy * (t[5] + fx * t[8] + fy * t[9])
);
} else {
var a = (1 - fx) * v00 + fx * v01;
var b = (1 - fx) * v10 + fx * v11;
h = (1 - fy) * a + fy * b;
}

return model.offset + model.scale * h;
};
});
};

// function that takes in lat, long, and returns the height
async function getGeoidHeightFromPGM(geoidFilePath, lat, long) {
const geoidModel = await loadGeoidModel(geoidFilePath);
kfarr marked this conversation as resolved.
Show resolved Hide resolved
const geoidHeight = geoidModel(lat, long, true);
return geoidHeight;
};
exports.getGeoidHeightFromPGM = getGeoidHeightFromPGM;
40 changes: 40 additions & 0 deletions public/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@ const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const { getAuth } = require('firebase-admin/auth');
const { getGeoidHeightFromPGM } = require('./geoid.js');
const { Client: GoogleMapsClient } = require("@googlemaps/google-maps-services-js");

exports.getGeoidHeight = functions
.runWith({ secrets: ["GOOGLE_MAPS_ELEVATION_API_KEY"] })
.https
.onCall(async (data, context) => {
const lat = parseFloat(data.lat);
const lon = parseFloat(data.lon);
const geoidFilePath = 'EGM96-15.pgm'; // Converted from USA NGA data under Public Domain license.
const geoidHeight = await getGeoidHeightFromPGM(geoidFilePath, lat, lon);
const client = new GoogleMapsClient({});

let orthometricHeight = null;
await client
.elevation({
params: {
locations: [{ lat: lat, lng: lon }],
key: process.env.GOOGLE_MAPS_ELEVATION_API_KEY,
},
timeout: 1000, // milliseconds
})
.then((r) => {
orthometricHeight = r.data.results[0].elevation;
})
.catch((e) => {
console.log(e.response.data.error_message);
});
// return json response with fields geoidModel, lat, lon, geoidHeight
return {
lat: lat,
lon: lon,
geoidHeight: geoidHeight,
geoidSource: geoidFilePath,
orthometricHeight: orthometricHeight,
orthometricSource: 'Google Maps Elevation Service',
ellipsoidalHeight: geoidHeight + orthometricHeight,
ellipsoidalSource: 'Calculated: ellipsoidalHeight = geoidHeight + orthometricHeight'
};
});

exports.getScene = functions
.https
Expand Down
Loading