"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.MbMap = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _lodash = _interopRequireDefault(require("lodash")); var _react = _interopRequireWildcard(require("react")); var _mapboxGlSupported = require("@mapbox/mapbox-gl-supported"); var _mapboxGl = require("@kbn/mapbox-gl"); var _analytics = require("@kbn/analytics"); var _draw_filter_control = require("./draw_control/draw_filter_control"); var _scale_control = require("./scale_control"); var _tooltip_control = require("./tooltip_control"); var _elasticsearch_util = require("../../../common/elasticsearch_util"); var _get_initial_view = require("./get_initial_view"); var _kibana_services = require("../../kibana_services"); var _constants = require("../../../common/constants"); var _glyphs = require("./glyphs"); var _sort_layers = require("./sort_layers"); var _remove_orphaned = require("./remove_orphaned"); var _tile_status_tracker = require("./tile_status_tracker"); var _draw_feature_control = require("./draw_control/draw_feature_control"); var _symbol_utils = require("../../classes/styles/vector/symbol_utils"); var _maki_icons = require("../../classes/styles/vector/maki_icons"); var _keydown_scroll_zoom = require("./keydown_scroll_zoom/keydown_scroll_zoom"); var _transform_request = require("./transform_request"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ class MbMap extends _react.Component { constructor(...args) { super(...args); (0, _defineProperty2.default)(this, "_isMounted", false); (0, _defineProperty2.default)(this, "_containerRef", null); (0, _defineProperty2.default)(this, "_prevCustomIcons", void 0); (0, _defineProperty2.default)(this, "_prevDisableInteractive", void 0); (0, _defineProperty2.default)(this, "_prevLayerList", void 0); (0, _defineProperty2.default)(this, "_prevTimeslice", void 0); (0, _defineProperty2.default)(this, "_navigationControl", new _mapboxGl.maplibregl.NavigationControl({ showCompass: false })); (0, _defineProperty2.default)(this, "state", { mbMap: undefined }); (0, _defineProperty2.default)(this, "_debouncedSync", _lodash.default.debounce(() => { if (this._isMounted && this.props.isMapReady && this.state.mbMap) { const hasLayerListChanged = this._prevLayerList !== this.props.layerList; // Comparing re-select memoized instance so no deep equals needed const hasTimesliceChanged = !_lodash.default.isEqual(this._prevTimeslice, this.props.timeslice); if (hasLayerListChanged || hasTimesliceChanged) { this._prevLayerList = this.props.layerList; this._prevTimeslice = this.props.timeslice; this._syncMbMapWithLayerList(); this._syncMbMapWithInspector(); } this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap); this._syncSettings(); } }, 256)); (0, _defineProperty2.default)(this, "_syncMbMapWithMapState", () => { const { isMapReady, goto, clearGoto } = this.props; if (!isMapReady || !goto || !this.state.mbMap) { return; } clearGoto(); if (goto.bounds) { // clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90) const lnLatBounds = new _mapboxGl.maplibregl.LngLatBounds(new _mapboxGl.maplibregl.LngLat((0, _elasticsearch_util.clampToLonBounds)(goto.bounds.minLon), (0, _elasticsearch_util.clampToLatBounds)(goto.bounds.minLat)), new _mapboxGl.maplibregl.LngLat((0, _elasticsearch_util.clampToLonBounds)(goto.bounds.maxLon), (0, _elasticsearch_util.clampToLatBounds)(goto.bounds.maxLat))); // maxZoom ensure we're not zooming in too far on single points or small shapes // the padding is to avoid too tight of a fit around edges this.state.mbMap.fitBounds(lnLatBounds, { maxZoom: 17, padding: 16 }); } else if (goto.center) { this.state.mbMap.setZoom(goto.center.zoom); this.state.mbMap.setCenter({ lng: goto.center.lon, lat: goto.center.lat }); } }); (0, _defineProperty2.default)(this, "_syncMbMapWithLayerList", () => { if (!this.state.mbMap) { return; } (0, _remove_orphaned.removeOrphanedSourcesAndLayers)(this.state.mbMap, this.props.layerList, this.props.spatialFiltersLayer); this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap, this.props.timeslice)); (0, _sort_layers.syncLayerOrder)(this.state.mbMap, this.props.spatialFiltersLayer, this.props.layerList); }); (0, _defineProperty2.default)(this, "_syncMbMapWithInspector", () => { if (!this.props.inspectorAdapters.map || !this.state.mbMap) { return; } const stats = { center: this.state.mbMap.getCenter().toArray(), zoom: this.state.mbMap.getZoom() }; this.props.inspectorAdapters.map.setMapState({ stats, style: this.state.mbMap.getStyle() }); }); (0, _defineProperty2.default)(this, "_setContainerRef", element => { this._containerRef = element; }); } componentDidMount() { this._initializeMap(); this._isMounted = true; } componentDidUpdate() { this._syncMbMapWithMapState(); // do not debounce syncing of map-state this._debouncedSync(); } componentWillUnmount() { this._isMounted = false; if (this.state.mbMap) { this.state.mbMap.remove(); this.state.mbMap = undefined; } this.props.onMapDestroyed(); } _getMapExtentState() { const zoom = this.state.mbMap.getZoom(); const mbCenter = this.state.mbMap.getCenter(); const mbBounds = this.state.mbMap.getBounds(); return { zoom: _lodash.default.round(zoom, _constants.ZOOM_PRECISION), center: { lon: _lodash.default.round(mbCenter.lng, _constants.DECIMAL_DEGREES_PRECISION), lat: _lodash.default.round(mbCenter.lat, _constants.DECIMAL_DEGREES_PRECISION) }, extent: { minLon: _lodash.default.round(mbBounds.getWest(), _constants.DECIMAL_DEGREES_PRECISION), minLat: _lodash.default.round(mbBounds.getSouth(), _constants.DECIMAL_DEGREES_PRECISION), maxLon: _lodash.default.round(mbBounds.getEast(), _constants.DECIMAL_DEGREES_PRECISION), maxLat: _lodash.default.round(mbBounds.getNorth(), _constants.DECIMAL_DEGREES_PRECISION) } }; } async _createMbMapInstance(initialView) { this._reportUsage(); return new Promise(resolve => { const glyphs = (0, _glyphs.getGlyphs)(); const mbStyle = { version: 8, sources: {}, layers: [], glyphs: glyphs.glyphUrlTemplate }; const options = { attributionControl: false, container: this._containerRef, style: mbStyle, preserveDrawingBuffer: (0, _kibana_services.getPreserveDrawingBuffer)(), maxZoom: this.props.settings.maxZoom, minZoom: this.props.settings.minZoom, transformRequest: _transform_request.transformRequest }; if (initialView) { options.zoom = initialView.zoom; options.center = { lng: initialView.lon, lat: initialView.lat }; } else { options.bounds = [-170, -60, 170, 75]; } const mbMap = new _mapboxGl.maplibregl.Map(options); mbMap.dragRotate.disable(); mbMap.touchZoomRotate.disableRotation(); let emptyImage; mbMap.on('styleimagemissing', e => { if (emptyImage) { // @ts-expect-error mbMap.addImage(e.id, emptyImage); } }); mbMap.on('load', () => { // Map instance automatically resizes when container size changes. // However, issues may arise if container resizes before map finishes loading. // This is occuring when by-value maps are used in dashboard. // To prevent issues, resize container after load mbMap.resize(); emptyImage = new Image(); emptyImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII='; emptyImage.crossOrigin = 'anonymous'; resolve(mbMap); }); if (glyphs.isEmsFont) { (0, _glyphs.getCanAccessEmsFonts)().then(canAccessEmsFonts => { if (!this._isMounted || canAccessEmsFonts) { return; } // fallback to kibana fonts when EMS fonts are not accessable to prevent layers from not displaying mbMap.setStyle({ ...mbMap.getStyle(), glyphs: (0, _glyphs.getKibanaFontsGlyphUrl)() }); }); } }); } async _initializeMap() { const initialView = await (0, _get_initial_view.getInitialView)(this.props.goto, this.props.settings); if (!this._isMounted) { return; } let mbMap; try { mbMap = await this._createMbMapInstance(initialView); } catch (error) { this.props.setMapInitError(error.message); return; } if (!this._isMounted) { return; } this.setState({ mbMap }, () => { this._loadMakiSprites(mbMap); this._registerMapEventListeners(mbMap); this.props.onMapReady(this._getMapExtentState()); }); } _registerMapEventListeners(mbMap) { // moveend callback is debounced to avoid updating map extent state while map extent is still changing // moveend is fired while the map extent is still changing in the following scenarios // 1) During opening/closing of layer details panel, the EUI animation results in 8 moveend events // 2) Setting map zoom and center from goto is done in 2 API calls, resulting in 2 moveend events mbMap.on('moveend', _lodash.default.debounce(() => { if (this._isMounted) { this.props.extentChanged(this._getMapExtentState()); } }, 100)); // do not update redux state on 'move' event for performance reasons // instead, callback provided for cases where consumers need to react to "move" event mbMap.on('move', () => { if (this.props.onMapMove) { const { zoom, center } = this._getMapExtentState(); this.props.onMapMove(center.lat, center.lon, zoom); } }); // Attach event only if view control is visible, which shows lat/lon if (!this.props.settings.hideViewControl) { const throttledSetMouseCoordinates = _lodash.default.throttle(e => { this.props.setMouseCoordinates({ lat: e.lngLat.lat, lon: e.lngLat.lng }); }, 100); mbMap.on('mousemove', throttledSetMouseCoordinates); mbMap.on('mouseout', () => { throttledSetMouseCoordinates.cancel(); // cancel any delayed setMouseCoordinates invocations this.props.clearMouseCoordinates(); }); } } _reportUsage() { const usageCollector = (0, _kibana_services.getUsageCollection)(); if (!usageCollector) return; const webglSupport = (0, _mapboxGlSupported.supported)(); usageCollector.reportUiCounter(_constants.APP_ID, _analytics.METRIC_TYPE.LOADED, webglSupport ? 'gl_webglSupported' : 'gl_webglNotSupported'); // Report low system performance or no hardware GPU if (webglSupport && !(0, _mapboxGlSupported.supported)({ failIfMajorPerformanceCaveat: true })) { usageCollector.reportUiCounter(_constants.APP_ID, _analytics.METRIC_TYPE.LOADED, 'gl_majorPerformanceCaveat'); } } async _loadMakiSprites(mbMap) { if (this._isMounted) { // Math.floor rounds values < 1 to 0. This occurs when browser is zoomed out // Math.max wrapper ensures value is always at least 1 in these cases const pixelRatio = Math.max(Math.floor(window.devicePixelRatio), 1); for (const [symbolId, { svg }] of Object.entries(_maki_icons.MAKI_ICONS)) { if (!mbMap.hasImage(symbolId)) { const imageData = await (0, _symbol_utils.createSdfIcon)({ renderSize: _constants.MAKI_ICON_SIZE, svg }); if (imageData) { mbMap.addImage(symbolId, imageData, { pixelRatio, sdf: true }); } } } } } _syncSettings() { if (!this.state.mbMap) { return; } if (!(0, _kibana_services.isScreenshotMode)() && (this._prevDisableInteractive === undefined || this._prevDisableInteractive !== this.props.settings.disableInteractive)) { this._prevDisableInteractive = this.props.settings.disableInteractive; if (this.props.settings.disableInteractive) { this.state.mbMap.boxZoom.disable(); this.state.mbMap.doubleClickZoom.disable(); this.state.mbMap.dragPan.disable(); try { this.state.mbMap.removeControl(this._navigationControl); } catch (error) { // ignore removeControl errors } } else { this.state.mbMap.boxZoom.enable(); this.state.mbMap.doubleClickZoom.enable(); this.state.mbMap.dragPan.enable(); this.state.mbMap.addControl(this._navigationControl, 'top-left'); } } if (this._prevCustomIcons === undefined || !_lodash.default.isEqual(this._prevCustomIcons, this.props.customIcons)) { this._prevCustomIcons = this.props.customIcons; const mbMap = this.state.mbMap; for (const { symbolId, svg, cutoff, radius } of this.props.customIcons) { (0, _symbol_utils.createSdfIcon)({ svg, renderSize: _constants.CUSTOM_ICON_SIZE, cutoff, radius }).then(imageData => { if (!imageData) { return; } if (mbMap.hasImage(symbolId)) mbMap.updateImage(symbolId, imageData);else mbMap.addImage(symbolId, imageData, { sdf: true, pixelRatio: _symbol_utils.CUSTOM_ICON_PIXEL_RATIO }); }); } } let zoomRangeChanged = false; if (this.props.settings.minZoom !== this.state.mbMap.getMinZoom()) { this.state.mbMap.setMinZoom(this.props.settings.minZoom); zoomRangeChanged = true; } if (this.props.settings.maxZoom !== this.state.mbMap.getMaxZoom()) { this.state.mbMap.setMaxZoom(this.props.settings.maxZoom); zoomRangeChanged = true; } // 'moveend' event not fired when map moves from setMinZoom or setMaxZoom // https://github.com/mapbox/mapbox-gl-js/issues/9610 // hack to update extent after zoom update finishes moving map. if (zoomRangeChanged) { setTimeout(() => { if (this._isMounted) { this.props.extentChanged(this._getMapExtentState()); } }, 300); } } render() { let drawFilterControl; let drawFeatureControl; let tooltipControl; let scaleControl; let keydownScrollZoomControl; let tileStatusTrackerControl; if (this.state.mbMap) { drawFilterControl = this.props.addFilters && this.props.filterModeActive ? /*#__PURE__*/_react.default.createElement(_draw_filter_control.DrawFilterControl, { mbMap: this.state.mbMap, addFilters: this.props.addFilters }) : null; drawFeatureControl = this.props.featureModeActive ? /*#__PURE__*/_react.default.createElement(_draw_feature_control.DrawFeatureControl, { mbMap: this.state.mbMap }) : null; tooltipControl = !this.props.settings.disableTooltipControl ? /*#__PURE__*/_react.default.createElement(_tooltip_control.TooltipControl, { mbMap: this.state.mbMap, addFilters: this.props.addFilters, getFilterActions: this.props.getFilterActions, getActionContext: this.props.getActionContext, onSingleValueTrigger: this.props.onSingleValueTrigger, renderTooltipContent: this.props.renderTooltipContent }) : null; scaleControl = this.props.settings.showScaleControl ? /*#__PURE__*/_react.default.createElement(_scale_control.ScaleControl, { mbMap: this.state.mbMap, isFullScreen: this.props.isFullScreen }) : null; keydownScrollZoomControl = this.props.settings.keydownScrollZoom ? /*#__PURE__*/_react.default.createElement(_keydown_scroll_zoom.KeydownScrollZoom, { mbMap: this.state.mbMap }) : null; tileStatusTrackerControl = /*#__PURE__*/_react.default.createElement(_tile_status_tracker.TileStatusTracker, { mbMap: this.state.mbMap }); } return /*#__PURE__*/_react.default.createElement("div", { id: "mapContainer", className: "mapContainer", ref: this._setContainerRef, "data-test-subj": "mapContainer" }, drawFilterControl, drawFeatureControl, keydownScrollZoomControl, scaleControl, tooltipControl, tileStatusTrackerControl); } } exports.MbMap = MbMap;