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( `