fix: Restore GeoCoding from coordinates without API \o/
This commit is contained in:
parent
07a42f4d8d
commit
94d30121d4
@ -3,6 +3,7 @@ const debug = require('debug')('soundbirder:api');
|
|||||||
const debugLocale = require('debug')('soundbirder:locale');
|
const debugLocale = require('debug')('soundbirder:locale');
|
||||||
const debugResponses = require('debug')('soundbirder:api:responses');
|
const debugResponses = require('debug')('soundbirder:api:responses');
|
||||||
const quizzController = require('./quizz');
|
const quizzController = require('./quizz');
|
||||||
|
const { getRegion } = require('./region');
|
||||||
|
|
||||||
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
||||||
|
|
||||||
@ -13,22 +14,32 @@ function getHome(req, res) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function region(req, res) {
|
||||||
|
let {lat, lon} = req.query;
|
||||||
|
lat = parseFloat(lat);
|
||||||
|
lon = parseFloat(lon);
|
||||||
|
const region = await getRegion(lat, lon);
|
||||||
|
res.json({
|
||||||
|
region
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function quizz(req, res) {
|
function quizz(req, res) {
|
||||||
debug('Generating quizz');
|
debug('Generating quizz');
|
||||||
const { region } = req.query;
|
const { region } = req.query;
|
||||||
// debug(`Coordinates: ${lat}, ${lng}`);
|
// debug(`Coordinates: ${lat}, ${lng}`);
|
||||||
const locale = req.i18n.locale;
|
const locale = req.i18n.locale;
|
||||||
debugLocale("Locale:", locale);
|
debugLocale("Locale:", locale);
|
||||||
quizzController.generateQuizz(region, locale, QUIZZ_SIZE)
|
quizzController.getQuizzCached(region, locale, QUIZZ_SIZE)
|
||||||
.then(({ species, answer, audio }) => {
|
.then(({ species, answer, audio }) => {
|
||||||
req.session.answer = answer;
|
req.session.answer = answer;
|
||||||
res.json({ species, audio });
|
res.json({ species, audio }).send();
|
||||||
debug("Quizz sent");
|
debug("Quizz sent");
|
||||||
|
quizzController.cacheQuizz(); // Prepare the next question in advance.
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
debug("Faced error while generating quizz");
|
debug("Faced error while generating quizz");
|
||||||
res.json({ error });
|
debug(error)
|
||||||
throw error;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,5 +83,6 @@ const game = {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getHome,
|
getHome,
|
||||||
game
|
game,
|
||||||
|
region
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
const { json } = require('express');
|
||||||
const { redisClient } = require('../redis');
|
const { redisClient } = require('../redis');
|
||||||
const debug = require('debug')('soundbirder:cache');
|
const debug = require('debug')('soundbirder:cache');
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ function cacheResponse(request, response) {
|
|||||||
redisClient.set(request, JSON.stringify(response));
|
redisClient.set(request, JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function getCached(request) {
|
async function getCached(request) {
|
||||||
const cached = await redisClient.get(request);
|
const cached = await redisClient.get(request);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@ -15,7 +17,22 @@ async function getCached(request) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function push(key, value) {
|
||||||
|
redisClient.lpush(key, JSON.stringify(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pop(key) {
|
||||||
|
const cached = await redisClient.lpop(key);
|
||||||
|
debug("Pop cached", cached);
|
||||||
|
if (cached) {
|
||||||
|
return JSON.parse(cached);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
cacheResponse,
|
cacheResponse,
|
||||||
getCached
|
getCached,
|
||||||
|
push,
|
||||||
|
pop
|
||||||
}
|
}
|
@ -5,20 +5,49 @@ const cache = require('./cache');
|
|||||||
const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY);
|
const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY);
|
||||||
const XenoCanto = require('@unclesamulus/xeno-canto-api');
|
const XenoCanto = require('@unclesamulus/xeno-canto-api');
|
||||||
const { choices, choice } = require('../utils/choices');
|
const { choices, choice } = require('../utils/choices');
|
||||||
// const { getRegion } = require('./region');
|
const { getRegion } = require('./region');
|
||||||
|
|
||||||
|
function quizzKey(region, locale, size) {
|
||||||
|
return `${region}:${locale}#${size}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getQuizzCached(region, locale, size) {
|
||||||
|
let key = quizzKey(region, locale, size);
|
||||||
|
let quizz = await cache.pop(key);
|
||||||
|
if (!quizz) {
|
||||||
|
quizz = generateQuizz(region, locale, size);
|
||||||
|
cache.push(key, quizz);
|
||||||
|
}
|
||||||
|
return quizz;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cacheQuizz(region, locale, size) {
|
||||||
|
let quizz = generateQuizz(region, locale, size);
|
||||||
|
let key = quizzKey(region, locale, size);
|
||||||
|
cache.push(key, quizz);
|
||||||
|
}
|
||||||
|
|
||||||
async function generateQuizz(region, locale, size) {
|
async function generateQuizz(region, locale, size) {
|
||||||
const quizz = {}
|
const quizz = {}
|
||||||
try {
|
try {
|
||||||
const speciesSelection = await getSpeciesSelection(region, size);
|
let speciesSelection = [];
|
||||||
|
do {
|
||||||
|
speciesSelection = await getSpeciesSelection(region, size);
|
||||||
|
} while (speciesSelection == []);
|
||||||
debugResponses(`Species selection: ${speciesSelection}`);
|
debugResponses(`Species selection: ${speciesSelection}`);
|
||||||
const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale);
|
const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale);
|
||||||
|
if (!speciesSelectionLocalized) {
|
||||||
|
throw 'Not localized';
|
||||||
|
}
|
||||||
debugResponses('Localized species selection:', speciesSelectionLocalized);
|
debugResponses('Localized species selection:', speciesSelectionLocalized);
|
||||||
quizz.species = speciesSelectionLocalized;
|
quizz.species = speciesSelectionLocalized;
|
||||||
debug("Got species selection", quizz.species);
|
debug("Got species selection", quizz.species);
|
||||||
let answer;
|
let answer;
|
||||||
do {
|
do {
|
||||||
answer = choice(speciesSelectionLocalized);
|
answer = choice(speciesSelectionLocalized);
|
||||||
|
if (answer === undefined) {
|
||||||
|
debug('Species', speciesSelectionLocalized);
|
||||||
|
}
|
||||||
quizz.answer = answer;
|
quizz.answer = answer;
|
||||||
quizz.audio = await getAudio(answer.sciName);
|
quizz.audio = await getAudio(answer.sciName);
|
||||||
if (quizz.audio === undefined) {
|
if (quizz.audio === undefined) {
|
||||||
@ -99,6 +128,7 @@ async function getSpeciesList(regionCode) {
|
|||||||
} else {
|
} else {
|
||||||
return eBird.product.spplist.in(regionCode)()
|
return eBird.product.spplist.in(regionCode)()
|
||||||
.then(species => {
|
.then(species => {
|
||||||
|
species = species.filter((sp) => !sp.includes(" x "));
|
||||||
cache.cacheResponse(`spplist-${regionCode}`, species);
|
cache.cacheResponse(`spplist-${regionCode}`, species);
|
||||||
return species;
|
return species;
|
||||||
})
|
})
|
||||||
@ -124,5 +154,7 @@ function getAudio(speciesScientificName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
generateQuizz
|
generateQuizz,
|
||||||
|
getQuizzCached,
|
||||||
|
cacheQuizz
|
||||||
}
|
}
|
@ -1,46 +1,41 @@
|
|||||||
require('dotenv').config();
|
require("dotenv").config();
|
||||||
const debug = require('debug')('soundbirder:api:region');
|
const debug = require("debug")("soundbirder:api:region");
|
||||||
const cache = require('./cache');
|
const cache = require("./cache");
|
||||||
const axios = require('axios');
|
const axios = require("axios");
|
||||||
|
|
||||||
const OPENCAGE_API_KEY = process.env.OPENCAGE_API_KEY;
|
const { lookUp } = require("geojson-places");
|
||||||
const OPENCAGE_API_URL = 'https://api.opencagedata.com/geocode/v1/json?q=<lat>+<lon>&key=<key>';
|
|
||||||
|
|
||||||
async function getRegion(lat, lon) {
|
async function getRegion(lat, lon) {
|
||||||
const url = OPENCAGE_API_URL
|
// Reverse geocoding to get the region info of Valladolid (Spain)
|
||||||
.replace('<lat>', lat)
|
const key = `region-${lat}-${lon}`;
|
||||||
.replace('<lon>', lon)
|
try {
|
||||||
.replace('<key>', OPENCAGE_API_KEY);
|
const cached = await cache.getCached(key);
|
||||||
try {
|
if (cached) {
|
||||||
const cached = await cache.getCached(`region-${lat}-${lon}`);
|
const { region } = cached;
|
||||||
if (cached) {
|
return region;
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
return axios.get(url).then(response => {
|
} catch (error) {
|
||||||
const { results } = response.data;
|
throw error;
|
||||||
const region = results[0].components;
|
}
|
||||||
region.country_code = region.country_code.toUpperCase();
|
const result = lookUp(lat, lon);
|
||||||
cache.cacheResponse(url, region);
|
const region = result.country_a2;
|
||||||
return { country_code, country, state, county } = region;
|
cache.cacheResponse(key, {region});
|
||||||
}).catch(error => {
|
return region;
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRegionCode({ country_code, country, state, county }) {
|
function getRegionCode({ country_code, country, state, county }) {
|
||||||
return eBird.ref.region.list('subnational1', country_code)()
|
return eBird.ref.region
|
||||||
.then(states => {
|
.list("subnational1", country_code)()
|
||||||
console.log(states);
|
.then((states) => {
|
||||||
const regionCode = states.find(region => region.name === state).code;
|
console.log(states);
|
||||||
return regionCode;
|
const regionCode = states.find((region) => region.name === state).code;
|
||||||
}).catch(error => {
|
return regionCode;
|
||||||
throw error;
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getRegion,
|
getRegion,
|
||||||
}
|
};
|
||||||
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"codegrid-js": "github:hlaw/codegrid-js",
|
||||||
"connect-redis": "^6.1.3",
|
"connect-redis": "^6.1.3",
|
||||||
"cookie-parser": "~1.4.4",
|
"cookie-parser": "~1.4.4",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
@ -20,6 +21,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"feather-icons": "^4.29.0",
|
"feather-icons": "^4.29.0",
|
||||||
|
"geojson-places": "^1.0.8",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"i18n-2": "^0.7.3",
|
"i18n-2": "^0.7.3",
|
||||||
"leaflet": "^1.8.0",
|
"leaflet": "^1.8.0",
|
||||||
@ -610,6 +612,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codegrid-js": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "git+ssh://git@github.com/hlaw/codegrid-js.git#38decfe10dced9006c6b722cc403d7c1217abe98",
|
||||||
|
"license": "WTFPL"
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@ -1264,6 +1271,14 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/geojson-places": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/geojson-places/-/geojson-places-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-4jqzFgpz9S3QZ9KsDTvokuf3qCygfhgYlBaVHhGt27bI9uzvbxacFz19qxGui1WUAwYXj39IZdtATsQ1ggtlOQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"just-clone": "^6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
@ -1527,6 +1542,11 @@
|
|||||||
"promise": "^7.0.1"
|
"promise": "^7.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/just-clone": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA=="
|
||||||
|
},
|
||||||
"node_modules/leaflet": {
|
"node_modules/leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"codegrid-js": "github:hlaw/codegrid-js",
|
||||||
"connect-redis": "^6.1.3",
|
"connect-redis": "^6.1.3",
|
||||||
"cookie-parser": "~1.4.4",
|
"cookie-parser": "~1.4.4",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
@ -19,6 +20,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"feather-icons": "^4.29.0",
|
"feather-icons": "^4.29.0",
|
||||||
|
"geojson-places": "^1.0.8",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"i18n-2": "^0.7.3",
|
"i18n-2": "^0.7.3",
|
||||||
"leaflet": "^1.8.0",
|
"leaflet": "^1.8.0",
|
||||||
|
@ -20,27 +20,23 @@ async function query(endpoint, params) {
|
|||||||
return await get(`${API_URL}/${endpoint}`, params);
|
return await get(`${API_URL}/${endpoint}`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQuizz(REGION) {
|
function getQuizz(region) {
|
||||||
return query('game/quizz', { region: REGION })
|
return query('game/quizz', { region })
|
||||||
.then(data => {
|
|
||||||
return data;
|
|
||||||
}).catch(error => {
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAnswer(speciesId) {
|
function getRegion(lat, lon) {
|
||||||
return query(`game/check`, {
|
return query('region', { lat, lon })
|
||||||
species: speciesId
|
}
|
||||||
}).then(data => {
|
|
||||||
return data;
|
function checkAnswer(species) {
|
||||||
}).catch(error => {
|
return query('game/check', {
|
||||||
throw error;
|
species: species
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
getQuizz,
|
getQuizz,
|
||||||
|
getRegion,
|
||||||
checkAnswer
|
checkAnswer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,24 +19,28 @@ function geolocationStep() {
|
|||||||
if (map != undefined)
|
if (map != undefined)
|
||||||
geolocationHandler();
|
geolocationHandler();
|
||||||
if (startButton != undefined)
|
if (startButton != undefined)
|
||||||
startButton.addEventListener('click', quizzStep);
|
startButton.addEventListener('click', regionCoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
const REGION = "FR";
|
function regionCoder() {
|
||||||
|
|
||||||
function quizzStep() {
|
|
||||||
// Start by disallowing geolocation step
|
// Start by disallowing geolocation step
|
||||||
gameMapStep.classList.add('none');
|
gameMapStep.classList.add('none');
|
||||||
// Then allow the quizz step
|
// Then allow the quizz step
|
||||||
gameQuizzStep.classList.remove('none');
|
gameQuizzStep.classList.remove('none');
|
||||||
|
|
||||||
// Retrieve coordinates from former done geolocation (TODO: fix the need of cookie)
|
// Retrieve coordinates from former done geolocation (TODO: fix the need of cookie)
|
||||||
// const coordinates = getCoordinates();
|
const coordinates = getCoordinates();
|
||||||
// const [lat, lng] = coordinates;
|
let [lat, lon] = coordinates;
|
||||||
// console.log("Coordinates: " + `${lat}, ${lng}`);
|
client.getRegion(lat, lon).then(data => {
|
||||||
client.getQuizz(REGION)
|
let { region } = data;
|
||||||
|
console.log(region);
|
||||||
|
quizzStep(region);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function quizzStep(region) {
|
||||||
|
client.getQuizz(region)
|
||||||
.then(quizz => {
|
.then(quizz => {
|
||||||
// Display the quizz
|
|
||||||
displayQuizz(quizz);
|
displayQuizz(quizz);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -11,4 +11,7 @@ router.route('/game/quizz')
|
|||||||
router.route('/game/check')
|
router.route('/game/check')
|
||||||
.get(apiController.game.check);
|
.get(apiController.game.check);
|
||||||
|
|
||||||
|
router.route('/region')
|
||||||
|
.get(apiController.region);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
@ -1,7 +1,7 @@
|
|||||||
.game
|
.game
|
||||||
.game-map-step
|
.game-map-step
|
||||||
//- #map.h-100
|
#map.h-100
|
||||||
//- button.button.geolocation-button
|
button.button.geolocation-button
|
||||||
i(data-feather="map-pin")
|
i(data-feather="map-pin")
|
||||||
button.button.start-button
|
button.button.start-button
|
||||||
i(data-feather="play")
|
i(data-feather="play")
|
||||||
|
Loading…
Reference in New Issue
Block a user