/* eslint-disable prefer-promise-reject-errors */
/* eslint vars-on-top: 0, newline-after-var: 0, consistent-return:0, no-throw-literal:0, operator-linebreak: [2, "before"], camelcase:0 */
/**
* Ensemble de fonctions utilitaires de geocodage
* @module geocodage
*/
import * as proj from 'ol/proj'
import { convertGeojsonGeometry } from './services/projections'
/**
* Permet de générer un identifiant unique sur les résultats de recherche
* @return {string} Identifiant unique généré
*/
import guid from '../utils/Guid'
// import { parse } from 'csv-parse/./sync' // L'import ne fonctionne pas
/**
* Permet d'utiliser le service de géocodage de OpenStreetMap
* http://wiki.openstreetmap.org/wiki/Nominatim
*
* @param {string} address Adresse à géocoder
* @param {string} epsg Systeme de projection. Par defaut, celui du service (généralement EPSG:4326)
* @param {string} protocol Protocol de la requête. Par défaut https
*/
function geocodeOSM (address, epsg, protocol = 'http', queryKey = 'q') {
return fetch(`${protocol}://nominatim.openstreetmap.org/search?${queryKey}=${address}&format=json&addressdetails=1`)
.then(response => response.json())
.then(json => ({
type: 'FeatureCollection',
features: json.map(item => ({
type: 'Feature',
id: guid(),
geometry: {
type: 'Point',
coordinates: proj.transform([+item.lon, +item.lat], 'EPSG:4326', epsg),
},
properties: {
type: item.type,
label: item.display_name,
postcode: item.address.postcode,
city: item.address.city,
country: item.address.country,
},
})),
}))
}
/**
* Permet d'utiliser le service de géocodage de Here
* https://developer.here.com/rest-apis/documentation/geocoder/topics/quick-start.html
* TODO: résoudre problème Invalid Credentials
* @param {string} address Adresse à géocoder
* @param {string} epsg Systeme de projection. Par defaut, celui du service (généralement EPSG:4326)
* @param {string} app_code Code de l'application pour l'authentification Here
* @param {string} app_id Id de l'application pour l'authentification Here
* @param {string} protocol Protocol de la requête. Par défaut https
* @param {string} url Url de service wfs
* @param {string} key Clé de recherche dans le wfs
*/
function geocodeHere (address, epsg, appCode, appId, protocol, queryKey = 'searchtext') {
return fetch(`${protocol}'://geocoder.cit.api.here.com/6.2/geocode.json?${queryKey}=${address}&app_code=${appCode}&app_id=${appId}`)
.then(response => response.json())
.then(json => ({
type: 'FeatureCollection',
features: json.Response.View[0].Results.map(({ Location }) => ({
type: 'Feature',
id: guid(),
geometry: {
type: 'Point',
coordinates: proj.transform([Location.DisplayPosition.Longitude, Location.DisplayPosition.Latitude], 'EPSG:4326', epsg),
},
properties: {
type: Location.LocationType,
label: Location.Address.Label,
postcode: Location.Address.PostalCode,
city: Location.Address.City,
country: Location.Address.Country,
},
})),
}))
}
/**
* Permet d'utiliser le service de géocodage de Google
* https://developers.google.com/maps/documentation/geocoding/intro
* @param {string} address Adresse à géocoder
* @param {string} epsg Systeme de projection. Par defaut, celui du service (généralement EPSG:4326)
* @param {string} key Clé d'authetification sur l'API de Google
* @param {string} protocol Protocol de la requête. Par défaut https
*/
function geocodeGoogle (address, epsg, key, protocol, queryKey = 'address') {
return fetch(`${protocol}://maps.googleapis.com/maps/api/geocode/json?${queryKey}=${address}&key=${key}`)
.then(response => response.json())
.then(json => {
switch (json.status) {
case 'ZERO_RESULTS':
return []
case 'OK':
return json.results
default:
return Promise.reject()
}
})
.then(json => ({
type: 'FeatureCollection',
features: json.map(item => ({
type: 'Feature',
id: guid(),
geometry: {
type: 'Point',
coordinates: proj.transform([item.geometry.location.lng, item.geometry.location.lat], 'EPSG:4326', epsg),
},
properties: {
type: item.types[0],
label: item.formatted_address,
...item.address_components.reduce((acc, { types, long_name }) => {
if (types.includes('postal_code')) {
acc.postcode = long_name
}
if (types.includes('locality')) {
acc.city = long_name
}
if (types.includes('country')) {
acc.country = long_name
}
return acc
}, {}),
},
})),
}))
}
/**
* Cette fonction ne fonctionne pas car la BAN ne comprend pas les données envoyée via le champs de formulaire
*/
async function geocodeCSVWithBAN (addresses, epsg, protocol) {
// const csvData = ['_code', ...addresses].join('\n')
// const formData = new FormData()
// const blob = new Blob([csvData], { type: 'text/csv' })
// formData.append('data', blob, 'address.csv')
// const response = await fetch(`${protocol}://api-adresse.data.gouv.fr/search/csv`, {
// method: 'POST', // *GET, POST, PUT, DELETE, etc.
// // mode: "cors", // no-cors, *cors, same-origin
// cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
// credentials: 'same-origin', // include, *same-origin, omit
// headers: {
// 'Content-Type': 'content-type: multipart/form-data',
// // 'Content-Type': 'application/x-www-form-urlencoded',
// },
// redirect: 'follow', // manual, *follow, error
// referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
// body: formData,
// })
// const csvResponse = await response.text()
// function parse (resp) {
// // TODO A implémenter
// return []
// }
// const records = parse(csvResponse, {
// columns: true,
// skip_empty_lines: true,
// })
// const features = records.map(record => ({
// type: 'Feature',
// id: guid(),
// geometry: {
// type: 'Point',
// coordinates: proj.transform([record.latitude, record.longitude], 'EPSG:4326', epsg),
// },
// properties: {
// ...records,
// country: 'France',
// },
// }))
return []
}
async function searchBAN (address, epsg, protocol) {
const response = await fetch(`${protocol}://data.geopf.fr/geocodage/search/?q=${address}`)
const jsonResponse = await response.json()
const features = jsonResponse.features.map(({ geometry, properties }) => ({
type: 'Feature',
id: guid(),
geometry: {
type: 'Point',
coordinates: proj.transform(geometry.coordinates, 'EPSG:4326', epsg),
},
properties: {
...properties,
country: 'France',
_code: address,
},
}))
return features
}
/**
* Permet d'utiliser le service de géocodage du gouvernement français
* https://adresse.data.gouv.fr/api/
* INFO: Actuellement en version bêta
* INFO: Uniquement sur des adresses situées en France
*
* @param {string} address Adresse à géocoder
* @param {string} epsg Systeme de projection. Par defaut, celui du service (généralement EPSG:4326)
* @param {string} protocol Protocol de la requête. Par défaut https
*/
async function geocodeBAN (address, epsg, protocol) {
const features = Array.isArray(address)
? await geocodeCSVWithBAN(address, epsg, protocol)
: await searchBAN(address, epsg, protocol)
return {
type: 'FeatureCollection',
features,
}
}
function geocodeWFS (url, queryKey, typeOfKey, address, epsg, epsgSource) {
let filters = null
if (Array.isArray(address)) {
// in
address = address.map(val => (typeOfKey === 'string' ? `'${val}'` : val)).join(',')
filters = `${queryKey} in (${address})`
} else {
// Equal
address = typeOfKey === 'string' ? `'${address}'` : address
filters = `${queryKey}=${address}`
}
return fetch(`${url}&cql_filter=${filters}`)
.then(response => response.json())
.then(json => ({
type: 'FeatureCollection',
features: json.features.map(({ geometry, properties }) => ({
type: 'Feature',
id: guid(),
geometry: convertGeojsonGeometry(geometry, epsgSource, epsg),
properties,
})),
}))
}
/**
* Permet de géocoder une adresse dans un system de projection données
* @param {Object} options Options pour la fonction
* @param {string} options.address Adresse à géocoder // liste de valeur dans le cas d'un WFS
* @param {string | undefined} options.service Nom du service de geoCodage (osm, here, google, ban). Par défaut osm
* @param {string | undefined} options.epsg Systeme de projection. Par defaut, celui du service (généralement EPSG:4326)
* @param {string | undefined} options.protocol Protocol de la requête. Par défaut https
* @param {string | undefined} options.app_code Code de l'application pour l'authentification Here. Requis pour le service Here
* @param {string | undefined} options.app_id Identifiant de l'application pour l'authentification Here. Requis pour le service Here
* @param {string | undefined} options.key Cle d'authentification de l'API de Google. Requis pour le service Google
*/
const geocode = function ({
address,
service = 'osm',
epsg = 'EPSG:4326',
epsgSource,
protocol = 'https',
app_code,
app_id,
key,
url,
queryKey,
typeOfKey,
}) {
if (!address) {
return Promise.reject('Missing parameters')
}
switch (service) {
case 'osm' :
return geocodeOSM(address, epsg, protocol, queryKey)
case 'here' :
return !app_code || !app_id
? Promise.reject('Missing parameters. Here require app_code and app_id')
: geocodeHere(address, epsg, app_code, app_id, protocol, queryKey)
case 'google' :
return !key
? Promise.reject('Missing parameters. Google require an API key')
: geocodeGoogle(address, epsg, key, protocol, queryKey)
case 'ban' :
return geocodeBAN(address, epsg, protocol, queryKey)
case 'wfs' :
if (!queryKey) {
return Promise.reject('Missing parameters. WFS require an QUERY key')
}
return geocodeWFS(url, queryKey, typeOfKey, address, epsg, epsgSource)
}
}
// Permet d'etendre le module
export default {
geocode,
}