Source: control/SearchBANAdvanced.js

/**
 * @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