tatry
Version:
Elevation API server.
157 lines (129 loc) • 3.71 kB
JavaScript
const Router = require('router');
const bodyParser = require('body-parser');
const querystring = require('querystring');
const parseurl = require('parseurl');
const isFresh = require('fresh');
const calculateEtag = require('etag');
const makeMetas = require('./metas');
const makeFileBag = require('./file-bag');
const makeLookup = require('./lookup');
const { INVALID_ELEVATION } = require('./interpolate');
module.exports = tatry;
// V1 support - points as { latitude, longitude }
function fromQueryLocations(req, res, next) {
const { locations } = req.query;
req.lls = locations.split('|').map(point => {
const latlon = point.split(',').map(parseFloat);
return [ latlon[1], latlon[0] ];
});
next();
}
function fromBodyLocations(req, res, next) {
const { locations } = req.body;
req.lls = locations.map(({ longitude, latitude }) => [ longitude, latitude ]);
next();
}
function collateResultsLocations(lls, elevations) {
return lls.map((ll, i) => ({
latitude: ll[1],
longitude: ll[0],
elevation: elevations[i]
}));
}
// V2 support - points as [ longitude, latitude ]
function fromQueryLls(req, res, next) {
const { lls } = req.query;
req.lls = lls.split('|').map(point => point.split(',').map(parseFloat));
next();
}
function fromBodyLls(req, res, next) {
const { lls = [] } = req.body;
req.lls = lls;
next();
}
function collateResultsLls(lls, elevations) {
return lls.map((ll, i) => elevations[i] !== INVALID_ELEVATION ?
{ ll, elevation: elevations[i] } :
{ ll }
);
}
function lookup(req, res, next) {
const { timings } = res.locals;
const { lookup, lls } = req;
timings.start('lookup');
lookup(lls).then(elevations => {
req.item = {
results: req.collateResults(lls, elevations)
};
timings.end('lookup');
next();
}, next);
}
function cacheControl(req, res, next) {
// good for a week
res.setHeader('Cache-Control', 'public, max-age=604800');
next();
}
function send(req, res) {
const data = Buffer.from(JSON.stringify(req.item));
const len = data.byteLength;
const etag = calculateEtag(data);
if (isFresh(req.headers, { etag })) {
// respond with 304...
res.statusCode = 304;
return res.end();
}
// normal response
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.setHeader('Content-Length', len);
res.setHeader('ETag', etag);
return res.end(data);
}
function makeRouter({ setLookup, collateResults, fromQuery, fromBody }) {
const router = new Router({
strict: true,
caseSensitive: true
});
function setCollate(req, res, next) {
req.collateResults = collateResults;
next();
}
function parseQuery(req, res, next) {
const { query } = parseurl(req);
req.query = querystring.parse(query);
next();
}
const parseBody = bodyParser.json({
limit: process.env.TATRY_BODY_LIMIT || '250kb'
});
router.use(setLookup);
router.use(setCollate);
router.get('/lookup', parseQuery, fromQuery, lookup, cacheControl, send);
router.post('/lookup', parseBody, fromBody, lookup, send);
return router;
}
function tatry(dataDir) {
const pLookup = makeMetas(dataDir)
.then(makeFileBag)
.then(fileBag => makeLookup({ fileBag }));
function setLookup(req, res, next) {
pLookup.then(({ lookup }) => {
req.lookup = lookup;
next();
}, next);
}
return {
v1: makeRouter({
setLookup,
collateResults: collateResultsLocations,
fromQuery: fromQueryLocations,
fromBody: fromBodyLocations
}),
v2: makeRouter({
setLookup,
collateResults: collateResultsLls,
fromQuery: fromQueryLls,
fromBody: fromBodyLls
})
};
}