import { App } from '../../gogocarto' import { FilterAbstractComponent } from './filter-abstract.component' declare let L: any interface CustomJQuery extends JQuery { gogoAutocomplete(options: JQueryUI.AutocompleteOptions): JQuery gogoAutocomplete(methodName: 'search', value?: string): void } interface FrenchCounty { code: string nom: string codeRegion: string } interface FrenchState { code: string nom: string } interface AjaxRequestData { q: string limit: number lang: string bbox?: string } export class FilterAreaComponent extends FilterAbstractComponent { isFirstLoading: boolean frenchAdministrativeBoundariesIsLoading: boolean frenchCounties: FrenchCounty[] frenchStates: FrenchState[] initialize(): void { const self = this $.widget('custom.gogoAutocomplete', $.ui.autocomplete, { _resizeMenu: () => { // Intentionally left empty }, _renderItem: (ul, item) => { return self.renderResults(ul, item) }, }) this.isFirstLoading = true this.frenchAdministrativeBoundariesIsLoading = false this.frenchCounties = null this.frenchStates = null this.loadFrenchAdministrativeBoundaries() let geocodingBoundsBycountryCodes = [] let bounds = L.latLngBounds([]) let stringBounds = null let bounded = false switch (App.config.map.geocoding.geocodingBoundsType) { case 'countryCodes': geocodingBoundsBycountryCodes = App.config.map.geocoding.geocodingBoundsByCountryCodes.toLowerCase().split(',') break case 'defaultView': bounded = true bounds = App.config.map.defaultBounds break case 'viewPicker': if (App.config.map.geocoding.geocodingBounds) { bounded = true bounds = App.config.map.geocoding.geocodingBounds } break } if (bounded) { // Photon Expected format is minLon,minLat,maxLon,maxLat. const southWest = bounds.getSouthWest() const northEast = bounds.getNorthEast() stringBounds = southWest.lng + ', ' + southWest.lat + ', ' + northEast.lng + ', ' + northEast.lat } const $filterAreaInput = $('.filter-area-input-' + self.filter.id) as CustomJQuery $filterAreaInput.gogoAutocomplete({ minLength: 3, appendTo: '.filter-area-input-container-' + $filterAreaInput.attr('data-filter-id'), source: function (params, response) { let searchTerm = params.term if (params.term.toLowerCase().startsWith('cc ')) { searchTerm = 'Communauté de communes ' + params.term.slice(3) } if (params.term.toLowerCase().startsWith('ca ')) { searchTerm = "Communauté d'agglomération " + params.term.slice(3) } const requestData: AjaxRequestData = { q: searchTerm, limit: 20, lang: 'fr', } if (bounded) { requestData.bbox = stringBounds } $.ajax({ url: 'https://photon.komoot.io/api?layer=city&layer=county&layer=state&layer=country&layer=locality&layer=district&layer=other', dataType: 'json', cache: true, data: requestData, success: function (data) { self.isFirstLoading = false try { let features = data.features // filtering osm entities features = features.filter((feature) => { return feature.properties.type !== 'locality' || feature.properties.osm_value === 'local_authority' }) features = features.filter((feature) => { return feature.properties.type !== 'city' || feature.properties.osm_key === 'place' }) features = features.filter((feature) => { return feature.properties.type !== 'district' || feature.properties.osm_value === 'city' }) // filter by country codes is not possible directly by requesting, so we filter the response if (geocodingBoundsBycountryCodes.length > 0) { features = features.filter((feature) => { return ( feature?.properties?.countrycode && geocodingBoundsBycountryCodes.includes(feature.properties.countrycode.toLowerCase()) ) }) } const resultItems = [] features.forEach((feature) => { resultItems.push({ id: feature.properties.osm_id, label: feature.properties.name, data: JSON.stringify({ name: feature.properties.name, type: feature.properties.type, county: feature.properties.county, state: feature.properties.state, country: feature.properties.country, postCode: feature.properties?.postcode?.slice(0, 5), hasResults: true, }), bbox: feature.properties.extent, }) }) response(resultItems) } catch (error) { console.error('Error processing results:', error) response([]) // Return an empty array in case of error } }, error: function (data) { console.log('Error during ajax process:', data) }, }) }, select: function (e, ui) { $filterAreaInput.val(ui.item.value) $filterAreaInput.attr('data-id', ui.item.id) const $li = $(e.currentTarget).find('li[data-id="' + ui.item.id + '"]') const meta = JSON.parse(ui.item.data) self.filter.currentValue = { name: meta.name, county: meta.county, state: meta.state, country: meta.country, codeEpci: $li.attr('data-epci-code'), departements: $li.attr('data-departements'), regions: $li.attr('data-regions'), } if (meta.postCode) { self.filter.currentValue.postCode = meta.postCode } self.emitFilterSet() if (self.filter.options?.autoZoom && ui.item?.bbox) { App.mapComponent.fitBounds( L.latLngBounds(L.latLng(ui.item.bbox[1], ui.item.bbox[0]), L.latLng(ui.item.bbox[3], ui.item.bbox[2])), true, ) } }, response: function (event, ui) { if (!ui.content.length) { const noResult = { data: '{ "hasResults": false }' } ui.content.push(noResult) } }, }) } // Needed to process french epecis processGeoApiGouvList(dataName, apiType) { const url = 'https://geo.api.gouv.fr/' + apiType if (!this[dataName]) { fetch(url, { method: 'get', headers: { 'Content-Type': 'application/json' } }) .then((response) => { if (!response.ok) { throw new Error(url + ' : Network response was not ok') } return response.json() }) .then((json) => { this[dataName] = json }) } } // Needed to process french epcis loadFrenchAdministrativeBoundaries() { if (this.frenchAdministrativeBoundariesIsLoading === false) { this.frenchAdministrativeBoundariesIsLoading = true this.processGeoApiGouvList('frenchCounties', 'departements') this.processGeoApiGouvList('frenchStates', 'regions') } } searchCommune($li, name, meta, postCode) { fetch('https://geo.api.gouv.fr/communes?nom=' + name + (postCode ? '&codePostal=' + postCode : ''), { method: 'get', headers: { 'Content-Type': 'application/json' }, }) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok') } return response.json() }) .then((results) => { if (results.length > 0 && results[0]?.codeEpci) { const codeEpci = results[0].codeEpci $li.attr('data-epci-code', codeEpci) fetch('https://geo.api.gouv.fr/epcis?code=' + codeEpci, { method: 'get', headers: { 'Content-Type': 'application/json' }, }) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok') } return response.json() }) .then((results) => { if (results.length > 0 && results[0].nom) { const epci = results[0].nom $li.attr('data-epci', epci) $li.attr('title', name + '\n' + meta + '\n' + epci) // $li.append( `
${epci}
` ); } }) } }) } searchEpci($li, name, meta) { fetch('https://geo.api.gouv.fr/epcis?nom=' + name, { method: 'get', headers: { 'Content-Type': 'application/json' }, }) .then((response) => { if (!response.ok) { throw new Error('Network response was not ok') } return response.json() }) .then((results) => { if (results.length > 0) { const epci = results[0] const codesDepartements = epci?.codesDepartements const departements = codesDepartements.map((codeDepartement) => { return this.frenchCounties.find((frenchCounty) => frenchCounty.code === codeDepartement).nom }) const codesRegions = epci?.codesRegions const regions = codesRegions.map((codeRegion) => { return this.frenchStates.find((frenchState) => frenchState.code === codeRegion).nom }) $li.attr('title', name + '\n' + meta + ' - ' + departements.join(', ')) $li.attr('data-epci-code', epci.code) $li.attr('data-departements', JSON.stringify(departements)) $li.attr('data-regions', JSON.stringify(regions)) $li.find('.area-filter-results-meta').text(meta + ' - ' + departements.join(', ')) } }) } renderResults(ul, { id, data }) { if (!data) { return '' // Handle case where data is not available yet } let $li = null const json = JSON.parse(data) const { name, type, country, state, county, postCode, epci, codeEpci, departements, codesDepartements, hasResults, } = json if (!hasResults) { $li = $(`
  • ${App.config.i18n[App.config.language]['no.result.found']}
  • `) } else { let meta = '' if (type !== 'country') { meta = country ?? '' if (state) { meta = meta + ' - ' + (state ?? '') } if (county) { meta = meta + ' - ' + (county ?? '') } if (departements) { meta = meta + ' - ' + (departements.join(', ') ?? '') } } $li = $(`
  • ${name}
    ${meta}
  • `) if (country === 'France' && !this.isFirstLoading) { if (type === 'city' && !codeEpci) { this.searchCommune($li, name, meta, postCode) } if (type === 'locality' && !codeEpci) { const formatedName = name.replace('Communauté de communes', 'CC').replace("Communauté d'agglomération", 'CA') this.searchEpci($li, formatedName, meta) } } } return $li.appendTo(ul) } }