diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts index bc1e5f8f89..d7a8261d1d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts @@ -18,7 +18,7 @@ import { DEFAULT_ZOOM_LEVEL, defaultGeoMapSettings, GeoMapSettings, - latLngPointToBounds, + latLngPointToBounds, MapControlsPosition, MapZoomAction, TbCircleData, TbPolygonCoordinate, @@ -93,6 +93,15 @@ export class TbGeoMap extends TbMap { tap((layers: L.TB.LayerData[]) => { if (layers.length) { const layer = layers[0]; + layers.forEach(layer => { + layer.layer.once('gl-error', () => { + const toastPosition = this.settings.controlsPosition === MapControlsPosition.bottomleft || this.settings.controlsPosition === MapControlsPosition.bottomright ? 'top' : 'bottom'; + this.ctx.showErrorToast( + this.ctx.translate.instant('widgets.maps.gl.webgl-not-available'), + toastPosition, 'left', this.ctx.toastTargetId, true + ); + }); + }) layer.layer.addTo(this.map); this.map.attributionControl.setPrefix(layer.attributionPrefix); if (layers.length > 1) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts index c16d5cc67c..0d3eaf2b08 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts @@ -1093,6 +1093,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { private _actualCanvas: HTMLCanvasElement; private _offset: L.Point; private _zooming: boolean; + private _glError = false; constructor(options: TB.MapLibreGL.LeafletMapLibreGLMapOptions) { super(); @@ -1136,7 +1137,9 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { const paneName = this.getPaneName(); map.getPane(paneName).removeChild(this._container); - this._glMap.remove(); + if (this._glMap) { + this._glMap.remove(); + } this._glMap = null; return this; @@ -1204,7 +1207,18 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { zoom: this._map.getZoom() - 1, attributionControl: false }); - this._glMap = new MapLibreGLMap(options); + this._glError = false; + try { + this._glMap = new MapLibreGLMap(options); + } catch (e) { + this._glError = true; + this.fire('gl-error', { error: e }); + return; + } + this._glMap.once('webglcontextlost', (e) => { + this._glError = true; + this.fire('gl-error', {error: e}); + }); this._glMap.once('load', () => { this.fire('load'); }); @@ -1223,7 +1237,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { } private _update() { - if (!this._map) { + if (!this._map || this._glError) { return; } this._offset = this._map.containerPointToLayerPoint([0, 0]); @@ -1253,6 +1267,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { private _transformGL(gl: MapLibreGLMap) { const center = this._map.getCenter(); const tr = gl._getTransformForUpdate(); + if (!tr) { return; } tr.setCenter(MapLibreGLLngLat.convert([center.lng, center.lat])); tr.setZoom(this._map.getZoom() - 1); gl.transform.apply(tr); @@ -1260,6 +1275,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { } private _pinchZoom() { + if (this._glError) { return; } this._glMap.jumpTo({ zoom: this._map.getZoom() - 1, center: this._map.getCenter() @@ -1267,6 +1283,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { } private _animateZoom(e: L.ZoomAnimEvent) { + if (this._glError || !this._actualCanvas) { return; } const scale = this._map.getZoomScale(e.zoom); const padding = this._map.getSize().multiplyBy(this.options.padding * scale); const viewHalf = this.getSize().divideBy(2); @@ -1291,6 +1308,7 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { } private _zoomEnd() { + if (this._glError || !this._actualCanvas) { return; } const scale = this._map.getZoomScale(this._map.getZoom()); L.DomUtil.setTransform( this._actualCanvas, @@ -1302,7 +1320,9 @@ class MapLibreGLLayer extends L.Layer implements TB.MapLibreGL.MapLibreGLLayer { } private _transitionEnd() { + if (this._glError) { return; } L.Util.requestAnimFrame(() => { + if (this._glError) { return; } const zoom = this._map.getZoom(); const center = this._map.getCenter(); const offset = this._map.latLngToContainerPoint( diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts index 2953c609ce..be4ae212b8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts @@ -35,7 +35,7 @@ import { DeepPartial } from '@shared/models/common'; import { mergeDeep } from '@core/utils'; import { Observable, of, shareReplay, switchMap } from 'rxjs'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; -import L from 'leaflet'; +import L, { LeafletEvent } from 'leaflet'; import { catchError, map } from 'rxjs/operators'; import { ResourcesService } from '@core/services/resources.service'; import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'; @@ -120,6 +120,9 @@ export abstract class TbMapLayer { let referenceLayerLoaded = false; baseLayer.addTo(layer); referenceLayer.addTo(layer); + baseLayer.once('gl-error', (e: LeafletEvent) => { + layer.fire('gl-error', e); + }); baseLayer.once('load', () => { baseLayerLoaded = true; if (referenceLayerLoaded) { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 039e7a33e7..d73a422773 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8261,6 +8261,7 @@ "roadmap": "Roadmap", "satellite": "Satellite", "hybrid": "Hybrid", + "webgl-not-available": "The selected map layer requires graphics acceleration to display. Please enable 'Hardware acceleration' in your browser settings, or switch to a different layer.", "reference": { "reference-layer": "Reference layer", "no-layer": "No layer",