diff --git a/package.json b/package.json index 6de5dbd4..fc848fb4 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,19 @@ "pre-commit": "lint-staged" } }, - "devDependencies": { - "cypress": "9.5.4", - "cypress-multi-reporters": "^1.5.0" - }, "dependencies": { "@opensearch-dashboards-test/opensearch-dashboards-test-library": "git+https://github.com/opensearch-project/opensearch-dashboards-test-library.git#main", "@cypress/skip-test": "^2.6.1", "cypress-file-upload": "^5.0.8", "maplibre-gl": "^2.4.0", "uuid": "3.3.2", + "prettier": "^2.1.1", + "@types/wellknown": "^0.5.4", + "wellknown": "^0.5.0" + }, + "devDependencies": { + "cypress": "9.5.4", + "cypress-multi-reporters": "^1.5.0", "prettier": "^2.1.1" } } diff --git a/public/components/layer_config/documents_config/document_layer_config_panel.tsx b/public/components/layer_config/documents_config/document_layer_config_panel.tsx index 86854147..4a41c76f 100644 --- a/public/components/layer_config/documents_config/document_layer_config_panel.tsx +++ b/public/components/layer_config/documents_config/document_layer_config_panel.tsx @@ -5,7 +5,6 @@ import React, { Fragment } from 'react'; import { EuiSpacer, EuiTabbedContent } from '@elastic/eui'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; import { DocumentLayerSpecification } from '../../../model/mapLayerType'; import { LayerBasicSettings } from '../layer_basic_settings'; import { DocumentLayerSource } from './document_layer_source'; @@ -15,7 +14,6 @@ interface Props { selectedLayerConfig: DocumentLayerSpecification; setSelectedLayerConfig: Function; setIsUpdateDisabled: Function; - layersIndexPatterns: IndexPattern[]; isLayerExists: Function; } diff --git a/public/components/layer_config/documents_config/document_layer_source.tsx b/public/components/layer_config/documents_config/document_layer_source.tsx index ba29eff2..905252cb 100644 --- a/public/components/layer_config/documents_config/document_layer_source.tsx +++ b/public/components/layer_config/documents_config/document_layer_source.tsx @@ -30,14 +30,12 @@ interface Props { setSelectedLayerConfig: Function; selectedLayerConfig: DocumentLayerSpecification; setIsUpdateDisabled: Function; - layersIndexPatterns: IndexPattern[]; } export const DocumentLayerSource = ({ setSelectedLayerConfig, selectedLayerConfig, setIsUpdateDisabled, - layersIndexPatterns, }: Props) => { const { services: { @@ -51,16 +49,10 @@ export const DocumentLayerSource = ({ const [indexPattern, setIndexPattern] = useState(); const [geoFields, setGeoFields] = useState(); const [selectedField, setSelectedField] = useState(); - const [documentRequestNumber, setDocumentRequestNumber] = useState( - selectedLayerConfig.source.documentRequestNumber - ); const [hasInvalidRequestNumber, setHasInvalidRequestNumber] = useState(false); const [showTooltips, setShowTooltips] = useState( selectedLayerConfig.source.showTooltips ); - const [selectedTooltipFields, setSelectedTooltipFields] = useState( - selectedLayerConfig.source.tooltipFields - ); const errorsMap = { datasource: ['Required'], @@ -68,10 +60,9 @@ export const DocumentLayerSource = ({ }; useEffect(() => { - const disableUpdate = - !indexPattern || !selectedField || documentRequestNumber < 1 || documentRequestNumber > 10000; + const disableUpdate = !indexPattern || !selectedField || hasInvalidRequestNumber; setIsUpdateDisabled(disableUpdate); - }, [setIsUpdateDisabled, indexPattern, selectedField, documentRequestNumber]); + }, [setIsUpdateDisabled, indexPattern, selectedField, hasInvalidRequestNumber]); const formatFieldToComboBox = (field?: IndexPatternField | null) => { if (!field) return []; @@ -124,8 +115,7 @@ export const DocumentLayerSource = ({ const onDocumentRequestNumberChange = (e: React.ChangeEvent) => { const value = e.target.value; - const selectedNumber = parseInt(value, 10) || 1; - setDocumentRequestNumber(selectedNumber); + const selectedNumber = parseInt(value, 10); const source = { ...selectedLayerConfig.source, documentRequestNumber: selectedNumber }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; @@ -135,7 +125,6 @@ export const DocumentLayerSource = ({ for (const option of options) { tooltipSelection.push(option.label); } - setSelectedTooltipFields(tooltipSelection); const source = { ...selectedLayerConfig.source, tooltipFields: tooltipSelection }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; @@ -153,14 +142,14 @@ export const DocumentLayerSource = ({ useEffect(() => { const selectIndexPattern = async () => { if (selectedLayerConfig.source.indexPatternId) { - const selectedIndexPattern = layersIndexPatterns.find( - (ip) => ip.id === selectedLayerConfig.source.indexPatternId + const selectedIndexPattern = await indexPatterns.get( + selectedLayerConfig.source.indexPatternId ); setIndexPattern(selectedIndexPattern); } }; selectIndexPattern(); - }, [indexPatterns]); + }, [indexPatterns, selectedLayerConfig.source.indexPatternId]); // Update the fields list every time the index pattern is modified. useEffect(() => { @@ -174,11 +163,6 @@ export const DocumentLayerSource = ({ (field) => field.name === selectedLayerConfig.source.geoFieldName ); setSelectedField(savedField); - if (selectedLayerConfig.source.indexPatternId === indexPattern?.id) { - setSelectedTooltipFields(selectedLayerConfig.source.tooltipFields); - } else { - setSelectedTooltipFields([]); - } }, [indexPattern]); useEffect(() => { @@ -197,8 +181,11 @@ export const DocumentLayerSource = ({ }, [selectedField]); useEffect(() => { - setHasInvalidRequestNumber(documentRequestNumber < 1 || documentRequestNumber > 10000); - }, [documentRequestNumber]); + setHasInvalidRequestNumber( + selectedLayerConfig.source.documentRequestNumber < 1 || + selectedLayerConfig.source.documentRequestNumber > 10000 + ); + }, [selectedLayerConfig.source.documentRequestNumber]); const onShowTooltipsChange = (event: { target: { checked: React.SetStateAction } }) => { setShowTooltips(event.target.checked); @@ -206,6 +193,11 @@ export const DocumentLayerSource = ({ setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; + const onToggleGeoBoundingBox = (e: React.ChangeEvent) => { + const source = { ...selectedLayerConfig.source, useGeoBoundingBoxFilter: e.target.checked }; + setSelectedLayerConfig({ ...selectedLayerConfig, source }); + }; + const shouldTooltipSectionOpen = () => { return ( selectedLayerConfig.source.showTooltips && @@ -213,6 +205,10 @@ export const DocumentLayerSource = ({ ); }; + const filterPanelInitialIsOpen = + selectedLayerConfig.source.filters?.length > 0 || + selectedLayerConfig.source.useGeoBoundingBoxFilter; + return (
@@ -270,7 +266,7 @@ export const DocumentLayerSource = ({ 0} + initialIsOpen={filterPanelInitialIsOpen} > + + + + @@ -327,7 +334,9 @@ export const DocumentLayerSource = ({ { - const [fillColor, setFillColor] = useColorPickerState(selectedLayerConfig?.style?.fillColor); - const [borderColor, setBorderColor] = useColorPickerState( - selectedLayerConfig?.style?.borderColor - ); - const [borderThickness, setBorderThickness] = useState( - selectedLayerConfig?.style?.borderThickness - ); - const [markerSize, setMarkerSize] = useState(selectedLayerConfig?.style?.markerSize); + const [fillColor, setFillColor] = useState(selectedLayerConfig?.style?.fillColor); + const [borderColor, setBorderColor] = useState(selectedLayerConfig?.style?.borderColor); const [hasInvalidThickness, setHasInvalidThickness] = useState(false); const [hasInvalidSize, setHasInvalidSize] = useState(false); const geoTypeToggleButtonGroupPrefix = 'geoTypeToggleButtonGroup'; @@ -42,6 +36,11 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig `${geoTypeToggleButtonGroupPrefix}__Point` ); + useEffect(() => { + setFillColor(selectedLayerConfig?.style?.fillColor); + setBorderColor(selectedLayerConfig?.style?.borderColor); + }, [selectedLayerConfig]); + useEffect(() => { setSelectedLayerConfig({ ...selectedLayerConfig, @@ -70,7 +69,6 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig borderThickness: Number(e.target.value), }, }); - setBorderThickness(Number(e.target.value)); }; const onMarkerSizeChange = (e: any) => { @@ -81,24 +79,29 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig markerSize: Number(e.target.value), }, }); - setMarkerSize(Number(e.target.value)); }; useEffect(() => { - if (borderThickness < 0 || borderThickness > 100) { + if ( + selectedLayerConfig?.style?.borderThickness < 0 || + selectedLayerConfig?.style?.borderThickness > 100 + ) { setHasInvalidThickness(true); } else { setHasInvalidThickness(false); } - }, [borderThickness]); + }, [selectedLayerConfig?.style?.borderThickness]); useEffect(() => { - if (markerSize < 0 || markerSize > 100) { + if ( + selectedLayerConfig?.style?.markerSize < 0 || + selectedLayerConfig?.style?.markerSize > 100 + ) { setHasInvalidSize(true); } else { setHasInvalidSize(false); } - }, [markerSize]); + }, [selectedLayerConfig?.style?.markerSize]); const toggleButtonsGeoType = [ { @@ -193,13 +196,13 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig @@ -210,7 +213,7 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig @@ -222,7 +225,7 @@ export const DocumentLayerStyle = ({ setSelectedLayerConfig, selectedLayerConfig diff --git a/public/components/layer_config/layer_config_panel.tsx b/public/components/layer_config/layer_config_panel.tsx index e9a15d7f..0df9edc6 100644 --- a/public/components/layer_config/layer_config_panel.tsx +++ b/public/components/layer_config/layer_config_panel.tsx @@ -28,7 +28,6 @@ import { BaseMapLayerConfigPanel } from './index'; import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../../common'; import { DocumentLayerConfigPanel } from './documents_config/document_layer_config_panel'; import { layersTypeIconMap } from '../../model/layersFunctions'; -import { IndexPattern } from '../../../../../src/plugins/data/public'; import { CustomMapConfigPanel } from './custom_map_config/custom_map_config_panel'; interface Props { @@ -39,9 +38,9 @@ interface Props { removeLayer: Function; isNewLayer: boolean; setIsNewLayer: Function; - layersIndexPatterns: IndexPattern[]; - updateIndexPatterns: Function; isLayerExists: Function; + originLayerConfig: MapLayerSpecification | null; + setOriginLayerConfig: Function; } export const LayerConfigPanel = ({ @@ -52,27 +51,31 @@ export const LayerConfigPanel = ({ removeLayer, isNewLayer, setIsNewLayer, - layersIndexPatterns, - updateIndexPatterns, isLayerExists, + originLayerConfig, + setOriginLayerConfig, }: Props) => { const [isUpdateDisabled, setIsUpdateDisabled] = useState(false); - const [originLayerConfig, setOriginLayerConfig] = useState(null); - const [warnModalVisible, setWarnModalVisible] = useState(false); + const [unsavedModalVisible, setUnsavedModalVisible] = useState(false); useEffect(() => { - setOriginLayerConfig(cloneDeep(selectedLayerConfig)); - }, []); + if (originLayerConfig === null || originLayerConfig.id !== selectedLayerConfig.id) { + setOriginLayerConfig(cloneDeep(selectedLayerConfig)); + } + }, [originLayerConfig, selectedLayerConfig]); const discardChanges = () => { closeLayerConfigPanel(false); setSelectedLayerConfig(undefined); + setOriginLayerConfig(null); + setUnsavedModalVisible(false); }; + const onClose = () => { if (isEqual(originLayerConfig, selectedLayerConfig)) { discardChanges(); } else { - setWarnModalVisible(true); + setUnsavedModalVisible(true); } if (isNewLayer) { removeLayer(selectedLayerConfig.id); @@ -82,13 +85,14 @@ export const LayerConfigPanel = ({ const onUpdate = () => { updateLayer(); closeLayerConfigPanel(false); + setOriginLayerConfig(null); if (isNewLayer) { setIsNewLayer(false); } }; const closeModal = () => { - setWarnModalVisible(false); + setUnsavedModalVisible(false); }; return ( @@ -125,7 +129,6 @@ export const LayerConfigPanel = ({ selectedLayerConfig={selectedLayerConfig} setSelectedLayerConfig={setSelectedLayerConfig} setIsUpdateDisabled={setIsUpdateDisabled} - layersIndexPatterns={layersIndexPatterns} isLayerExists={isLayerExists} /> )} @@ -154,7 +157,7 @@ export const LayerConfigPanel = ({ - {warnModalVisible && ( + {unsavedModalVisible && ( Unsaved changes diff --git a/public/components/layer_control_panel/layer_control_panel.scss b/public/components/layer_control_panel/layer_control_panel.scss index b3ad8c0c..27d19370 100644 --- a/public/components/layer_control_panel/layer_control_panel.scss +++ b/public/components/layer_control_panel/layer_control_panel.scss @@ -15,6 +15,10 @@ width: $euiSizeL; } + .layerControlPanel__layerTypeIcon { + padding-left: $euiSizeM; + } + .euiListGroupItem__label { width: $euiSizeL * 6; } diff --git a/public/components/layer_control_panel/layer_control_panel.tsx b/public/components/layer_control_panel/layer_control_panel.tsx index 14a7d9f2..f6fb5615 100644 --- a/public/components/layer_control_panel/layer_control_panel.tsx +++ b/public/components/layer_control_panel/layer_control_panel.tsx @@ -5,33 +5,35 @@ import React, { memo, useEffect, useState } from 'react'; import { - EuiPanel, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiListGroupItem, + DropResult, EuiButtonEmpty, - EuiHorizontalRule, EuiButtonIcon, + EuiConfirmModal, EuiDragDropContext, EuiDraggable, EuiDroppable, - EuiConfirmModal, - DropResult, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiListGroupItem, + EuiPanel, + EuiTitle, + EuiIcon, + EuiToolTip, } from '@elastic/eui'; import { I18nProvider } from '@osd/i18n/react'; import { Map as Maplibre } from 'maplibre-gl'; import './layer_control_panel.scss'; +import { isEqual } from 'lodash'; import { IndexPattern } from '../../../../../src/plugins/data/public'; import { AddLayerPanel } from '../add_layer_panel'; import { LayerConfigPanel } from '../layer_config'; import { MapLayerSpecification } from '../../model/mapLayerType'; import { - LAYER_VISIBILITY, - DASHBOARDS_MAPS_LAYER_TYPE, LAYER_ICON_TYPE_MAP, - LAYER_PANEL_SHOW_LAYER_ICON, LAYER_PANEL_HIDE_LAYER_ICON, + LAYER_PANEL_SHOW_LAYER_ICON, + LAYER_VISIBILITY, } from '../../../common'; import { LayerActions, @@ -41,8 +43,8 @@ import { import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { MapServices } from '../../types'; import { - handleReferenceLayerRender, handleDataLayerRender, + handleReferenceLayerRender, } from '../../model/layerRenderController'; import { MapState } from '../../model/mapState'; @@ -57,6 +59,7 @@ interface Props { layersIndexPatterns: IndexPattern[]; setLayersIndexPatterns: (indexPatterns: IndexPattern[]) => void; mapState: MapState; + zoom: number; } export const LayerControlPanel = memo( @@ -67,10 +70,12 @@ export const LayerControlPanel = memo( layersIndexPatterns, setLayersIndexPatterns, mapState, + zoom, }: Props) => { const { services } = useOpenSearchDashboards(); const { data: { indexPatterns }, + notifications, } = services; const [isLayerConfigVisible, setIsLayerConfigVisible] = useState(false); @@ -82,9 +87,11 @@ export const LayerControlPanel = memo( const [isUpdatingLayerRender, setIsUpdatingLayerRender] = useState(false); const [isNewLayer, setIsNewLayer] = useState(false); const [isDeleteLayerModalVisible, setIsDeleteLayerModalVisible] = useState(false); + const [originLayerConfig, setOriginLayerConfig] = useState(null); const [selectedDeleteLayer, setSelectedDeleteLayer] = useState< MapLayerSpecification | undefined >(); + const [visibleLayers, setVisibleLayers] = useState([]); useEffect(() => { if (!isUpdatingLayerRender && initialLayersLoaded) { @@ -98,10 +105,7 @@ export const LayerControlPanel = memo( if (!selectedLayerConfig) { return; } - if ( - selectedLayerConfig.type === DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP || - selectedLayerConfig.type === DASHBOARDS_MAPS_LAYER_TYPE.CUSTOM_MAP - ) { + if (referenceLayerTypeLookup[selectedLayerConfig.type]) { handleReferenceLayerRender(selectedLayerConfig, maplibreRef, undefined); } else { updateIndexPatterns(); @@ -122,6 +126,16 @@ export const LayerControlPanel = memo( setIsUpdatingLayerRender(false); }, [layers]); + useEffect(() => { + const getCurrentVisibleLayers = () => { + return layers.filter( + (layer: { visibility: string; zoomRange: number[] }) => + zoom >= layer.zoomRange[0] && zoom <= layer.zoomRange[1] + ); + }; + setVisibleLayers(getCurrentVisibleLayers()); + }, [layers, zoom]); + // Get layer id from layers that is above the selected layer function getMapBeforeLayerId(selectedLayer: MapLayerSpecification): string | undefined { const selectedLayerIndex = layers.findIndex((layer) => layer.id === selectedLayer.id); @@ -174,10 +188,24 @@ export const LayerControlPanel = memo( } }; + const hasUnsavedChanges = () => { + if (!selectedLayerConfig || !originLayerConfig) { + return false; + } + return !isEqual(originLayerConfig, selectedLayerConfig); + }; + const onClickLayerName = (layer: MapLayerSpecification) => { - setSelectedLayerConfig(layer); - setIsLayerConfigVisible(true); + if (hasUnsavedChanges()) { + notifications.toasts.addWarning( + `You have unsaved changes for ${selectedLayerConfig?.name}` + ); + } else { + setSelectedLayerConfig(layer); + setIsLayerConfigVisible(true); + } }; + const isLayerExists = (name: string) => { return layers.findIndex((layer) => layer.name === name) > -1; }; @@ -298,6 +326,18 @@ export const LayerControlPanel = memo( ); } + const getLayerTooltipContent = (layer: MapLayerSpecification) => { + if (zoom < layer.zoomRange[0] || zoom > layer.zoomRange[1]) { + return `Layer is not visible outside of zoom range ${layer.zoomRange[0]} - ${layer.zoomRange[1]}`; + } else { + return `Layer is visible within zoom range ${layer.zoomRange[0]} - ${layer.zoomRange[1]}`; + } + }; + + const layerIsVisible = (layer: MapLayerSpecification) => { + return visibleLayers.includes(layer); + }; + if (isLayerControlVisible) { return ( @@ -349,17 +389,35 @@ export const LayerControlPanel = memo( alignItems="center" gutterSize="none" direction="row" + justifyContent={'flexStart'} > - - onClickLayerName(layer)} + + + + + onClickLayerName(layer)} + showToolTip={false} + /> + + )} void; @@ -31,6 +35,7 @@ export const MapContainer = ({ maplibreRef, mapState, }: MapContainerProps) => { + const { services } = useOpenSearchDashboards(); const mapContainer = useRef(null); const [mounted, setMounted] = useState(false); const [zoom, setZoom] = useState(MAP_INITIAL_STATE.zoom); @@ -62,6 +67,7 @@ export const MapContainer = ({ }); }, []); + // Create onClick tooltip for each layer features that has tooltip enabled useEffect(() => { let clickPopup: Popup | null = null; let hoverPopup: Popup | null = null; @@ -69,7 +75,7 @@ export const MapContainer = ({ // We don't want to show layer information in the popup for the map tile layer const tooltipEnabledLayers = layers.filter(isTooltipEnabledLayer); - function onClickMap(e: MapMouseEvent) { + function onClickMap(e: MapEventType['click']) { // remove previous popup clickPopup?.remove(); @@ -82,7 +88,7 @@ export const MapContainer = ({ } } - function onMouseMoveMap(e: MapMouseEvent) { + function onMouseMoveMap(e: MapEventType['mousemove']) { setCoordinates(e.lngLat.wrap()); // remove previous popup @@ -126,6 +132,35 @@ export const MapContainer = ({ }; }, [layers]); + // Handle map bounding box change, it should update the search if "request data around map extent" was enabled + useEffect(() => { + function renderLayers() { + layers.forEach((layer: MapLayerSpecification) => { + // We don't send search query if the layer doesn't have "request data around map extent" enabled + if ( + layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS && + layer.source.useGeoBoundingBoxFilter + ) { + handleDataLayerRender(layer, mapState, services, maplibreRef, undefined); + } + }); + } + + // Rerender layers with 200ms debounce to avoid calling the search API too frequently, especially when + // resizing the window, the "moveend" event could be fired constantly + const debouncedRenderLayers = debounce(renderLayers, 200); + + if (maplibreRef.current) { + maplibreRef.current.on('moveend', debouncedRenderLayers); + } + + return () => { + if (maplibreRef.current) { + maplibreRef.current.off('moveend', debouncedRenderLayers); + } + }; + }, [layers, mapState, services]); + return (
@@ -144,6 +179,7 @@ export const MapContainer = ({ layersIndexPatterns={layersIndexPatterns} setLayersIndexPatterns={setLayersIndexPatterns} mapState={mapState} + zoom={zoom} /> )}
diff --git a/public/components/tooltip/create_tooltip.tsx b/public/components/tooltip/create_tooltip.tsx index e77bd0ca..7d86fc66 100644 --- a/public/components/tooltip/create_tooltip.tsx +++ b/public/components/tooltip/create_tooltip.tsx @@ -16,7 +16,11 @@ type Options = { export function isTooltipEnabledLayer( layer: MapLayerSpecification ): layer is DocumentLayerSpecification { - return layer.type !== 'opensearch_vector_tile_map' && layer.source.showTooltips === true; + return ( + layer.type !== 'opensearch_vector_tile_map' && + layer.type !== 'custom_map' && + layer.source.showTooltips === true + ); } export function groupFeaturesByLayers( diff --git a/public/model/documentLayerFunctions.ts b/public/model/documentLayerFunctions.ts index c39dce4c..33bf6b82 100644 --- a/public/model/documentLayerFunctions.ts +++ b/public/model/documentLayerFunctions.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Map as Maplibre, Popup, MapGeoJSONFeature } from 'maplibre-gl'; -import { createPopup, getPopupLngLat } from '../components/tooltip/create_tooltip'; +import { Map as Maplibre } from 'maplibre-gl'; +import { parse } from 'wellknown'; import { DocumentLayerSpecification } from './mapLayerType'; import { convertGeoPointToGeoJSON, isGeoJSON } from '../utils/geo_formater'; import { getMaplibreBeforeLayerId, layerExistInMbSource } from './layersFunctions'; @@ -23,11 +23,22 @@ const openSearchGeoJSONMap = new Map([ ['geometrycollection', 'GeometryCollection'], ]); -const GeoJSONMaplibreMap = new Map([ - ['Point', 'circle'], - ['LineString', 'line'], - ['Polygon', 'fill'], -]); +const buildLayerSuffix = (layerId: string, mapLibreType: string) => { + if (mapLibreType.toLowerCase() === 'circle') { + return layerId; + } + if (mapLibreType.toLowerCase() === 'line') { + return layerId + '-line'; + } + if (mapLibreType.toLowerCase() === 'fill') { + return layerId + '-fill'; + } + if (mapLibreType.toLowerCase() === 'fill-outline') { + return layerId + '-outline'; + } + // if unknown type is found, use layerId as default + return layerId; +}; const getFieldValue = (data: any, name: string) => { if (!name) { @@ -58,11 +69,20 @@ const buildGeometry = (fieldType: string, location: any) => { coordinates: location.coordinates, }; } + + if (typeof location === 'string') { + // Check if location is WKT format + const geometry = parse(location); + if (geometry) { + return geometry; + } + } + // Geopoint supports other format like object, string, array, if (fieldType === 'geo_point') { // convert other supported formats to GeoJSON return convertGeoPointToGeoJSON(location); } - // We don't support non-geo-json format for geo_shape yet + // We don't support any other format return undefined; }; @@ -109,118 +129,205 @@ const addNewLayer = ( ) => { const maplibreInstance = maplibreRef.current; const mbLayerBeforeId = getMaplibreBeforeLayerId(layerConfig, maplibreRef, beforeLayerId); - const addGeoPointLayer = () => { + const addLineLayer = ( + documentLayerConfig: DocumentLayerSpecification, + beforeId: string | undefined + ) => { + const lineLayerId = buildLayerSuffix(documentLayerConfig.id, 'line'); maplibreInstance?.addLayer( { - id: layerConfig.id, + id: lineLayerId, + type: 'line', + source: documentLayerConfig.id, + filter: ['==', '$type', 'LineString'], + paint: { + 'line-color': documentLayerConfig.style?.fillColor, + 'line-opacity': documentLayerConfig.opacity / 100, + 'line-width': documentLayerConfig.style?.borderThickness, + }, + }, + beforeId + ); + maplibreInstance?.setLayoutProperty(lineLayerId, 'visibility', documentLayerConfig.visibility); + }; + + const addCircleLayer = ( + documentLayerConfig: DocumentLayerSpecification, + beforeId: string | undefined + ) => { + const circleLayerId = buildLayerSuffix(documentLayerConfig.id, 'circle'); + maplibreInstance?.addLayer( + { + id: circleLayerId, type: 'circle', source: layerConfig.id, + filter: ['==', '$type', 'Point'], paint: { - 'circle-radius': layerConfig.style?.markerSize, - 'circle-color': layerConfig.style?.fillColor, - 'circle-opacity': layerConfig.opacity / 100, - 'circle-stroke-width': layerConfig.style?.borderThickness, - 'circle-stroke-color': layerConfig.style?.borderColor, + 'circle-radius': documentLayerConfig.style?.markerSize, + 'circle-color': documentLayerConfig.style?.fillColor, + 'circle-opacity': documentLayerConfig.opacity / 100, + 'circle-stroke-width': documentLayerConfig.style?.borderThickness, + 'circle-stroke-color': documentLayerConfig.style?.borderColor, }, }, - mbLayerBeforeId + beforeId + ); + maplibreInstance?.setLayoutProperty( + circleLayerId, + 'visibility', + documentLayerConfig.visibility ); - maplibreInstance?.setLayoutProperty(layerConfig.id, 'visibility', layerConfig.visibility); }; - const addGeoShapeLayer = (source: any) => { - source.features.map((feature: any, index: number) => { - const mbType = GeoJSONMaplibreMap.get(feature.geometry.type); - const mbLayerId = `${layerConfig.id}-${index}`; - if (mbType === 'circle') { - maplibreInstance?.addLayer( - { - id: mbLayerId, - type: 'circle', - source: layerConfig.id, - filter: ['==', '$type', 'Point'], - paint: { - 'circle-radius': layerConfig.style?.markerSize, - 'circle-color': layerConfig.style?.fillColor, - 'circle-opacity': layerConfig.opacity / 100, - 'circle-stroke-width': layerConfig.style?.borderThickness, - 'circle-stroke-color': layerConfig.style?.borderColor, - }, - }, - mbLayerBeforeId - ); - maplibreInstance?.setLayoutProperty(mbLayerId, 'visibility', layerConfig.visibility); - } else if (mbType === 'line') { - maplibreInstance?.addLayer( - { - id: mbLayerId, - type: 'line', - source: layerConfig.id, - filter: ['==', '$type', 'LineString'], - paint: { - 'line-color': layerConfig.style?.fillColor, - 'line-opacity': layerConfig.opacity / 100, - 'line-width': layerConfig.style?.borderThickness, - }, - }, - mbLayerBeforeId - ); - maplibreInstance?.setLayoutProperty(mbLayerId, 'visibility', layerConfig.visibility); - } else if (mbType === 'fill') { - const polygonBorderLayerId = `${mbLayerId}-border`; - maplibreInstance?.addLayer( - { - id: mbLayerId, - type: 'fill', - source: layerConfig.id, - filter: ['==', '$type', 'Polygon'], - paint: { - 'fill-color': layerConfig.style?.fillColor, - 'fill-opacity': layerConfig.opacity / 100, - 'fill-outline-color': layerConfig.style?.borderColor, - }, - }, - mbLayerBeforeId - ); - maplibreInstance?.setLayoutProperty(mbLayerId, 'visibility', layerConfig.visibility); - // Add boarder for polygon - maplibreInstance?.addLayer( - { - id: polygonBorderLayerId, - type: 'line', - source: layerConfig.id, - filter: ['==', '$type', 'Polygon'], - paint: { - 'line-color': layerConfig.style?.borderColor, - 'line-opacity': layerConfig.opacity / 100, - 'line-width': layerConfig.style?.borderThickness, - }, - }, - mbLayerBeforeId - ); - maplibreInstance?.setLayoutProperty( - polygonBorderLayerId, - 'visibility', - layerConfig.visibility - ); - } - }); + const addFillLayer = ( + documentLayerConfig: DocumentLayerSpecification, + beforeId: string | undefined + ) => { + const fillLayerId = buildLayerSuffix(documentLayerConfig.id, 'fill'); + maplibreInstance?.addLayer( + { + id: fillLayerId, + type: 'fill', + source: layerConfig.id, + filter: ['==', '$type', 'Polygon'], + paint: { + 'fill-color': documentLayerConfig.style?.fillColor, + 'fill-opacity': documentLayerConfig.opacity / 100, + }, + }, + beforeId + ); + maplibreInstance?.setLayoutProperty(fillLayerId, 'visibility', documentLayerConfig.visibility); + // Due to limitations on WebGL, fill can't render outlines with width wider than 1, + // so we have to create another style layer with type=line to apply width. + const outlineId = buildLayerSuffix(documentLayerConfig.id, 'fill-outline'); + maplibreInstance?.addLayer( + { + id: outlineId, + type: 'line', + source: layerConfig.id, + filter: ['==', '$type', 'Polygon'], + paint: { + 'line-color': layerConfig.style?.borderColor, + 'line-opacity': layerConfig.opacity / 100, + 'line-width': layerConfig.style?.borderThickness, + }, + }, + beforeId + ); + maplibreInstance?.setLayoutProperty(outlineId, 'visibility', layerConfig.visibility); }; + if (maplibreInstance) { const source = getLayerSource(data, layerConfig); maplibreInstance.addSource(layerConfig.id, { type: 'geojson', data: source, }); + addCircleLayer(layerConfig, mbLayerBeforeId); const geoFieldType = getGeoFieldType(layerConfig); - if (geoFieldType === 'geo_point') { - addGeoPointLayer(); - } else { - addGeoShapeLayer(source); + if (geoFieldType === 'geo_shape') { + addLineLayer(layerConfig, mbLayerBeforeId); + addFillLayer(layerConfig, mbLayerBeforeId); } } }; +const updateCircleLayer = ( + maplibreInstance: Maplibre, + documentLayerConfig: DocumentLayerSpecification +) => { + const circleLayerId = buildLayerSuffix(documentLayerConfig.id, 'circle'); + const circleLayerStyle = documentLayerConfig.style; + maplibreInstance?.setLayerZoomRange( + circleLayerId, + documentLayerConfig.zoomRange[0], + documentLayerConfig.zoomRange[1] + ); + maplibreInstance?.setPaintProperty( + circleLayerId, + 'circle-opacity', + documentLayerConfig.opacity / 100 + ); + maplibreInstance?.setPaintProperty(circleLayerId, 'circle-color', circleLayerStyle?.fillColor); + maplibreInstance?.setPaintProperty( + circleLayerId, + 'circle-stroke-color', + circleLayerStyle?.borderColor + ); + maplibreInstance?.setPaintProperty( + circleLayerId, + 'circle-stroke-width', + circleLayerStyle?.borderThickness + ); + maplibreInstance?.setPaintProperty(circleLayerId, 'circle-radius', circleLayerStyle?.markerSize); +}; + +const updateLineLayer = ( + maplibreInstance: Maplibre, + documentLayerConfig: DocumentLayerSpecification +) => { + const lineLayerId = buildLayerSuffix(documentLayerConfig.id, 'line'); + maplibreInstance?.setLayerZoomRange( + lineLayerId, + documentLayerConfig.zoomRange[0], + documentLayerConfig.zoomRange[1] + ); + maplibreInstance?.setPaintProperty( + lineLayerId, + 'line-opacity', + documentLayerConfig.opacity / 100 + ); + maplibreInstance?.setPaintProperty( + lineLayerId, + 'line-color', + documentLayerConfig.style?.fillColor + ); + maplibreInstance?.setPaintProperty( + lineLayerId, + 'line-width', + documentLayerConfig.style?.borderThickness + ); +}; + +const updateFillLayer = ( + maplibreInstance: Maplibre, + documentLayerConfig: DocumentLayerSpecification +) => { + const fillLayerId = buildLayerSuffix(documentLayerConfig.id, 'fill'); + maplibreInstance?.setLayerZoomRange( + fillLayerId, + documentLayerConfig.zoomRange[0], + documentLayerConfig.zoomRange[1] + ); + maplibreInstance?.setPaintProperty( + fillLayerId, + 'fill-opacity', + documentLayerConfig.opacity / 100 + ); + maplibreInstance?.setPaintProperty( + fillLayerId, + 'fill-color', + documentLayerConfig.style?.fillColor + ); + maplibreInstance?.setPaintProperty( + fillLayerId, + 'fill-outline-color', + documentLayerConfig.style?.borderColor + ); + const outlineLayerId = buildLayerSuffix(documentLayerConfig.id, 'fill-outline'); + maplibreInstance?.setPaintProperty( + outlineLayerId, + 'line-color', + documentLayerConfig.style?.borderColor + ); + maplibreInstance?.setPaintProperty( + outlineLayerId, + 'line-width', + documentLayerConfig.style?.borderThickness + ); +}; + const updateLayerConfig = ( layerConfig: DocumentLayerSpecification, maplibreRef: MaplibreRef, @@ -233,116 +340,11 @@ const updateLayerConfig = ( // @ts-ignore dataSource.setData(getLayerSource(data, layerConfig)); } + updateCircleLayer(maplibreInstance, layerConfig); const geoFieldType = getGeoFieldType(layerConfig); - if (geoFieldType === 'geo_point') { - maplibreInstance?.setLayerZoomRange( - layerConfig.id, - layerConfig.zoomRange[0], - layerConfig.zoomRange[1] - ); - maplibreInstance?.setPaintProperty( - layerConfig.id, - 'circle-opacity', - layerConfig.opacity / 100 - ); - maplibreInstance?.setPaintProperty( - layerConfig.id, - 'circle-color', - layerConfig.style?.fillColor - ); - maplibreInstance?.setPaintProperty( - layerConfig.id, - 'circle-stroke-color', - layerConfig.style?.borderColor - ); - maplibreInstance?.setPaintProperty( - layerConfig.id, - 'circle-stroke-width', - layerConfig.style?.borderThickness - ); - maplibreInstance?.setPaintProperty( - layerConfig.id, - 'circle-radius', - layerConfig.style?.markerSize - ); - } else { - getCurrentStyleLayers(maplibreRef).forEach((layer) => { - if (layer.id.includes(layerConfig.id)) { - maplibreInstance.setLayerZoomRange( - layer.id, - layerConfig.zoomRange[0], - layerConfig.zoomRange[1] - ); - if (layer.type === 'circle') { - maplibreInstance?.setPaintProperty( - layer.id, - 'circle-opacity', - layerConfig.opacity / 100 - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'circle-color', - layerConfig.style?.fillColor - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'circle-stroke-color', - layerConfig.style?.borderColor - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'circle-stroke-width', - layerConfig.style?.borderThickness - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'circle-radius', - layerConfig.style?.markerSize - ); - } else if (layer.type === 'line') { - if (layer.id.includes('border')) { - maplibreInstance?.setPaintProperty( - layer.id, - 'line-color', - layerConfig.style?.borderColor - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'line-width', - layerConfig.style?.borderThickness - ); - } else { - maplibreInstance?.setPaintProperty( - layer.id, - 'line-opacity', - layerConfig.opacity / 100 - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'line-color', - layerConfig.style?.fillColor - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'line-width', - layerConfig.style?.borderThickness - ); - } - } else if (layer.type === 'fill') { - maplibreInstance?.setPaintProperty(layer.id, 'fill-opacity', layerConfig.opacity / 100); - maplibreInstance?.setPaintProperty( - layer.id, - 'fill-color', - layerConfig.style?.fillColor - ); - maplibreInstance?.setPaintProperty( - layer.id, - 'fill-outline-color', - layerConfig.style?.borderColor - ); - } - } - }); + if (geoFieldType === 'geo_shape') { + updateLineLayer(maplibreInstance, layerConfig); + updateFillLayer(maplibreInstance, layerConfig); } } }; diff --git a/public/model/layerRenderController.ts b/public/model/layerRenderController.ts index 5e710d17..2f34cde3 100644 --- a/public/model/layerRenderController.ts +++ b/public/model/layerRenderController.ts @@ -4,10 +4,12 @@ */ import { Map as Maplibre } from 'maplibre-gl'; -import { MapLayerSpecification } from './mapLayerType'; +import { DocumentLayerSpecification, MapLayerSpecification } from './mapLayerType'; import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../common'; import { buildOpenSearchQuery, + Filter, + GeoBoundingBoxFilter, getTime, IOpenSearchDashboardsSearchResponse, isCompleteResponse, @@ -20,10 +22,23 @@ interface MaplibreRef { current: Maplibre | null; } +// OpenSearch only accepts longitude in range [-180, 180] +// Maplibre could return value out of the range +function adjustLongitudeForSearch(lon: number) { + if (lon < -180) { + return -180; + } + if (lon > 180) { + return 180; + } + return lon; +} + export const prepareDataLayerSource = ( layer: MapLayerSpecification, mapState: MapState, - { data, notifications }: MapServices + { data, notifications }: MapServices, + filters: Filter[] = [] ): Promise => { return new Promise(async (resolve, reject) => { if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) { @@ -42,6 +57,7 @@ export const prepareDataLayerSource = ( indexPattern, [], [ + ...filters, ...(layer.source.filters ? layer.source.filters : []), ...(timeFilters ? [timeFilters] : []), ] @@ -79,13 +95,48 @@ export const prepareDataLayerSource = ( }; export const handleDataLayerRender = ( - mapLayer: MapLayerSpecification, + mapLayer: DocumentLayerSpecification, mapState: MapState, services: MapServices, maplibreRef: MaplibreRef, beforeLayerId: string | undefined ) => { - return prepareDataLayerSource(mapLayer, mapState, services).then((result) => { + const filters: Filter[] = []; + const geoField = mapLayer.source.geoFieldName; + const geoFieldType = mapLayer.source.geoFieldType; + + // geo bounding box query supports geo_point fields + if ( + geoFieldType === 'geo_point' && + mapLayer.source.useGeoBoundingBoxFilter && + maplibreRef.current + ) { + const mapBounds = maplibreRef.current.getBounds(); + const filterBoundingBox = { + bottom_right: { + lon: adjustLongitudeForSearch(mapBounds.getSouthEast().lng), + lat: mapBounds.getSouthEast().lat, + }, + top_left: { + lon: adjustLongitudeForSearch(mapBounds.getNorthWest().lng), + lat: mapBounds.getNorthWest().lat, + }, + }; + const geoBoundingBoxFilter: GeoBoundingBoxFilter = { + meta: { + disabled: false, + negate: false, + alias: null, + params: filterBoundingBox, + }, + geo_bounding_box: { + [geoField]: filterBoundingBox, + }, + }; + filters.push(geoBoundingBoxFilter); + } + + return prepareDataLayerSource(mapLayer, mapState, services, filters).then((result) => { const { layer, dataSource } = result; if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) { layersFunctionMap[layer.type].render(maplibreRef, layer, dataSource, beforeLayerId); diff --git a/public/model/mapLayerType.ts b/public/model/mapLayerType.ts index e792c2f4..0d558103 100644 --- a/public/model/mapLayerType.ts +++ b/public/model/mapLayerType.ts @@ -43,6 +43,7 @@ export type DocumentLayerSpecification = { documentRequestNumber: number; showTooltips: boolean; tooltipFields: string[]; + useGeoBoundingBoxFilter: boolean; filters: Filter[]; }; style: { diff --git a/public/utils/geo_formater.ts b/public/utils/geo_formater.ts index 2af1933c..2c0c274b 100644 --- a/public/utils/geo_formater.ts +++ b/public/utils/geo_formater.ts @@ -50,6 +50,6 @@ export function convertGeoPointToGeoJSON(location: any) { if (values && (values.length === 2 || values.length === 3)) { return buildGeoJSONOfTypePoint(parseFloat(values[1].trim()), parseFloat(values[0].trim())); } - // TODO Geopoint as geohash & WKT Format + // TODO Geopoint as geohash return undefined; } diff --git a/tsconfig.json b/tsconfig.json index 6836455d..01fa7676 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,58 +1,58 @@ { - "compilerOptions": { - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - // Allows for importing from `opensearch-dashboards` package for the exported types. - "opensearch-dashboards": ["./opensearch_dashboards"], - "ui/*": ["src/legacy/ui/public/*"], - "test_utils/*": ["src/test_utils/public/*"] - }, - // Support .tsx files and transform JSX into calls to React.createElement - "jsx": "react", - // Enables all strict type checking options. - "strict": true, - // enables "core language features" - "lib": [ - // ESNext auto includes previous versions all the way back to es5 - "esnext", - // includes support for browser APIs - "dom" - ], - // Node 8 should support everything output by esnext, we override this - // in webpack with loader-level compiler options - "target": "esnext", - // Use commonjs for node, overridden in webpack to keep import statements - // to maintain support for things like `await import()` - "module": "commonjs", - // Allows default imports from modules with no default export. This does not affect code emit, just type checking. - // We have to enable this option explicitly since `esModuleInterop` doesn't enable it automatically when ES2015 or - // ESNext module format is used. - "allowSyntheticDefaultImports": true, - // Emits __importStar and __importDefault helpers for runtime babel ecosystem compatibility. - "esModuleInterop": true, - // Resolve modules in the same way as Node.js. Aka make `require` works the - // same in TypeScript as it does in Node.js. - "moduleResolution": "node", - // Disallow inconsistently-cased references to the same file. - "forceConsistentCasingInFileNames": true, - // Disable the breaking keyof behaviour introduced in TS 2.9.2 until EUI is updated to support that too - "keyofStringsOnly": true, - // Forbid unused local variables as the rule was deprecated by ts-lint - "noUnusedLocals": true, - // Provide full support for iterables in for..of, spread and destructuring when targeting ES5 or ES3. - "downlevelIteration": true, - // import tslib helpers rather than inlining helpers for iteration or spreading, for instance - "importHelpers": true, - // adding global typings - "types": ["node", "jest", "react"] + "compilerOptions": { + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + // Allows for importing from `opensearch-dashboards` package for the exported types. + "opensearch-dashboards": ["./opensearch_dashboards"], + "ui/*": ["src/legacy/ui/public/*"], + "test_utils/*": ["src/test_utils/public/*"] }, - "include": [ - "server/**/*", - "public/**/*", - "utils/**/*", - "models/**/*", - "test/**/*" + // Support .tsx files and transform JSX into calls to React.createElement + "jsx": "react", + // Enables all strict type checking options. + "strict": true, + // enables "core language features" + "lib": [ + // ESNext auto includes previous versions all the way back to es5 + "esnext", + // includes support for browser APIs + "dom" ], - "exclude": ["node_modules", "*/node_modules/"] - } + // Node 8 should support everything output by esnext, we override this + // in webpack with loader-level compiler options + "target": "esnext", + // Use commonjs for node, overridden in webpack to keep import statements + // to maintain support for things like `await import()` + "module": "commonjs", + // Allows default imports from modules with no default export. This does not affect code emit, just type checking. + // We have to enable this option explicitly since `esModuleInterop` doesn't enable it automatically when ES2015 or + // ESNext module format is used. + "allowSyntheticDefaultImports": true, + // Emits __importStar and __importDefault helpers for runtime babel ecosystem compatibility. + "esModuleInterop": true, + // Resolve modules in the same way as Node.js. Aka make `require` works the + // same in TypeScript as it does in Node.js. + "moduleResolution": "node", + // Disallow inconsistently-cased references to the same file. + "forceConsistentCasingInFileNames": true, + // Disable the breaking keyof behaviour introduced in TS 2.9.2 until EUI is updated to support that too + "keyofStringsOnly": true, + // Forbid unused local variables as the rule was deprecated by ts-lint + "noUnusedLocals": true, + // Provide full support for iterables in for..of, spread and destructuring when targeting ES5 or ES3. + "downlevelIteration": true, + // import tslib helpers rather than inlining helpers for iteration or spreading, for instance + "importHelpers": true, + // adding global typings + "types": ["node", "jest", "react"] + }, + "include": [ + "server/**/*", + "public/**/*", + "utils/**/*", + "models/**/*", + "test/**/*" + ], + "exclude": ["node_modules", "*/node_modules/"] +} diff --git a/yarn.lock b/yarn.lock index a1ab629b..f21443e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -137,6 +137,11 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== +"@types/wellknown@^0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@types/wellknown/-/wellknown-0.5.4.tgz#1f12a2ca9cda236673272688b7549a30f0c9e3bb" + integrity sha512-zcsf4oHeEcvpvWhDOB1+qNK04oFJtsmv7Otb1Vy8w8GAqQkgRrVkI/C76PdwtpiT216aM1SbhkKgKWzGLhHKng== + "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -367,11 +372,25 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@~1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" + integrity sha512-H6xsIBfQ94aESBG8jGHXQ7i5AEpy5ZeVaLDOisDICiTCKpqEfr34/KmTrspKQNoLKNu9gTkovlpQcUi630AKiQ== + dependencies: + inherits "~2.0.1" + readable-stream "~2.0.0" + typedarray "~0.0.5" + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cross-spawn@^7.0.0: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -717,7 +736,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@~2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -772,6 +791,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -939,7 +963,7 @@ minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.6: +minimist@^1.2.6, minimist@~1.2.0: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== @@ -1040,6 +1064,11 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== + protocol-buffers-schema@^3.3.1: version "3.6.0" resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" @@ -1078,6 +1107,18 @@ quickselect@^2.0.0: resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== +readable-stream@~2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + integrity sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + request-progress@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" @@ -1195,6 +1236,11 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -1280,6 +1326,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +typedarray@~0.0.5: + version "0.0.7" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.7.tgz#799207136a37f3b3efb8c66c40010d032714dc73" + integrity sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -1290,6 +1341,11 @@ untildify@^4.0.0: resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -1318,6 +1374,14 @@ vt-pbf@^3.1.3: "@mapbox/vector-tile" "^1.3.1" pbf "^3.2.1" +wellknown@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wellknown/-/wellknown-0.5.0.tgz#09ae9871fa826cf0a6ec1537ef00c379d78d7101" + integrity sha512-za5vTLuPF9nmrVOovYQwNEWE/PwJCM+yHMAj4xN1WWUvtq9OElsvKiPL0CR9rO8xhrYqL7NpI7IknqR8r6eYOg== + dependencies: + concat-stream "~1.5.0" + minimist "~1.2.0" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"