Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
normanrz committed Aug 4, 2018
0 parents commit 2e1c256
Show file tree
Hide file tree
Showing 9 changed files with 1,138 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
122 changes: 122 additions & 0 deletions hgt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const { readFile } = require("fs");
const { promisify } = require("util");

const asyncReadFile = promisify(readFile);

function avg(v1, v2, f) {
return v1 + (v2 - v1) * f;
}

function bufferStream(stream) {
return new Promise(resolve => {
const bufs = [];
stream.on("data", d => {
bufs.push(d);
});
stream.on("end", () => {
resolve(Buffer.concat(bufs));
});
});
}

class HGT {
constructor(buffer, swLatLng, options) {
this._buffer = buffer;
this._swLatLng = swLatLng;

this.options = Object.assign(
{},
{
interpolation: HGT.bilinear
},
options
);

if (buffer.length === 12967201 * 2) {
this._resolution = 1;
this._size = 3601;
} else if (buffer.length === 1442401 * 2) {
this._resolution = 3;
this._size = 1201;
} else {
throw new Error(
"Unknown tile format (1 arcsecond and 3 arcsecond supported)."
);
}
}

static async loadFile(path, swLatLng, options) {
const buffer = await asyncReadFile(path);
return new HGT(buffer, swLatLng, options);
}

static async loadStream(stream, swLatLng, options) {
const buffer = await bufferStream(stream);
return new HGT(buffer, swLatLng, options);
}

static nearestNeighbour(row, col) {
return this._rowCol(Math.round(row), Math.round(col));
}

static bilinear(row, col) {
const rowLow = Math.floor(row);
const rowHi = rowLow + 1;
const rowFrac = row - rowLow;
const colLow = Math.floor(col);
const colHi = colLow + 1;
const colFrac = col - colLow;
const v00 = this._rowCol(rowLow, colLow);
const v10 = this._rowCol(rowLow, colHi);
const v11 = this._rowCol(rowHi, colHi);
const v01 = this._rowCol(rowHi, colLow);
const v1 = avg(v00, v10, colFrac);
const v2 = avg(v01, v11, colFrac);

// console.log('row = ' + row);
// console.log('col = ' + col);
// console.log('rowLow = ' + rowLow);
// console.log('rowHi = ' + rowHi);
// console.log('rowFrac = ' + rowFrac);
// console.log('colLow = ' + colLow);
// console.log('colHi = ' + colHi);
// console.log('colFrac = ' + colFrac);
// console.log('v00 = ' + v00);
// console.log('v10 = ' + v10);
// console.log('v11 = ' + v11);
// console.log('v01 = ' + v01);
// console.log('v1 = ' + v1);
// console.log('v2 = ' + v2);

return avg(v1, v2, rowFrac);
}

getElevation(latLng) {
const size = this._size - 1;
const ll = latLng;
const row = (ll[0] - this._swLatLng[0]) * size;
const col = (ll[1] - this._swLatLng[1]) * size;

if (row < 0 || col < 0 || row > size || col > size) {
throw new Error(
"Latitude/longitude is outside tile bounds (row=" +
row +
", col=" +
col +
"; size=" +
size
);
}

return this.options.interpolation.call(this, row, col);
}

_rowCol(row, col) {
const size = this._size;
const offset = ((size - row - 1) * size + col) * 2;

return this._buffer.readInt16BE(offset);
}
}

module.exports = HGT;
22 changes: 22 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { json, send } = require("micro");
const TileSet = require("./tileset");

const cacheSize = process.env.TILE_SET_CACHE || 128;
const tileFolder = process.env.TILE_SET_PATH || __dirname;
const maxPostSize = process.env.MAX_POST_SIZE || "500kb";

const tiles = new TileSet(tileFolder, { cacheSize });

module.exports = async (req, res) => {
if (req.method !== "POST") {
return send(res, 405, { error: "Only POST allowed" });
}

const geojson = await json(req, { limit: maxPostSize });
if (!geojson || Object.keys(geojson).length === 0) {
return send(res, 400, { error: "Invalid GeoJSON" });
}

const result = await Promise.all(geojson.map(ll => tiles.getElevation(ll)));
return result;
};
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "elevation-service",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "micro index",
"test": "node test/test.js"
},
"author": "Norman Rzepka <[email protected]>",
"license": "MIT",
"dependencies": {
"lru-memoize": "^1.0.2",
"micro": "^9.3.2"
}
}
Binary file added test/N51E013.hgt.gz
Binary file not shown.
9 changes: 9 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const TileSet = require("../tileset");

(async function() {
const tileset = new TileSet(__dirname);

// Return elevation in meters above sea level.
// By default, elevation is interpolated bilinearly.
console.log(await tileset.getElevation([51.3, 13.4]));
})();
44 changes: 44 additions & 0 deletions tileset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const path = require("path");
const fs = require("fs");
const { createGunzip } = require("zlib");
const memoize = require("lru-memoize").default;

const HGT = require("./hgt");

class TileSet {
constructor(folder, options) {
this.options = Object.assign(
{},
{
cacheSize: 128,
gzip: true
},
options
);
this.getTile = memoize(this.options.cacheSize)(this._getTile.bind(this));
this._folder = folder;
}

async _getTile(lat, lng) {
const fileName =
`${lat < 0 ? "S" : "N"}${String(Math.abs(lat)).padStart(2, "0")}` +
`${lng < 0 ? "W" : "E"}${String(Math.abs(lng)).padStart(3, "0")}.hgt.gz`;

let stream = fs.createReadStream(path.join(this._folder, fileName));
if (this.options.gzip) {
stream = stream.pipe(createGunzip());
}
const tile = await HGT.loadStream(stream, [lat, lng]);
return tile;
}

async getElevation(latLng) {
const tile = await this.getTile(
Math.floor(latLng[0]),
Math.floor(latLng[1])
);
return tile.getElevation(latLng);
}
}

module.exports = TileSet;
Loading

0 comments on commit 2e1c256

Please sign in to comment.