Source: utils/wfs-utils.js

import GeoJSON from 'ol/format/GeoJSON'
import { bbox as bboxStrategy, tile as tileStrategy } from 'ol/loadingstrategy'
import VectorSource from 'ol/source/Vector'
import { createXYZ } from 'ol/tilegrid'
import * as MapviewerServices from '../tools/mapviewer-services'
import isEmpty from 'lodash/isEmpty'

/**
  * @param {Object?} wfsOptions Option de chargement des données via un service wfs
  * @param {String} wfsOptions.url Url du service wfs
  * @param {string} wfsOptions.geometryName nom du champs geométrie du service (défaut geo)
  * @param {Object} wfsOptions.params paramètre du service wfs
  * @param {string} wfs.params.typename Nom de la couche du service WFS à exploiter
  * @param {string} [wfsOptions.params.srsname=EPSG:3857] projection de retour des données
  * @param {string} [wfsOptions.params.version=1.1.1] version du service WFS
  * @param {string} wfsOptions.params.* autres paramètres du service WFS
  * @param {string} [wfsOptions.projection=EPSG:3857] projection de la bbox pour interroger le service
  * @param {string} [wfsOptions.geometryName=geometry] nom de la géométrie pour interroger la bbox
  * @param {boolean} [wfsOptions.bboxStrategy=false] Utiliser une stratégie de chargement par bbox (true) ou par tuile (false). Par défaut false
  * @param {boolean | Object} [wfsOptions.tileStrategy=false] Utiliser une stratégie de chargemen par tuile (false). Par défaut true
  * @param {boolean | Object} [wfsOptions.tileStrategy.tileSize=256] Taille des tuiles pour la stratégie par tuile
  * @param {string} wfsOptions.cqlFilter filtre cql
  * @param {Object<string,(string|Array)>} wfsOptions.filters objet represant des filtres a générer (exemple {code_insee:[9420,8521]}). Ignoré si cqlFilter est défini
  * @returns VectorSource | Null
  */
export function getWfsVectorSource (wfsOptions, otherSourceOptions = {}) {
  if (!wfsOptions) {
    return null
  }

  const options = {
    projection: 'EPSG:3857', // format d'interrogation de la l'etendue des données
    geometryName: 'geometry', // nom de la géométrie par défaut si rien n'est demandé
    ...wfsOptions,
    params: {
      version: '1.1.0', // 1.1.0 si rien n'est demandé
      request: 'GetFeature', // GetFeature si rien n'est demandé
      srsname: 'EPSG:3857', // format de retour des données
      // projection: 'EPSG:3857', // format d'interrogation de la l'etendue des données
      // geometryName: 'geometry', // nom de la géométrie par défaut si rien n'est demandé
      ...wfsOptions.params,
      outputFormat: 'application/json',
    },
  }

  if (isEmpty(options.geometryName)) {
    throw new Error('paramètre options.wfs.geometryName manquant')
  }

  const wfsUrl = options.url + (options.url.indexOf('?') > -1 ? '&' : '?') +
        Object.entries(options.params).map(([param, value]) => `${param}=${value}`).join('&')

  // Gestion des filtres
  let filters = options?.cqlFilter
    ? options.cqlFilter
    : options.filters
      ? Object.entries(options.filters || {}).map(([property, value]) => {
        if (Array.isArray(value)) {
          // in
          value = value.map(val => (typeof val === 'string' ? `'${val}'` : val)).join(',')
          return `${property} in (${value})`
        } else {
          // Equal
          value = typeof value === 'string' ? `'${value}'` : value
          return `${property}=${value}`
        }
      }).join(' AND ')
      : null
  filters = filters !== null && !isEmpty(filters) ? ` AND ${filters}` : ''

  // pour le wfs, si on a certaine options on passe par un loader personnalisé, sinon undefined donne le loader par défaut
  function getWfsLoader () {
    // actuellement prend en compte uniquement les headers
    if (!options?.headers) {
      return undefined
    }
    const headers = new Headers(options?.headers || {})
    return async function (extent, resolution, projection, success, failure) {
      const url = typeof this.getUrl() === 'function' ? this.getUrl()(extent, resolution, projection) : this.getUrl()
      try {
        const response = await fetch(url, { headers })
        if (!response.ok) {
          throw new Error(`Response status: ${response.status}`)
        }
        const json = await response.json()
        // le wfs service renvoi du geojson, on a juste à utiliser le service afin de les convertir en features
        const features = MapviewerServices.geoJsonToFeature(json, options.params.srsname, projection.getCode())
        this.addFeatures(features)
        success(features)
      } catch (error) {
        console.error('[datalayer-getWfsLoader]', error)
        this.removeLoadedExtent(extent)
        // si on est pas dans le scope on peut faire ceci:
        failure()
      }
    }
  }

  return new VectorSource({
    format: new GeoJSON(),
    url: function (extent, resolution, projection) {
      const dataProjection = options.projection
      const extentTransformed = MapviewerServices.convertExtent(extent, projection.getCode(), dataProjection)
      return `${wfsUrl}&cql_filter=BBOX(${options.geometryName},${extentTransformed.join(',')},'${dataProjection}')${filters}`
      /* 'https://wxs.ign.fr/parcellaire/geoportail/wfs?SERVICE=WFS&' +
            'VERSION=2.0.0&request=GetFeature&typename=CADASTRALPARCELS.PARCELLAIRE_EXPRESS:parcelle&' +
            'outputFormat=application/json&srsname=EPSG:3857&' +
            'bbox=' +
            extent.join(',') +
            ',EPSG:3857' */
    },
    strategy: options?.bboxStrategy ? bboxStrategy : tileStrategy(createXYZ({ tileSize: options?.tileStrategy?.tileSize || 256 })),
    loader: getWfsLoader(),
    ...otherSourceOptions,
  })
}