UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

103 lines (102 loc) • 3.8 kB
const MAPS_API_LOADED_CALLBACK = 'girafeGoogleMapsApiLoaded'; let mapsApiLoading = null; /** * Loads the Google Maps JavaScript API once, when the first Google StreetView is displayed. * The API is only usable once it calls back the application: the load event of the script fires * before the API has finished loading. * The API key is part of the script URL, and therefore visible to the user: it must be restricted * to the domain of the application in the Google Cloud Console. */ function loadMapsApi(apiKey) { mapsApiLoading ??= new Promise((resolve, reject) => { window[MAPS_API_LOADED_CALLBACK] = () => { delete window[MAPS_API_LOADED_CALLBACK]; resolve(); }; const script = document.createElement('script'); const params = new URLSearchParams({ key: apiKey, v: 'weekly', loading: 'async', callback: MAPS_API_LOADED_CALLBACK }); script.src = `https://maps.googleapis.com/maps/api/js?${params}`; script.async = true; script.addEventListener('error', () => reject(new Error('The Google Maps JavaScript API could not be loaded'))); document.head.appendChild(script); }); return mapsApiLoading; } function isZeroResults(error) { return error?.code === 'ZERO_RESULTS'; } /** * The Google Street View footage, displayed by a StreetViewPanorama. * This module is the only place where the Google Maps API is used. It is loaded on demand * by the StreetView Component, so that the API stays out of the application as long as * no Google StreetView is displayed. */ export default class GoogleStreetViewProvider { apiKey; service; panorama; nearest; constructor(apiKey) { this.apiKey = apiKey; } async initialize(target, onFootageChanged) { await loadMapsApi(this.apiKey); const library = (await google.maps.importLibrary('streetView')); this.nearest = library.StreetViewPreference.NEAREST; this.service = new library.StreetViewService(); this.panorama = new library.StreetViewPanorama(target, { visible: false, pov: { heading: 0, pitch: 0 } }); const notifyFootageChanged = () => { const footage = this.getFootage(); if (footage) { onFootageChanged(footage); } }; this.panorama.addListener('position_changed', notifyFootageChanged); this.panorama.addListener('pov_changed', notifyFootageChanged); } getFootage() { const position = this.panorama.getPosition(); if (!position) { return null; } const pov = this.panorama.getPov(); return { lonLat: [position.lng(), position.lat()], heading: pov.heading, pitch: pov.pitch }; } async showClosestFootage(lonLat, radius, pov) { try { const response = await this.service.getPanorama({ location: { lng: lonLat[0], lat: lonLat[1] }, radius: radius, preference: this.nearest }); // The API does not respond when it is in error (invalid key, ...) const pano = response?.data?.location?.pano; if (!pano) { return false; } this.panorama.setPano(pano); if (pov) { this.panorama.setPov(pov); } this.panorama.setVisible(true); return true; } catch (error) { if (isZeroResults(error)) { return false; } throw error; } } setVisible(visible) { this.panorama?.setVisible(visible && this.panorama.getPosition() !== null); } }