/**
* @classdesc
* Un contrôle permettant de lancer une recherche via la BAN
*
* @constructor
* @extends {ol-ext/control/SearchBAN}
* @api
*/
import Point from 'ol/geom/Point'
import SearchBAN from 'ol-ext/control/SearchBAN'
import { mapSeries } from 'blend-promise-utils'
/**
* Search places using the French National Base Address (BAN) API.
*
* @constructor
* @extends {ol-ext/control/SearchBAN}
* @fires select
* @param {Object=} Control options.
* @param {string} options.className classname qui sera appliqué à l'élément html portant le contrôle
* @param {Element | string} [options.target] Spécifie une cible, si l'on veut que le contrôle soit géré en dehors de la map
* @param {string} [options.reverseTitle=Cliquer sur la carte...] Titre à afficher sur le tooltip du bouton de géocodage inverse
* @param {boolean} [options.reverse=false] Affiche un outil de géocodage inverse d'adresse
* @param {boolean} [options.position=true] Priorise les résultats près du centre de la carte affichée
* @param {string} [options.label=Rechercher] Libellé affiché pour la zone de recherche
* @param {string} [options.placeholder=Rechercher une adresse] Placeholder de la zone de recherche
* @param {number} [options.typing=500] le délais en ms pour lancer la recherche après une saisie utilisateur
* @param {integer} [options.minLength=3] la longueur de la chaine de recherche à partir de laquelle lancer la recherche
* @param {integer} [options.maxItems=10] Nombre de résultats affichés classés par score
* @param {integer} [options.limit=10] Nombre de résultats recherchés (utile lorsque l'on va appliquer un filtre)
* @param {integer} [options.resultZoom=16] Zoom minimal à appliquer lors de la localisation sur la carte d'un résultat
* @param {Array<string>} [options.citycodes=[]] Liste de code insee sur lesquels on va lancer la recherche (attention, une requête sera réalisée par code insee)
* @param {Array<PostFilter>} [options.postfilters=[]] Liste des filtres à appliquer sur les résultats (attention, il est possible qu'aucun résultat ne s'affiche)
*
* @param {string|undefined} options.url Url de l'api BAN, défaut "https://api-adresse.data.gouv.fr/search/"
* @param {function} [options.getTitle] une fonction qui retourne la chaine à affichée pour un résultat, par défaut le label de ce dernier
* @see {@link https://adresse.data.gouv.fr/api/}
*/
class SearchBANAdvanced extends SearchBAN {
constructor (options) {
super({
reverseTitle: 'Cliquer sur la carte...',
reverse: false,
position: true,
label: 'Rechercher',
placeholder: 'Rechercher une adresse',
maxItems: 10,
resultZoom: 16,
url: 'https://data.geopf.fr/geocodage/search/',
...options,
})
this.citycodes = options.citycodes || []
this.limit = options.limit || 10
this.maxItems = options.maxItems || 10
const postFilters = options.postFilters || []
this.postFilters = postFilters.map(filter => ({
...filter,
values: filter.values.map(value => `${value}`),
}))
/* SearchBAN.call(this, {
reverseTitle: 'Cliquer sur la carte...',
reverse: false,
position: true,
label: 'Rechercher',
placeholder: 'Rechercher une adresse',
maxItems: 10,
resultZoom: 16,
...options,
}) */
}
}
SearchBANAdvanced.prototype.sendRequest = async function sendRequest (url, data) {
return new Promise((resolve, reject) => {
try {
this.ajax(url, data, function (response) {
resolve(response)
})
} catch (error) {
reject(error)
}
})
}
/**
* Handle server response to pass the features array to the list
* @param {any} response server response
* @return {Array<any>} an array of feature
*/
SearchBANAdvanced.prototype.handleResponse = function (response) {
const filteredFeatures = response.features.filter(feature => {
if (!this.postFilters || !this.postFilters.length) {
return true
}
// Forcer les valeurs des propriétés en string pour comparaison avec les filtres transformés en string
return this.postFilters.some(filter => {
const isInFilter = filter.values.includes(`${feature.properties[filter.type]}`)
return isInFilter
})
})
return filteredFeatures
}
SearchBANAdvanced.prototype.sendRequestForAllCitycodes = async function sendRequestForAllCitycodes (url, data) {
if (!this.citycodes || this.citycodes.length === 0) {
return this.sendRequest(url, data)
}
const responses = await mapSeries(this.citycodes, (citycode) => this.sendRequest(url, { ...data, citycode }))
const allFeatures =
responses.reduce((acc, response) => {
return response
? [
...acc,
...response.features,
]
: acc
}, [])
allFeatures.sort((feature1, feature2) => feature2.properties.score - feature1.properties.score)
const features = allFeatures.splice(0, data.limit || this.limit)
return {
...responses[0],
filters: this.citycodes,
features,
}
}
/**
* @param {string} s the search string
* @return {Object} request data (as key:value)
* @api
*/
SearchBANAdvanced.prototype.requestData = function (s) {
const limit = Math.max(this.limit, this.maxItems)
const data = {
q: s,
lang: this.get('lang'),
limit,
}
// Handle position priority
if (this.get('position')) {
const view = this.getMap().getView()
let pt = new Point(view.getCenter())
pt = (pt.transform(view.getProjection(), 'EPSG:4326')).getCoordinates()
data.lon = pt[0]
data.lat = pt[1]
}
return data
}
SearchBANAdvanced.prototype.autocomplete = async function autocomplete (s, cback) {
const data = {
...this.requestData(s),
}
const url = encodeURI(this.get('url'))
const response = await this.sendRequestForAllCitycodes(url, data)
if (typeof (cback) === 'function') {
cback(this.handleResponse(response))
}
}
export default SearchBANAdvanced