const log = require('../../log') const nominatim = require('../../services/geocoding/nominatim') const photon = require('../../services/geocoding/photon') const axios = require('axios') const { version } = require('../../../package.json') let d = 0 // departure time let h = 0 // hit geocoding provider time (aka Latency) const geocodingController = { /** * Limit provider api usage. * From https://operations.osmfoundation.org/policies/nominatim/ * [Requirements] No heavy uses (an absolute maximum of 1 request per second). * [Websites and Apps] * - Note that the usage limits above apply per website/application: the sum of traffic by all your users should not exceed the limits. * - If at all possible, set up a proxy and also enable caching of requests. */ providerRateLimit (req, res, next, providerCache) { let a = Date.now(); // arrival time let dprev = d d = dprev + 1000 + h // console.log('a: ' + a) // console.log('dprev: ' + dprev) // console.log('d: ' + d) // if the same request was already cached skip the delay mechanism if (providerCache.get(req.params.place_details)) { if (a < d) { log.warn('More than 1 request per second to geocoding api. This from ' + req.ip + ' . The response data is served from memory-cache.') } // reset departure time because there is no need to ask provider d = dprev return next() } if (d === 0 || a > d) { // no-queue or old-queue // console.log('No queue or Old queue') // arrival time + 10ms estimated computing time d = a + 10 next() } else { // fresh queue // console.log('Fresh queue') let wait = d - a // console.log('Waiting '+ wait) log.warn('More than 1 request per second to geocoding api. This from ' + req.ip + ' . Applying ToS padding before asking to provider. The response data is now cached.') setTimeout(() => { next() }, wait) } }, async nominatimRateLimit(req, res, next) { process.env.NODE_ENV === 'test' ? next() : geocodingController.providerRateLimit(req, res, next, nominatim.cache) }, async photonRateLimit(req, res, next) { process.env.NODE_ENV === 'test' ? next() : geocodingController.providerRateLimit(req, res, next, photon.cache) }, async checkInCache (req, res, details, providerCache) { const ret = await providerCache.get(details) if (ret) { return ret } else { return } }, async queryProvider(req, res, details, provider) { let RTTstart = Date.now() // console.log('Asking Provider: ' + RTTstart) const ret = await axios.get(`${provider.endpoint(req, res)}`, { params: provider.getParams(req, res), headers: { 'User-Agent': `gancio ${version}` } }) if (ret) { let RTTend = Date.now() // console.log('Asking Provider: ' + RTTend) // Save the hit time (aka Latency) // console.log('Saving latency h: ' + h) h = (RTTend - RTTstart) / 2 } // Cache the response data provider.cache.put(details, ret.data, 1000 * 60 * 60 * 24); // console.log(cache.keys()) // console.log(cache.exportJson()) return ret.data }, async _nominatim (req, res) { const details = req.params.place_details const ret = await geocodingController.checkInCache(req, res, details, nominatim.cache) || await geocodingController.queryProvider(req, res, details, nominatim) return res.json(ret) }, async _photon (req, res) { const details = req.params.place_details const ret = await geocodingController.checkInCache(req, res, details, photon.cache) || await geocodingController.queryProvider(req, res, details, photon) return res.json(ret) }, } module.exports = geocodingController