diff --git a/maps_dashboards/common/index.ts b/maps_dashboards/common/index.ts
index e131abe8..a45056be 100644
--- a/maps_dashboards/common/index.ts
+++ b/maps_dashboards/common/index.ts
@@ -6,7 +6,9 @@
export const PLUGIN_ID = 'maps-dashboards';
export const PLUGIN_NAME = 'Maps';
-export const MAP_VECTOR_TILE_URL = 'https://tiles.maps.opensearch.org/styles/basic.json';
+export const MAP_VECTOR_TILE_BASIC_STYLE = 'https://tiles.maps.opensearch.org/styles/basic.json';
+export const MAP_GLYPHS = 'https://tiles.maps.opensearch.org/fonts/{fontstack}/{range}.pbf';
+export const MAP_VECTOR_TILE_DATA_SOURCE = 'https://tiles.maps.opensearch.org/data/v1.json';
// Starting position [lng, lat] and zoom
export const MAP_INITIAL_STATE = {
diff --git a/maps_dashboards/package.json b/maps_dashboards/package.json
index 44692ffa..8b56ebe9 100644
--- a/maps_dashboards/package.json
+++ b/maps_dashboards/package.json
@@ -8,6 +8,8 @@
"osd": "node ../../scripts/osd"
},
"dependencies": {
- "maplibre-gl": "^2.4.0"
+ "maplibre-gl": "^2.4.0",
+ "uuid": "3.3.2",
+ "prettier": "^2.1.1"
}
}
diff --git a/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx b/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx
index 45fa843f..6f88f464 100644
--- a/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx
+++ b/maps_dashboards/public/components/add_layer_panel/add_layer_panel.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { Fragment, useState } from 'react';
+import React, { useState } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
@@ -11,27 +11,36 @@ import {
EuiModalBody,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiSpacer,
- EuiTabbedContent,
+ EuiHorizontalRule,
EuiTitle,
EuiButton,
EuiIcon,
EuiKeyPadMenuItem,
} from '@elastic/eui';
import './add_layer_panel.scss';
-import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../../common';
+import { v4 as uuidv4 } from 'uuid';
+import { DASHBOARDS_MAPS_LAYER_TYPE, LAYER_VISIBILITY } from '../../../common';
interface Props {
setIsLayerConfigVisible: Function;
setSelectedLayerConfig: Function;
- addNewLayerToList: Function;
}
export const AddLayerPanel = ({ setIsLayerConfigVisible, setSelectedLayerConfig }: Props) => {
const [isAddNewLayerModalVisible, setIsAddNewLayerModalVisible] = useState(false);
function onClickAddNewLayer(layerItem: string) {
- // Will add new layer logic
+ if (layerItem === DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP) {
+ setSelectedLayerConfig({
+ iconType: 'visMapRegion',
+ name: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
+ type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
+ id: uuidv4(),
+ zoomRange: [0, 22],
+ opacity: 1,
+ visibility: LAYER_VISIBILITY.VISIBLE,
+ });
+ }
setIsAddNewLayerModalVisible(false);
setIsLayerConfigVisible(true);
}
@@ -56,31 +65,6 @@ export const AddLayerPanel = ({ setIsLayerConfigVisible, setSelectedLayerConfig
const closeModal = () => setIsAddNewLayerModalVisible(false);
const showModal = () => setIsAddNewLayerModalVisible(true);
- const tabs = [
- {
- id: 'select-layer-id',
- name: 'Select layer',
- content: (
-
-
- {availableLayers}
-
- ),
- },
- {
- id: 'import-id',
- name: 'Import',
- content: (
-
-
-
- Import GeoJSON or JSON
-
-
- ),
- },
- ];
-
return (
@@ -96,12 +80,11 @@ export const AddLayerPanel = ({ setIsLayerConfigVisible, setSelectedLayerConfig
-
+
+
+ Reference layer
+
+ {availableLayers}
)}
diff --git a/maps_dashboards/public/components/layer_config/layer_config_panel.tsx b/maps_dashboards/public/components/layer_config/layer_config_panel.tsx
index f1802d4b..e879330a 100644
--- a/maps_dashboards/public/components/layer_config/layer_config_panel.tsx
+++ b/maps_dashboards/public/components/layer_config/layer_config_panel.tsx
@@ -23,7 +23,6 @@ interface Props {
setIsLayerConfigVisible: Function;
selectedLayerConfig: ILayerConfig;
setSelectedLayerConfig: Function;
- addNewLayerFunction: Function;
updateLayer: Function;
}
@@ -39,7 +38,6 @@ export const LayerConfigPanel = ({
};
const onUpdate = () => {
updateLayer();
- selectedLayerConfig.update?.();
onClose();
};
diff --git a/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx b/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx
index 3f1c71d9..f690c2b7 100644
--- a/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx
+++ b/maps_dashboards/public/components/layer_control_panel/layer_control_panel.tsx
@@ -17,10 +17,16 @@ import {
import { I18nProvider } from '@osd/i18n/react';
import { Map as Maplibre } from 'maplibre-gl';
import './layer_control_panel.scss';
+import { v4 as uuidv4 } from 'uuid';
import { AddLayerPanel } from '../add_layer_panel';
import { LayerConfigPanel } from '../layer_config';
import { ILayerConfig } from '../../model/ILayerConfig';
-import { DASHBOARDS_MAPS_LAYER_TYPE, MAP_VECTOR_TILE_URL, LAYER_VISIBILITY } from '../../../common';
+import {
+ DASHBOARDS_MAPS_LAYER_TYPE,
+ LAYER_VISIBILITY,
+ MAP_VECTOR_TILE_BASIC_STYLE,
+} from '../../../common';
+import { layersFunctionMap } from '../../model/layersFunctions';
interface MaplibreRef {
current: Maplibre | null;
@@ -44,81 +50,45 @@ const LayerControlPanel = ({ maplibreRef }: Props) => {
visibility: '',
});
- const [layers, setLayers] = useState([
- {
- iconType: 'visMapRegion',
- id: 'example_id_1',
- type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
- name: 'Base Map Layer',
- zoomRange: [0, 12],
- opacity: 1,
- visibility: LAYER_VISIBILITY.VISIBLE,
- update() {
- const maplibreInstance = maplibreRef.current;
- if (maplibreInstance) {
- const baseMapJson = maplibreInstance.getStyle().layers;
- if (baseMapJson) {
- baseMapJson.forEach((mbLayer) => {
- maplibreInstance.setLayerZoomRange(mbLayer.id, this.zoomRange[0], this.zoomRange[1]);
- // it will catch error when update opacity in symbol type layer, need figure out later
- if (mbLayer.type === 'symbol') {
- return;
- }
- maplibreInstance.setPaintProperty(
- mbLayer.id,
- `${mbLayer.type}-opacity`,
- this.opacity
- );
- maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', this.visibility);
- });
- } else {
- maplibreInstance.setStyle(MAP_VECTOR_TILE_URL);
- }
- }
- },
- hide() {
- const maplibreInstance = maplibreRef.current;
- if (maplibreInstance) {
- const baseMapJson = maplibreInstance.getStyle().layers;
- if (baseMapJson) {
- baseMapJson.forEach((mbLayer) => {
- maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', this.visibility);
- });
- }
- }
- },
- remove() {
- const maplibreInstance = maplibreRef.current;
- if (maplibreInstance) {
- const baseMapJson = maplibreInstance.getStyle().layers;
- if (baseMapJson) {
- baseMapJson.forEach((mbLayer) => {
- maplibreInstance.removeLayer(mbLayer.id);
- });
- }
- }
- },
+ const initialLoadLayer: ILayerConfig = {
+ iconType: 'visMapRegion',
+ id: uuidv4(),
+ type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
+ name: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
+ zoomRange: [0, 22],
+ opacity: 1,
+ visibility: LAYER_VISIBILITY.VISIBLE,
+ layerSpec: {
+ OSMUrl: MAP_VECTOR_TILE_BASIC_STYLE,
},
- ]);
+ };
+
+ const [layers, setLayers] = useState([initialLoadLayer]);
useEffect(() => {
- layers.forEach((layer) => {
- layer.update?.();
+ maplibreRef.current?.on('load', function () {
+ layers.forEach((layer) => {
+ layersFunctionMap[layer.type]?.initial(maplibreRef, layer);
+ });
});
- }, [layers]);
+ }, []);
- const updateLayers = () => {
+ const updateLayer = () => {
const layersClone = [...layers];
const index = layersClone.findIndex((layer) => layer.id === selectedLayerConfig.id);
- if (index > -1) {
+ if (index <= -1) {
+ layersClone.push(selectedLayerConfig);
+ layersFunctionMap[selectedLayerConfig.type]?.addNewLayer(maplibreRef, selectedLayerConfig);
+ } else {
layersClone[index] = {
...layersClone[index],
...selectedLayerConfig,
};
- } else {
- layersClone.push(selectedLayerConfig);
}
setLayers(layersClone);
+ setTimeout(function () {
+ layersFunctionMap[selectedLayerConfig.type]?.update(maplibreRef, selectedLayerConfig);
+ }, 50);
};
const removeLayer = (index: number) => {
@@ -188,7 +158,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => {
{
if (layer.visibility === LAYER_VISIBILITY.VISIBLE) {
@@ -196,7 +166,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => {
} else {
layer.visibility = LAYER_VISIBILITY.VISIBLE;
}
- layer.hide(index);
+ layersFunctionMap[layer.type]?.hide(maplibreRef, layer);
}}
aria-label="Hide or show layer"
color="text"
@@ -208,7 +178,7 @@ const LayerControlPanel = ({ maplibreRef }: Props) => {
size="s"
iconType="trash"
onClick={() => {
- layer.remove(index);
+ layersFunctionMap[layer.type]?.remove(maplibreRef, layer);
removeLayer(index);
}}
aria-label="Delete layer"
@@ -226,14 +196,13 @@ const LayerControlPanel = ({ maplibreRef }: Props) => {
)}
diff --git a/maps_dashboards/public/components/map_container/map_container.scss b/maps_dashboards/public/components/map_container/map_container.scss
index e73cad5b..be555172 100644
--- a/maps_dashboards/public/components/map_container/map_container.scss
+++ b/maps_dashboards/public/components/map_container/map_container.scss
@@ -22,3 +22,10 @@
left: $euiSizeS;
top: $euiSizeS;
}
+
+.zoombar {
+ z-index: 1;
+ position: absolute;
+ bottom: $euiSizeM;
+ right: $euiSizeS;
+}
diff --git a/maps_dashboards/public/components/map_container/map_container.tsx b/maps_dashboards/public/components/map_container/map_container.tsx
index 8d53d3e7..8615a91d 100644
--- a/maps_dashboards/public/components/map_container/map_container.tsx
+++ b/maps_dashboards/public/components/map_container/map_container.tsx
@@ -3,36 +3,63 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useEffect, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
+import { EuiPanel } from '@elastic/eui';
import { Map as Maplibre, NavigationControl } from 'maplibre-gl';
import { LayerControlPanel } from '../layer_control_panel';
import './map_container.scss';
-import { MAP_VECTOR_TILE_URL, MAP_INITIAL_STATE } from '../../../common/index';
+import { MAP_INITIAL_STATE, MAP_GLYPHS, MAP_VECTOR_TILE_DATA_SOURCE } from '../../../common';
export const MapContainer = () => {
const maplibreRef = useRef(null);
const mapContainer = useRef(null);
+ const [mounted, setMounted] = useState(false);
+ const [zoom, setZoom] = useState(MAP_INITIAL_STATE.zoom);
useEffect(() => {
- const mapStyle = MAP_VECTOR_TILE_URL;
- const initialState = MAP_INITIAL_STATE;
+ const mbStyle = {
+ version: 8 as 8,
+ sources: {},
+ layers: [],
+ glyphs: MAP_GLYPHS,
+ };
- const maplibre = new Maplibre({
+ maplibreRef.current = new Maplibre({
// @ts-ignore
container: mapContainer.current,
- center: [initialState.lng, initialState.lat],
- zoom: initialState.zoom,
+ center: [MAP_INITIAL_STATE.lng, MAP_INITIAL_STATE.lat],
+ zoom,
+ style: mbStyle,
});
- maplibre.setStyle(mapStyle);
- maplibreRef.current = maplibre;
- maplibre.addControl(new NavigationControl({ showCompass: false }), 'top-right');
+ maplibreRef.current.addControl(new NavigationControl({ showCompass: false }), 'top-right');
+ maplibreRef.current.on('style.load', function () {
+ // @ts-ignore
+ maplibreRef.current.addSource('openmaptiles', {
+ type: 'vector',
+ url: MAP_VECTOR_TILE_DATA_SOURCE,
+ });
+ setMounted(true);
+ });
}, []);
+ useEffect(() => {
+ // @ts-ignore
+ maplibreRef.current.on('move', () => {
+ // @ts-ignore
+ return setZoom(maplibreRef.current.getZoom().toFixed(2));
+ });
+ }, [zoom]);
+
+ useEffect(() => {}, []);
+
return (
+
+ Zoom: {zoom}
+
-
+ {mounted && }
diff --git a/maps_dashboards/public/model/ILayerConfig.ts b/maps_dashboards/public/model/ILayerConfig.ts
index f9c54749..9a6dcd4e 100644
--- a/maps_dashboards/public/model/ILayerConfig.ts
+++ b/maps_dashboards/public/model/ILayerConfig.ts
@@ -11,7 +11,5 @@ export interface ILayerConfig {
zoomRange: number[];
opacity: number;
visibility: string;
- update: any;
- remove: any;
- hide: any;
+ layerSpec?: any;
}
diff --git a/maps_dashboards/public/model/OSMLayerFunctions.ts b/maps_dashboards/public/model/OSMLayerFunctions.ts
new file mode 100644
index 00000000..7cb565da
--- /dev/null
+++ b/maps_dashboards/public/model/OSMLayerFunctions.ts
@@ -0,0 +1,123 @@
+import { Map as Maplibre, LayerSpecification } from 'maplibre-gl';
+import { ILayerConfig } from './ILayerConfig';
+import { MAP_VECTOR_TILE_BASIC_STYLE } from '../../common';
+
+interface MaplibreRef {
+ current: Maplibre | null;
+}
+
+// Functions for OpenSearch maps vector tile layer
+export const OSMLayerFunctions = {
+ initial: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => {
+ const maplibreInstance = maplibreRef.current;
+ if (maplibreInstance) {
+ const renderStyleData = async () => {
+ try {
+ const response = await fetch(MAP_VECTOR_TILE_BASIC_STYLE);
+ const json = await response.json();
+ const styleLayers: LayerSpecification[] = await json.layers;
+ styleLayers.forEach((styleLayer) => {
+ styleLayer.id = styleLayer.id + '_' + layerConfig.id;
+ // @ts-ignore
+ maplibreRef.current.addLayer(styleLayer);
+ });
+ } catch (error) {
+ console.log('error', error);
+ }
+ };
+ renderStyleData();
+
+ // @ts-ignore
+ setTimeout(function () {
+ const mbLayerJson: LayerSpecification[] = maplibreRef.current.getStyle().layers;
+ mbLayerJson.map((mbLayer) => {
+ if (mbLayer.id.includes(layerConfig.id)) {
+ maplibreInstance.setLayerZoomRange(
+ mbLayer.id,
+ layerConfig.zoomRange[0],
+ layerConfig.zoomRange[1]
+ );
+ // TODO: figure out error reason
+ if (mbLayer.type === 'symbol') {
+ return;
+ }
+ maplibreInstance.setPaintProperty(
+ mbLayer.id,
+ `${mbLayer.type}-opacity`,
+ layerConfig.opacity
+ );
+ }
+ });
+ }, 50);
+ }
+ },
+ update: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => {
+ const maplibreInstance = maplibreRef.current;
+ if (maplibreInstance) {
+ const mbLayerJson = maplibreInstance.getStyle().layers;
+ mbLayerJson.forEach((mbLayer: { id: any; type: string }) => {
+ if (mbLayer.id.includes(layerConfig.id)) {
+ maplibreInstance.setLayerZoomRange(
+ mbLayer.id,
+ layerConfig.zoomRange[0],
+ layerConfig.zoomRange[1]
+ );
+ if (mbLayer.type === 'symbol') {
+ return;
+ }
+ maplibreInstance.setPaintProperty(
+ mbLayer.id,
+ `${mbLayer.type}-opacity`,
+ layerConfig.opacity
+ );
+ }
+ });
+ }
+ },
+ addNewLayer: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => {
+ const maplibreInstance = maplibreRef.current;
+ if (maplibreInstance) {
+ const renderStyleData = async () => {
+ try {
+ const response = await fetch(MAP_VECTOR_TILE_BASIC_STYLE);
+ const json = await response.json();
+ const styleLayers: LayerSpecification[] = await json.layers;
+ styleLayers.forEach((styleLayer) => {
+ styleLayer.id = styleLayer.id + '_' + layerConfig.id;
+ // @ts-ignore
+ maplibreRef.current.addLayer(styleLayer);
+ });
+ } catch (error) {
+ console.log('error', error);
+ }
+ };
+ renderStyleData();
+ }
+ },
+ remove: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => {
+ const maplibreInstance = maplibreRef.current;
+ if (maplibreInstance) {
+ const mbLayerJson = maplibreInstance.getStyle().layers;
+ if (mbLayerJson) {
+ mbLayerJson.forEach((mbLayer: { id: any }) => {
+ if (mbLayer.id.includes(layerConfig.id)) {
+ maplibreInstance.removeLayer(mbLayer.id);
+ }
+ });
+ }
+ }
+ },
+ hide: (maplibreRef: MaplibreRef, layerConfig: ILayerConfig) => {
+ const maplibreInstance = maplibreRef.current;
+ if (maplibreInstance) {
+ const mbLayerJson = maplibreInstance.getStyle().layers;
+ if (mbLayerJson) {
+ mbLayerJson.forEach((mbLayer: { id: any }) => {
+ if (mbLayer.id.includes(layerConfig.id)) {
+ maplibreInstance.setLayoutProperty(mbLayer.id, 'visibility', layerConfig.visibility);
+ }
+ });
+ }
+ }
+ },
+};
diff --git a/maps_dashboards/public/model/layersFunctions.ts b/maps_dashboards/public/model/layersFunctions.ts
new file mode 100644
index 00000000..eee50a51
--- /dev/null
+++ b/maps_dashboards/public/model/layersFunctions.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DASHBOARDS_MAPS_LAYER_TYPE } from '../../common';
+import { OSMLayerFunctions } from './OSMLayerFunctions';
+
+export const layersFunctionMap = {
+ [DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP]: OSMLayerFunctions,
+};
diff --git a/maps_dashboards/yarn.lock b/maps_dashboards/yarn.lock
index 4f6e9646..818b5a9f 100644
--- a/maps_dashboards/yarn.lock
+++ b/maps_dashboards/yarn.lock
@@ -183,6 +183,11 @@ potpack@^1.0.2:
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
+prettier@^2.1.1:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
+ integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
+
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"
@@ -212,6 +217,11 @@ tinyqueue@^2.0.3:
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
+uuid@3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
+ integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
+
vt-pbf@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac"