import { App } from '../gogocarto' import { ViewPort, Event } from '../classes/classes' import { slugify } from '../utils/string-helpers' import { Geocoded } from 'universal-geocoder' import { GoGoConfig } from '../classes/config/gogo-config.class' import { BoundingBox } from 'universal-geocoder/src/types' declare let L, $, UniversalGeocoder // south, west, north, east export type RawBounds = [number, number, number, number] /** * Interface between Universal Geocoder and GoGoCartoJS. * Allow to change geocoding technology without changing code in GoGoCartoJS. */ export class GeocoderModule { private geocoder = null private lastAddressRequest = '' private lastResults: Geocoded[] = [] private lastResultBounds: L.LatLngBounds = null private location: L.LatLng = null public onGeocodeResult = new Event() public onGeolocalizationResult = new Event() public initialize(): void { this.geocoder = UniversalGeocoder.createGeocoder({ provider: App.config.search.geocodingProvider, useSsl: true, ...(App.config.search.geocodingProvider === GoGoConfig.GEOCODING_PROVIDER_NOMINATIM ? { userAgent: 'GoGoCartoJS' } : {}), ...(App.config.search.geocodingProvider === GoGoConfig.GEOCODING_PROVIDER_MAPBOX ? { apiKey: App.config.search.meta.apiKey } : {}), }) } public getLocation(): null | L.LatLng { return this.location } public getBounds(): null | L.LatLngBounds { return this.lastResultBounds } public getLocationSlug(): string { return slugify(this.lastAddressRequest) } public getLocationAddress(): string { return this.lastAddressRequest } public latLngBoundsFromRawBounds(rawbounds: BoundingBox): L.LatLngBounds { if (!rawbounds) return null const corner1 = L.latLng(rawbounds.latitudeSW, rawbounds.longitudeSW) const corner2 = L.latLng(rawbounds.latitudeNE, rawbounds.longitudeNE) return L.latLngBounds(corner1, corner2) } public geocodeAddress( address: string, callbackComplete?: (results: Geocoded[]) => void, callbackFail?: () => void, ): void { if (!this.geocoder) return this.lastAddressRequest = address // if no address, we show france if (address == '') { this.lastResults = [] this.lastResultBounds = App.boundsModule.defaultBounds this.onGeocodeResult.emit() // leave time for map to load if (callbackComplete) { setTimeout(() => { callbackComplete(this.lastResults) }, 200) } return } // fake geocoder when no internet connexion const fake = false if (!fake) { let countryCodes = [] let bounds = L.latLngBounds([]) let bounded = false switch (App.config.map.geocoding.geocodingBoundsType) { case 'countryCodes': countryCodes = countryCodes.concat(App.config.map.geocoding.geocodingBoundsByCountryCodes.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 } this.geocoder.geocode( { text: address, locale: App.config.language, bounds: bounded ? { latitudeSW: bounds.getSouthWest().lat, longitudeSW: bounds.getSouthWest().lng, latitudeNE: bounds.getNorthEast().lat, longitudeNE: bounds.getNorthEast().lng, } : { latitudeSW: App.config.map.maxBounds.getSouthWest().lat, longitudeSW: App.config.map.maxBounds.getSouthWest().lng, latitudeNE: App.config.map.maxBounds.getNorthEast().lat, longitudeNE: App.config.map.maxBounds.getNorthEast().lng, }, shape: 'geojson', countryCodes: countryCodes, bounded: bounded, limit: 1, }, (results: Geocoded[]) => { if (results.length <= 0) { if (callbackFail) { callbackFail() } return } this.lastResults = results this.lastResultBounds = this.latLngBoundsFromRawBounds(this.lastResults[0].getBounds()) if (this.lastResults && this.lastResults[0]) { this.location = L.latLng( this.lastResults[0].getCoordinates().latitude, this.lastResults[0].getCoordinates().longitude, ) } else { this.location = null } this.onGeocodeResult.emit() if (callbackComplete) { callbackComplete(results) } }, ) } else { const result = { bounds: [0.069185, -0.641415, 44.1847351, -0.4699835], city: 'Labrit', formattedAddress: 'Labrit 40420', latitude: 44.1049567, longitude: -0.5445296, postal_code: '40420', region: 'Nouvelle-Aquitaine', getBounds() { return this.bounds }, getCoordinates() { return [this.latitude, this.longitude] }, getFormattedAddress() { return this.formattedAddress }, } const results = [] results.push(result) this.lastResults = results this.lastResultBounds = this.latLngBoundsFromRawBounds(this.lastResults[0].getBounds()) callbackComplete(results) } } public geolocateUser( callbackComplete?: (viewPort: ViewPort) => void, callbackError?: (viewPort: ViewPort) => void, forceApi = false, ): void { if ('geolocation' in navigator && !forceApi) { const geocodeOptions = { enableHighAccuracy: true, maximumAge: 30000, timeout: 27000, } navigator.geolocation.getCurrentPosition( (position) => { // associate zoom to accuracy let zoom = 17 - Math.log(position.coords.accuracy / 3000) * Math.LOG2E zoom = Math.min(zoom, 16) zoom = Math.max(zoom, 8) const viewPort = new ViewPort(position.coords.latitude, position.coords.longitude, zoom) this.handleGeolocalisationResponse(viewPort) callbackComplete(viewPort) }, (error) => { this.geocodeError(callbackComplete, callbackError, error) }, geocodeOptions, ) } else { $.getJSON('https://www.geoplugin.net/json.gp', (data) => { if (!data) { this.geocodeError(callbackComplete, callbackError) return } const viewPort = new ViewPort(parseFloat(data.geoplugin_latitude), parseFloat(data.geoplugin_longitude), 14) this.handleGeolocalisationResponse(viewPort) callbackComplete(viewPort) }).error(() => { this.geocodeError(callbackComplete, callbackError) }) } } private geocodeError( callbackComplete?: (viewPort: ViewPort) => void, callbackError?: (viewPort: ViewPort) => void, error: any = {}, ): void { if (error.code == 1) { // permission denied with navigator geoloc App.component.toastMessage(App.config.translate('geolocation.error.refused'), 6000) } else if (!error.code) { // Geocode with API failed App.component.toastMessage(App.config.translate('geolocation.error.failed'), 6000) } else { // error from navigator geoloc (unavailable or timeout), try with json API this.geolocateUser(callbackComplete, callbackError, true) } // Real error, we initialize to default bounds if (error.code == 1 || !error.code) { const viewport = new ViewPort(App.boundsModule.defaultCenter.lat, App.boundsModule.defaultCenter.lng, 7) this.handleGeolocalisationResponse(viewport) callbackError(viewport) } } private handleGeolocalisationResponse(viewPort: ViewPort): void { this.location = viewPort.toLocation() this.onGeolocalizationResult.emit(viewPort) } }