Procházet zdrojové kódy

空间分析实现矩形圆形、历史记录、优化代码

Hwf před 11 měsíci
rodič
revize
939eda9d4c

+ 77 - 5
package-lock.json

@@ -35,6 +35,7 @@
         "js-cookie": "3.0.5",
         "jsencrypt": "3.3.2",
         "konva": "^9.3.14",
+        "nanoid": "^5.0.7",
         "nprogress": "0.2.0",
         "ol": "^10.0.0",
         "pinia": "^2.1.7",
@@ -4967,6 +4968,24 @@
         "source-map-js": "^1.2.0"
       }
     },
+    "node_modules/@vue/compiler-sfc/node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
     "node_modules/@vue/compiler-sfc/node_modules/postcss": {
       "version": "8.4.40",
       "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.40.tgz",
@@ -9621,9 +9640,9 @@
       "dev": true
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "5.0.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.0.7.tgz",
+      "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
       "funding": [
         {
           "type": "github",
@@ -9631,10 +9650,10 @@
         }
       ],
       "bin": {
-        "nanoid": "bin/nanoid.cjs"
+        "nanoid": "bin/nanoid.js"
       },
       "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+        "node": "^18 || >=20"
       }
     },
     "node_modules/nanomatch": {
@@ -10399,6 +10418,24 @@
       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
       "dev": true
     },
+    "node_modules/postcss/node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
     "node_modules/posthtml": {
       "version": "0.9.2",
       "resolved": "https://registry.npmmirror.com/posthtml/-/posthtml-0.9.2.tgz",
@@ -12926,6 +12963,24 @@
       "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==",
       "dev": true
     },
+    "node_modules/vite/node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
     "node_modules/vite/node_modules/postcss": {
       "version": "8.4.40",
       "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.40.tgz",
@@ -13343,6 +13398,23 @@
       "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.25.tgz",
       "integrity": "sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA=="
     },
+    "node_modules/vue/node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
     "node_modules/vue/node_modules/postcss": {
       "version": "8.4.40",
       "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.40.tgz",

+ 1 - 0
package.json

@@ -44,6 +44,7 @@
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
     "konva": "^9.3.14",
+    "nanoid": "^5.0.7",
     "nprogress": "0.2.0",
     "ol": "^10.0.0",
     "pinia": "^2.1.7",

+ 5 - 3
src/components/Map/YMap.vue

@@ -24,11 +24,13 @@ import { reactive } from "vue";
 
 interface Props {
   activeMap: string;
+  drawing: boolean;
+  color: string;
+  drawType: string;
+  figureType: string;
 }
 
-const props = withDefaults(defineProps<Props>(), {
-  maxZoom: 16
-});
+const props = withDefaults(defineProps<Props>(), {});
 
 const mapState = reactive({
   center: [110.93154257997, 21.669064031332],

+ 76 - 212
src/components/Map/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="map-container">
-    <div id="yztMap" class="map-container"></div>
+    <div id="aMap" class="map-container"></div>
     <!-- 右下工具  -->
     <div v-show="mapState.showScale" class="zoom-text">{{ mapState.zoom }}级</div>
     <div class="right-tool">
@@ -20,14 +20,18 @@
 
 <script setup lang="ts" name="Map">
 import { onMounted, onUnmounted, reactive, watch, defineProps, withDefaults, ref } from 'vue';
-import AMapLoader from '@amap/amap-jsapi-loader';
 import QuickZoom from './quickZoom.vue';
+import { useAMap } from '@/hooks/AMap/useAMap';
+import { useHistory } from '@/hooks/useHistory';
+import { useMouseTool } from '@/hooks/AMap/useMouseTool';
+import { useRuler } from '@/hooks/AMap/useRuler';
 
 interface Props {
   activeMap: string;
   drawing: boolean;
   color: string;
   drawType: string;
+  graphicsType: string;
 }
 
 const props = withDefaults(defineProps<Props>(), {});
@@ -43,166 +47,80 @@ const mapState = reactive({
   showScale: true
 });
 
-let map, scale, nowLayer, ruler, mouseTool;
-const isRanging = ref(false);
-
-// 监听地图类型变化
-watch(
-  () => props.activeMap,
-  (value, oldValue) => {
+let AMap, map, scale;
+const { historyStack, currentIndex, pushHistory, undo } = useHistory();
+// 鼠标绘制工具
+const { initMouseTool, drawGraphics, setColor, setDrawType, setGraphicsType } = useMouseTool({
+  color: props.color,
+  drawType: props.drawType,
+  graphicsType: props.graphicsType,
+  onDrawCompleted: (data) => {
+    pushHistory(data);
+  }
+});
+// 测距工具
+const { initRuler, isRanging, toggleRangingTool } = useRuler();
+// 初始化地图
+const { getAMap, getMap, switchMap, addMarker, clearMarker, getMarkers } = useAMap({
+  key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
+  version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+  plugins: ['AMap.Scale', 'AMap.RangingTool', 'AMap.MouseTool', 'AMap.PolygonEditor'],
+  pitch: mapState.isThreeDimensional ? 45 : 0,
+  zoom: mapState.zoom,
+  center: [mapState.center[0], mapState.center[1]],
+  dragEnable: true,
+  scrollWheel: true,
+  showScale: true,
+  enableMouseTool: true,
+  onLoadCompleted: () => {
+    AMap = getAMap();
+    map = getMap();
     if (!['logical', 'vectorgraph'].includes(props.activeMap)) {
       switchMap(props.activeMap);
     } else {
-      map.removeLayer(nowLayer);
+      map.removeLayer();
     }
+    map.on('zoomchange', zoomChangeHandler);
+    initMouseTool(map, AMap);
+    initRuler(map, AMap);
+  }
+});
+// 监听地图类型变化
+watch(
+  () => props.activeMap,
+  (value, oldValue) => {
+    switchMap(props.activeMap);
   }
 );
-
-// 初始化事件
-const initMap = () => {
-  AMapLoader.load({
-    key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
-    version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
-    plugins: ['AMap.Scale', 'AMap.RangingTool', 'AMap.MouseTool']
-  }).then((AMap) => {
-    map = new AMap.Map('yztMap', {
-      // 是否为3D地图模式
-      viewMode: '3D',
-      pitch: mapState.isThreeDimensional ? 45 : 0,
-      // 初始化地图级别
-      zoom: mapState.zoom,
-      // 初始化地图中心点位置
-      center: [mapState.center[0], mapState.center[1]],
-      // 是否可拖拽
-      dragEnable: true,
-      // 是否允许通过鼠标滚轮来缩放
-      scrollWheel: true
-    });
-    // 初始化绘制工具
-    mouseTool = new AMap.MouseTool(map);
-    mouseTool.on('draw', function (event) {
-      // event.obj 为绘制出来的覆盖物对象
-      console.log('覆盖物对象绘制完成', event);
-      mouseTool.close();
-      emits('update:drawing', false);
-    });
-    if (!['logical', 'vectorgraph'].includes(props.activeMap)) {
-      switchMap(props.activeMap);
-    }
-    // else {
-    //   map.removeLayer();
-    // }
-
-    scale = new AMap.Scale();
-    if (mapState.showScale) {
-      map.addControl(scale);
-    }
-
-    // 自定义测距工具样式
-    const startMarkerOptions = {
-      icon: new AMap.Icon({
-        size: new AMap.Size(19, 31), // 图标大小
-        imageSize: new AMap.Size(19, 31),
-        image: '//webapi.amap.com/theme/v1.3/markers/b/start.png'
-      }),
-      offset: new AMap.Pixel(-9, -31)
-    };
-    const endMarkerOptions = {
-      icon: new AMap.Icon({
-        size: new AMap.Size(19, 31), // 图标大小
-        imageSize: new AMap.Size(19, 31),
-        image: '//webapi.amap.com/theme/v1.3/markers/b/end.png'
-      }),
-      offset: new AMap.Pixel(-9, -31)
-    };
-    const midMarkerOptions = {
-      icon: new AMap.Icon({
-        size: new AMap.Size(19, 31), // 图标大小
-        imageSize: new AMap.Size(19, 31),
-        image: '//webapi.amap.com/theme/v1.3/markers/b/mid.png'
-      }),
-      offset: new AMap.Pixel(-9, -31)
-    };
-    const lineOptions = {
-      strokeStyle: 'solid',
-      strokeColor: '#FF33FF',
-      strokeOpacity: 1,
-      strokeWeight: 2
-    };
-    const rulerOptions = {
-      startMarkerOptions: startMarkerOptions,
-      midMarkerOptions: midMarkerOptions,
-      endMarkerOptions: endMarkerOptions,
-      lineOptions: lineOptions
-    };
-
-    // 初始化自定义样式测距工具
-    ruler = new AMap.RangingTool(map, rulerOptions);
-
-    map.on('zoomchange', zoomChangeHandler);
-  });
-};
-
-// 切换测距工具状态
-const toggleRangingTool = () => {
-  if (ruler) {
-    if (isRanging.value) {
-      ruler.turnOff(); // 关闭测距工具
-    } else {
-      ruler.turnOn(); // 启动测距工具
+// 监听是否开启绘制
+watch(
+  () => props.drawing,
+  (value) => {
+    if (value) {
+      drawGraphics(props.graphicsType);
     }
-    isRanging.value = !isRanging.value; // 切换状态
   }
-};
-
-// 切换地图
-const switchMap = (type) => {
-  mapState.zoom = 11;
-  if (type === 'satellite') {
-    const satellite = new AMap.TileLayer.Satellite();
-    map.addLayer(satellite);
-    nowLayer = satellite;
-  } else if (type === 'satellite2') {
-    let queryParamsArr = [
-      {
-        serviceCode: 'YZT1715739306532'
-      },
-      {
-        serviceCode: 'YZT1695608158269',
-        REQUEST: 'GetCapabilities'
-      }
-    ];
-    console.log(queryParamsArr[0].serviceCode);
-    var wms = new AMap.TileLayer.WMTS({
-      // url: 'http://t0.tianditu.gov.cn/img_c/wmts',
-      url: 'http://10.181.7.236:9988/api/oneShare/proxyHandler/mm/' + queryParamsArr[0].serviceCode,
-      // blend: true,
-      tileSize: 256,
-      params: {
-        // Layer: "img",
-        // Version: "1.0.0",
-        // Format: "tiles",
-        // TileMatrixSet: "w",
-        // STYLE: "default",
-        // tk: 'd31190edaa4190af833234ab7b1a6f27'
-        SERVICE: 'WMTS',
-        // REQUEST: 'GetTile',
-        VERSION: '1.0.0',
-        LAYER: 'gds_2m_kj202401',
-        STYLE: 'default',
-        // TILEMATRIXSET: 'default028mm',
-        // TILEMATRIX: '9',
-        // TILEROW: '97',
-        // TILECOL: '413',
-        Format: 'image/jpgpng'
-      }
-    });
-    wms.setMap(map);
+);
+watch(
+  () => props.color,
+  () => {
+    setColor(props.color);
   }
-};
-
+);
+watch(
+  () => props.drawType,
+  () => {
+    setDrawType(props.drawType);
+  }
+);
+watch(
+  () => props.graphicsType,
+  () => {
+    setGraphicsType(props.graphicsType);
+  }
+);
 // 缩放级别变化
-const zoomChangeHandler = (event) => {
+const zoomChangeHandler = () => {
   mapState.zoom = map.getZoom();
 };
 
@@ -243,69 +161,15 @@ const switchThreeDimensional = () => {
   const pitch = mapState.isThreeDimensional ? 45 : 0;
   map.setPitch(pitch);
 };
-let markers = {};
-// 实例化点标记
-const addMarker = (item) => {
-  // 点标记显示内容,HTML要素字符串
-  var markerContent =
-    '' +
-    '<div class="custom-content-marker" style="display: flex;align-items: center;flex-direction: column;width: 100px;color: #000;font-size: 16px;">' +
-    '<div>' +
-    item.label +
-    '</div>' +
-    '   <img src="//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png" style="width: 20px;">' +
-    '</div>';
-  let marker = new AMap.Marker({
-    size: new AMap.Size(25, 34),
-    content: markerContent,
-    position: [item.center[0], item.center[1]],
-    offset: new AMap.Pixel(-13, -30)
-  });
-  marker.setMap(map);
-  if (markers[item.id]) {
-    markers[item.id].push(marker);
-  } else {
-    markers[item.id] = [marker];
-  }
-};
-// 清除所有标加
-const clearMarker = (id) => {
-  if (!markers[id]) return;
-  markers[id].forEach((marker) => {
-    marker.remove();
-  });
-  delete markers[id];
-};
-const getMarkers = () => {
-  return markers;
-};
 
-// 监听是否开启绘制
-watch(
-  () => props.drawing,
-  (value) => {
-    if (value) {
-      drawPolygon();
-    }
-  }
-);
-
-const drawPolygon = () => {
-  mouseTool.polygon({
-    strokeColor: props.color,
-    strokeOpacity: 1,
-    strokeWeight: 2,
-    fillColor: props.color,
-    fillOpacity: props.drawType === '1' ? 0 : 0.5,
-    strokeStyle: 'solid'
-  });
+// 处理撤销
+const handleUndo = () => {
+  undo();
+  map.clearMap();
+  console.log(historyStack.value.length, currentIndex.value);
 };
 
-defineExpose({ addMarker, getMarkers, clearMarker });
-// 加载事件
-onMounted(() => {
-  initMap();
-});
+defineExpose({ addMarker, getMarkers, clearMarker, handleUndo });
 
 // 卸载事件
 onUnmounted(() => {

+ 101 - 0
src/hooks/AMap/useAMap.ts

@@ -0,0 +1,101 @@
+import AMapLoader from '@amap/amap-jsapi-loader';
+
+export function useAMap(options) {
+  let AMap, map, nowLayer;
+  const markers = [];
+  // 初始化事件
+  const initMap = (options) => {
+    AMapLoader.load({
+      key: options.key, // 申请好的Web端开发者Key,首次调用 load 时必填
+      version: !!options.version ? options.version : '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
+      plugins: options.plugins ? options.plugins : []
+    }).then((res) => {
+      AMap = res;
+      map = new AMap.Map('aMap', {
+        // 是否为3D地图模式
+        viewMode: '3D',
+        pitch: options.pitch,
+        // 初始化地图级别
+        zoom: options.zoom ? options.zoom : 11,
+        // 初始化地图中心点位置
+        center: options.center,
+        // 是否可拖拽
+        dragEnable: options.dragEnable,
+        // 是否允许通过鼠标滚轮来缩放
+        scrollWheel: options.scrollWheel
+      });
+      // 初始化比例尺
+      if (options.showScale) {
+        map.addControl(new AMap.Scale());
+      }
+      if (typeof options.onLoadCompleted === 'function') {
+        options.onLoadCompleted(AMap, map);
+      }
+    });
+  };
+  const getAMap = () => {
+    return AMap;
+  };
+  const getMap = () => {
+    return map;
+  };
+  // 切换地图
+  const switchMap = (type: string) => {
+    if (type === 'vectorgraph') {
+      map.removeLayer(nowLayer);
+    } else if (type === 'satellite') {
+      const satellite = new AMap.TileLayer.Satellite();
+      map.addLayer(satellite);
+      nowLayer = satellite;
+    }
+  };
+  // 实例化点标记
+  const addMarker = (item) => {
+    // 点标记显示内容,HTML要素字符串
+    const markerContent =
+      '' +
+      '<div class="custom-content-marker" style="display: flex;align-items: center;flex-direction: column;width: 100px;color: #000;font-size: 16px;">' +
+      '<div>' +
+      item.label +
+      '</div>' +
+      '   <img src="//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png" style="width: 20px;">' +
+      '</div>';
+    const marker = new AMap.Marker({
+      size: new AMap.Size(25, 34),
+      content: markerContent,
+      position: [item.center[0], item.center[1]],
+      offset: new AMap.Pixel(-13, -30)
+    });
+    marker.setMap(map);
+    if (markers[item.id]) {
+      markers[item.id].push(marker);
+    } else {
+      markers[item.id] = [marker];
+    }
+  };
+
+  // 清除所有标加
+  const clearMarker = (id) => {
+    if (!markers[id]) return;
+    markers[id].forEach((marker) => {
+      marker.remove();
+    });
+    delete markers[id];
+  };
+
+  const getMarkers = () => {
+    return markers;
+  };
+
+  onMounted(() => {
+    initMap(options);
+  });
+  return {
+    getAMap,
+    getMap,
+    switchMap,
+    addMarker,
+    clearMarker,
+    getMarkers
+  };
+}

+ 110 - 0
src/hooks/AMap/useMouseTool.ts

@@ -0,0 +1,110 @@
+interface MouseToolOptions {
+  color: string;
+  drawType: string;
+  graphicsType: string;
+  onDrawCompleted: Function;
+}
+export function useMouseTool(options: MouseToolOptions) {
+  const drawOptions = {
+    strokeColor: options.color,
+    strokeOpacity: 1,
+    strokeWeight: 2,
+    fillColor: options.color,
+    fillOpacity: options.drawType === '1' ? 0 : 0.5,
+    strokeStyle: 'solid'
+  };
+
+  let graphicsType = options.graphicsType;
+  let mouseTool;
+  const initMouseTool = (map, AMap) => {
+    // 初始化绘制工具
+    mouseTool = new AMap.MouseTool(map);
+    mouseTool.on('draw', function (event) {
+      const obj = event.obj;
+      const data: any = {
+        type: getType(obj.className),
+        color: obj._opts.strokeColor,
+        drawType: obj._opts.fillOpacity === 0 ? '1' : '2'
+      };
+      if (data.type === 'circle') {
+        data.center = [obj.getCenter().lng, obj.getCenter().lat];
+        data.radius = obj.getRadius();
+      }
+      if (typeof options.onDrawCompleted === 'function') {
+        options.onDrawCompleted(data, event);
+      }
+      console.log('覆盖物对象绘制完成', data, event);
+    });
+  };
+  const getType = (type) => {
+    let res = '';
+    if (type === 'Overlay.Circle') {
+      res = 'circle';
+    } else if (type === 'Overlay.Rectangle') {
+      res = 'rectangle';
+    } else if (type === 'Overlay.Polygon') {
+      res = 'polygon';
+    }
+    return res;
+  };
+  // 绘制图形
+  const drawGraphics = (type: string) => {
+    let graphics;
+    if (type === 'circle') {
+      // 绘制圆形
+      graphics = mouseTool.circle(drawOptions);
+    } else if (type === 'rectangle') {
+      // 绘制矩形
+      graphics = mouseTool.rectangle(drawOptions);
+    } else if (type === 'polygon') {
+      // 绘制多边形
+      graphics = mouseTool.polygon(drawOptions);
+    }
+  };
+  // 设置颜色
+  const setColor = (color: string) => {
+    drawOptions.strokeColor = color;
+    drawOptions.fillColor = color;
+  };
+  // 设置线框面框
+  const setDrawType = (drawType: string) => {
+    drawOptions.fillOpacity = drawType === '1' ? 0 : 0.5;
+  };
+  // 设置图形类型
+  const setGraphicsType = (type: string) => {
+    closeDraw();
+    graphicsType = type;
+    drawGraphics(type);
+  };
+  // 关闭绘制
+  const closeDraw = () => {
+    mouseTool.close();
+  };
+  // 返回鼠标工具对象
+  const getMouseTool = () => {
+    return mouseTool;
+  };
+  // 创建圆形
+  const createCircle = (options) => {
+    var circle = new AMap.Circle({
+      center: options.center,
+      radius: options.radius, //半径
+      strokeColor: options.color,
+      strokeOpacity: 1,
+      strokeWeight: 2,
+      fillColor: options.color,
+      fillOpacity: options.drawType === '1' ? 0 : 0.5,
+      strokeStyle: 'solid'
+    });
+  };
+  return {
+    getMouseTool,
+    initMouseTool,
+    drawGraphics,
+    closeDraw,
+    setColor,
+    setDrawType,
+    setGraphicsType,
+    createCircle
+  };
+}

+ 63 - 0
src/hooks/AMap/useRuler.ts

@@ -0,0 +1,63 @@
+export function useRuler() {
+  let ruler;
+  let isRanging = ref(false);
+  const initRuler = (map, AMap) => {
+    // 自定义测距工具样式
+    const startMarkerOptions = {
+      icon: new AMap.Icon({
+        size: new AMap.Size(19, 31), // 图标大小
+        imageSize: new AMap.Size(19, 31),
+        image: '//webapi.amap.com/theme/v1.3/markers/b/start.png'
+      }),
+      offset: new AMap.Pixel(-9, -31)
+    };
+    const endMarkerOptions = {
+      icon: new AMap.Icon({
+        size: new AMap.Size(19, 31), // 图标大小
+        imageSize: new AMap.Size(19, 31),
+        image: '//webapi.amap.com/theme/v1.3/markers/b/end.png'
+      }),
+      offset: new AMap.Pixel(-9, -31)
+    };
+    const midMarkerOptions = {
+      icon: new AMap.Icon({
+        size: new AMap.Size(19, 31), // 图标大小
+        imageSize: new AMap.Size(19, 31),
+        image: '//webapi.amap.com/theme/v1.3/markers/b/mid.png'
+      }),
+      offset: new AMap.Pixel(-9, -31)
+    };
+    const lineOptions = {
+      strokeStyle: 'solid',
+      strokeColor: '#FF33FF',
+      strokeOpacity: 1,
+      strokeWeight: 2
+    };
+    const rulerOptions = {
+      startMarkerOptions: startMarkerOptions,
+      midMarkerOptions: midMarkerOptions,
+      endMarkerOptions: endMarkerOptions,
+      lineOptions: lineOptions
+    };
+
+    // 初始化自定义样式测距工具
+    ruler = new AMap.RangingTool(map, rulerOptions);
+  };
+
+  // 切换测距工具状态
+  const toggleRangingTool = () => {
+    if (ruler) {
+      if (isRanging.value) {
+        ruler.turnOff(); // 关闭测距工具
+      } else {
+        ruler.turnOn(); // 启动测距工具
+      }
+      isRanging.value = !isRanging.value; // 切换状态
+    }
+  };
+  return {
+    initRuler,
+    isRanging,
+    toggleRangingTool
+  };
+}

+ 58 - 0
src/hooks/useHistory.ts

@@ -0,0 +1,58 @@
+export function useHistory() {
+  // 撤销栈
+  const historyStack = ref([]);
+  // 重做栈
+  const redoStack = ref([]);
+  // 当前指针位置,初始化为-1表示没有选中任何历史记录
+  const currentIndex = ref(-1);
+
+  const pushHistory = (item) => {
+    // 清空重做栈,因为新的操作会覆盖之前的重做历史
+    redoStack.value = [];
+
+    // 将新项添加到撤销栈的当前位置之后
+    historyStack.value.splice(currentIndex.value + 1, historyStack.value.length - currentIndex.value - 1, item);
+
+    // 更新当前索引
+    currentIndex.value++;
+  };
+
+  const undo = () => {
+    if (currentIndex.value >= 0) {
+      // 将当前项移动到重做栈
+      redoStack.value.unshift(historyStack.value[currentIndex.value]);
+
+      // 从撤销栈中移除当前项
+      historyStack.value.splice(currentIndex.value, 1);
+
+      // 索引减一
+      currentIndex.value--;
+    }
+  };
+
+  const redo = () => {
+    if (redoStack.value.length > 0) {
+      // 从重做栈中取出第一个项并添加到撤销栈的当前位置之后
+      historyStack.value.splice(currentIndex.value + 1, 0, redoStack.value.shift());
+
+      // 索引加一
+      currentIndex.value++;
+    }
+  };
+
+  const clearHistory = () => {
+    historyStack.value = [];
+    redoStack.value = [];
+    currentIndex.value = -1;
+  };
+
+  return {
+    historyStack, // 通常不直接暴露给模板,除非有特定需求
+    redoStack, // 通常不暴露
+    currentIndex, // 可选,如果你需要在模板中显示当前状态
+    pushHistory,
+    undo,
+    redo,
+    clearHistory
+  };
+}

+ 0 - 0
src/hooks/useSeamlessScroll.ts


+ 0 - 248
src/hooks/useYztMap.ts

@@ -1,248 +0,0 @@
-import { Ref } from 'vue';
-
-interface Options {
-  zoom?: number;
-  maxZoom?: number;
-  minZoom?: number;
-  center?: number[];
-}
-interface Return {
-  map: Ref<string>;
-  initMap: () => any;
-  initMapData: (value: string, params: any) => any;
-}
-export default (ops?: Options): Return => {
-  let map;
-  let wmtsManager;
-  // 初始化地图
-  const initMap = () => {
-    const simple = {
-      version: 8,
-      sources: {},
-      layers: []
-    };
-    const wgs84_wgs84_mapcrs = {
-      topTileExtent: [-180, -270, 180, 90],
-      coordtransform: 'none',
-      tileSize: 256
-    };
-    map = new GeoGlobe.Map({
-      mapCRS: wgs84_wgs84_mapcrs,
-      style: simple,
-      maxZoom: ops.maxZoom ? ops.maxZoom : 20,
-      zoom: ops.zoom ? ops.zoom : 10,
-      minZoom: ops.minZoom ? ops.minZoom : 7,
-      center: ops.center ? ops.center : [110.999229, 21.919751],
-      container: 'yztMap'
-    });
-    // style加载完成
-    map.on('style.load', function (e) {
-      const token = '3e25b8af23f8d5194175220fe67fe941';
-      const layer_vtc = new GeoGlobe.TDTLayer('vec_c', token);
-      const layer_cva = new GeoGlobe.TDTLayer('cva_c', token);
-      layer_vtc.metadata = { group: 'basemap' };
-      layer_cva.metadata = { group: 'basemap' };
-      map.addLayer(layer_vtc);
-      map.addLayer(layer_cva);
-    });
-    if (!wmtsManager) {
-      wmtsManager = new WMTSManager(map);
-      wmtsManager.init();
-    }
-    return map;
-  };
-
-  // 加载地图数据
-  const initMapData = (value, params) => {
-    const format = new GeoGlobe.Format.WMTSCapabilities();
-    //为了解决BoundingBox节点或者WGS84BoundingBox中存在“NaN NaN”的情况,导致format.read解析时报错,出现这个问题时,将坐标改为广东省范围
-    if (value.indexOf('<ows:LowerCorner>NaN NaN</ows:LowerCorner>') >= 0 || value.indexOf('<ows:UpperCorner>NaN NaN</ows:UpperCorner>') >= 0) {
-      value = value.replaceAll('<ows:LowerCorner>NaN NaN</ows:LowerCorner>', '<ows:LowerCorner>109.61 20</ows:LowerCorner>');
-      value = value.replaceAll('<ows:UpperCorner>NaN NaN</ows:UpperCorner>', '<ows:UpperCorner>117.39 25.51</ows:UpperCorner>');
-    } else if (value.indexOf('<ows:LowerCorner> </ows:LowerCorner>') >= 0 || value.indexOf('<ows:UpperCorner> </ows:UpperCorner>') >= 0) {
-      value = value.replaceAll('<ows:LowerCorner> </ows:LowerCorner>', '<ows:LowerCorner>109.61 20</ows:LowerCorner>');
-      value = value.replaceAll('<ows:UpperCorner> </ows:UpperCorner>', '<ows:UpperCorner>117.39 25.51</ows:UpperCorner>');
-    }
-    const json = format.read(value);
-    const layerOptions = WMTSMatchAnalyzer(json, params.layerExtent, params.wmtsLoadParam);
-    const layerInf = createLayer(params.serviceUrl, layerOptions[0], isSpecialLayer);
-    map.addSource(layerInf.source.id, layerInf.source);
-    wmtslayergroup.addLayerToGroup(that.map, that.wmtsgroupid, layerInf.layer);
-    let maxt = '';
-    let extent = '';
-    if (layerInf.layer.metadata[0]) {
-      extent = layerInf.layer.metadata[0].maxExtent.split(',');
-      maxt = GeoGlobe.LngLatBounds.fromString(layer.metadata[0].maxExtent);
-    } else if (layerInf.layer.metadata.maxExtent) {
-      extent = layerInf.layer.metadata.maxExtent.split(',');
-      maxt = GeoGlobe.LngLatBounds.fromString(layerInf.layer.metadata.maxExtent);
-    } else if (layerInf.layer.metadata.tileFullExtent) {
-      extent = layerInf.layer.metadata.tileFullExtent.split(',');
-      maxt = GeoGlobe.LngLatBounds.fromString(layerInf.layer.metadata.tileFullExtent);
-    }
-    if (val.layerExtent) {
-      that.map.fitBounds([
-        [val.layerExtent.left, val.layerExtent.bottom],
-        [val.layerExtent.right, val.layerExtent.top]
-      ]);
-    } else {
-      if (that.map.getZoom() < layerInf.layer.minzoom || that.map.getZoom() > layerInf.layer.maxzoom) {
-        that.map.setZoom(layerInf.layer.minzoom);
-      }
-      if (maxt) {
-        //如果当前坐标单位为经纬度,判断范围中的坐标是否在有效范围内
-        if (that.map.units === 'degrees') {
-          if (
-            that.validLonCoord(maxt.getEast()) &&
-            that.validLonCoord(maxt.getWest()) &&
-            that.validLatCoord(maxt.getSouth()) &&
-            that.validLatCoord(maxt.getNorth())
-          ) {
-            var centerPoint = maxt.getCenterLonLat();
-            that.map.setCenter(centerPoint);
-          }
-        } else {
-          var centerPoint = maxt.getCenterLonLat();
-          that.map.setCenter(centerPoint);
-        }
-        var centerPoint = maxt.getCenterLonLat();
-        that.map.setCenter(centerPoint);
-      }
-    }
-    that.mapLayerIds.push(layerInf.layer.id);
-    that.currentMapLayerIds.push(layerInf.layer.id);
-    that.index++;
-    that.addWmtsLayers(wmtsInfo, callback);
-  };
-
-  const WMTSMatchAnalyzer = (json, layerExtent, wmtsLoadParam) => {
-    const layers = json.contents.layers;
-    const tileMatrixSets = json.contents.tileMatrixSets;
-    const layerArr = [];
-    for (const data of layers) {
-      const layerObj = {};
-      layerObj['name'] = data.identifier;
-      layerObj['type'] = 'WMTS';
-      layerObj['alias'] = data.identifier;
-      layerObj['opacity'] = 1;
-      layerObj['visibility'] = true;
-      layerObj['formats'] = data.formats;
-      if (wmtsLoadParam && wmtsLoadParam.format && wmtsLoadParam.layer == data.identifier) {
-        layerObj['format'] = wmtsLoadParam.format;
-      } else {
-        layerObj['format'] = data.formats[0];
-      }
-
-      if (wmtsLoadParam && wmtsLoadParam.style && wmtsLoadParam.layer == data.identifier) {
-        layerObj['style'] = wmtsLoadParam.style;
-      } else {
-        layerObj['style'] = data.styles[0].identifier;
-      }
-
-      if (layerExtent) {
-        layerObj['tileFullExtent'] = layerExtent.left + ',' + layerExtent.bottom + ',' + layerExtent.right + ',' + layerExtent.top;
-      } else if (data.bounds) {
-        layerObj['tileFullExtent'] =
-          data.bounds.getWest() + ',' + data.bounds.getSouth() + ',' + data.bounds.getEast() + ',' + data.bounds.getNorth();
-      }
-
-      if (wmtsLoadParam && wmtsLoadParam.tileMatrixset && wmtsLoadParam.layer == data.identifier) {
-        layerObj['matrixSet'] = wmtsLoadParam.tileMatrixset;
-      } else {
-        layerObj['matrixSet'] = data.tileMatrixSetLinks[0].tileMatrixSet;
-      }
-
-      const matrixIds = [];
-      for (const d of tileMatrixSets[layerObj.matrixSet].matrixIds) {
-        const matrixid = {};
-        matrixid.identifier = d.identifier;
-        matrixid.scaleDenominator = d.scaleDenominator;
-        matrixid.tileHeight = d.tileHeight;
-        matrixid.tileWidth = d.tileWidth;
-        matrixIds.push(matrixid);
-      }
-      layerObj['matrixIds'] = matrixIds;
-      layerArr.push(layerObj);
-    }
-    return layerArr;
-  };
-
-  const createLayer = (url, options, isSpecialLayer) => {
-    const layerInf = {};
-    if (url.indexOf('?') > -1) {
-      url =
-        url +
-        '&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
-        options.name +
-        '&STYLE=' +
-        options.style +
-        '&TILEMATRIXSET=' +
-        options.matrixSet +
-        '&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=' +
-        options.formats[0];
-    } else {
-      url =
-        url +
-        '?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
-        options.name +
-        '&STYLE=' +
-        options.style +
-        '&TILEMATRIXSET=' +
-        options.matrixSet +
-        '&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=' +
-        options.formats[0];
-    }
-    // let index = url.lastIndexOf("?")
-    // var resolves = url.substring(index + 1, url.length);
-    // if(resolves.indexOf('token')>-1||resolves.indexOf('TOKEN')>-1){
-
-    // }
-    const matrixIds = options.matrixIds;
-    const minzoom = matrixIds[0].identifier;
-    const maxzoom = matrixIds[matrixIds.length - 1].identifier;
-    const wmtsSource = new GeoGlobe.Source.RasterSource({
-      id:
-        'sourceId_' +
-        options.name +
-        (isSpecialLayer ? '_isSpecialLayer' : '') +
-        '_' +
-        // Date.parse(new Date()),
-        this.guid(),
-      type: 'raster',
-      url: [url],
-      minzoom: parseInt(minzoom),
-      maxzoom: parseInt(maxzoom) + 1,
-      tileSize: 256
-    });
-    wmtsSource['minzoom'] = parseInt(minzoom);
-    wmtsSource['maxzoom'] = parseInt(maxzoom) + 1;
-    let visibility = options.visibility;
-    if (options.cityName && parseInt(minzoom) > 17) {
-      visibility = 'false';
-    }
-    const wmtsLayer = new GeoGlobe.Layer.RasterLayer({
-      id: 'layerId_' + options.name + '_' + this.guid(),
-      type: 'raster',
-      source: wmtsSource.id,
-      name: options.name,
-      metadata: options,
-      minzoom: parseInt(minzoom),
-      maxzoom: parseInt(maxzoom) + 2,
-      paint: {
-        'raster-opacity': 1
-      },
-      layout: {
-        visibility: visibility == true || visibility == 'true' ? 'visible' : 'none'
-      }
-    });
-    layerInf['layer'] = wmtsLayer;
-    layerInf['source'] = wmtsSource;
-    return layerInf;
-  };
-
-  return {
-    map,
-    initMap,
-    initMapData
-  };
-};

+ 51 - 23
src/views/globalMap/drawTools.vue

@@ -4,9 +4,14 @@
       <div class="item-label">画笔选择</div>
       <i class="color-box" :style="{ backgroundColor: colorList[drawerColorIndex]?.value }" />
       <!--弹窗选择器-->
-      <div ref="selectBoxRef" v-show="showColorSelect" class="select-box" style="top: -169px; padding-bottom: 20px">
+      <div ref="selectBoxRef" v-show="showColorSelect" class="select-box" style="top: -149px; padding-bottom: 20px">
         <div class="box-content">
-          <div v-for="(item, index) in colorList" :key="index" class="select-item" @click="selectColor(index)">
+          <div
+            v-for="(item, index) in colorList"
+            :key="index"
+            :class="drawType === item.value ? 'select-item active' : 'select-item'"
+            @click="selectColor(index)"
+          >
             <i class="color-box" :style="{ backgroundColor: item.value }" />
             <span>{{ item.label }}</span>
           </div>
@@ -16,25 +21,37 @@
     <div class="draw-item" @mouseover="showDrawTypeSelect = true" @mouseleave="showDrawTypeSelect = false">
       <div class="item-label">框面类型</div>
       <!--弹窗选择器-->
-      <div ref="selectBoxRef2" v-show="showDrawTypeSelect" class="select-box" style="top: -92px; padding-bottom: 20px">
+      <div ref="selectBoxRef2" v-show="showDrawTypeSelect" class="select-box" style="top: -82px; padding-bottom: 20px">
         <div class="box-content">
-          <div v-for="(item, index) in drawTypeList" :key="index" class="select-item" @click="selectDrawType(index)">
+          <div
+            v-for="(item, index) in drawTypeList"
+            :key="index"
+            :class="drawType === item.value ? 'select-item active' : 'select-item'"
+            @click="selectDrawType(index)"
+          >
             <span>{{ item.label }}</span>
           </div>
         </div>
       </div>
     </div>
-    <div class="draw-item" @mouseover="showFigureTypeSelect = true" @mouseleave="showFigureTypeSelect = false">
+    <div class="draw-item" @mouseover="showGraphicsTypeSelect = true" @mouseleave="showGraphicsTypeSelect = false">
       <div class="item-label">图像类型</div>
       <!--弹窗选择器-->
-      <div ref="selectBoxRef2" v-show="showFigureTypeSelect" class="select-box" style="top: -130.5px; padding-bottom: 20px">
+      <div ref="selectBoxRef2" v-show="showGraphicsTypeSelect" class="select-box" style="top: -115.5px; padding-bottom: 20px">
         <div class="box-content">
-          <div v-for="(item, index) in figureTypeList" :key="index" class="select-item" @click="selectFigureType(index)">
+          <div
+            v-for="(item, index) in graphicsTypeList"
+            :key="index"
+            :class="graphicsType === item.value ? 'select-item active' : 'select-item'"
+            @click="selectGraphicsType(index)"
+          >
             <span>{{ item.label }}</span>
           </div>
         </div>
       </div>
     </div>
+    <div class="draw-item" @click="openDraw">{{ drawing ? '关闭绘制' : '开启绘制' }}</div>
+    <div class="draw-item" @click="undo">撤销</div>
   </div>
 </template>
 
@@ -44,16 +61,17 @@ interface ListItem {
   value: string;
 }
 interface Props {
+  drawing: boolean;
   color: string;
   drawType: string;
-  figureType: string;
+  graphicsType: string;
 }
 const props = withDefaults(defineProps<Props>(), {});
-
-const emits = defineEmits(['update:drawType', 'update:color', 'update:figureType', 'changDrawType']);
+const emits = defineEmits(['update:drawType', 'update:color', 'update:graphicsType', 'update:drawing', 'undo']);
 const selectBoxRef = ref(null);
 const selectBoxRef2 = ref(null);
 const selectBoxRef3 = ref(null);
+
 // 显示画笔选择弹窗
 let showColorSelect = ref(false);
 // 画笔颜色索引
@@ -89,7 +107,6 @@ let showDrawTypeSelect = ref(false);
 const selectDrawType = (index) => {
   emits('update:drawType', drawTypeList[index].value);
   showDrawTypeSelect.value = false;
-  emits('changDrawType', drawTypeList[index].value);
 };
 // 点击绘制类型选择之外
 onClickOutside(selectBoxRef2, () => {
@@ -98,25 +115,34 @@ onClickOutside(selectBoxRef2, () => {
 });
 
 // 绘制图形列表
-const figureTypeList: ListItem[] = reactive([
-  { label: '圆形', value: '1' },
-  { label: '矩形', value: '2' },
-  // { label: '套圈', value: '3' },
-  { label: '多边形', value: '3' }
+const graphicsTypeList: ListItem[] = reactive([
+  { label: '圆形', value: 'circle' },
+  { label: '矩形', value: 'rectangle' },
+  { label: '多边形', value: 'polygon' }
 ]);
 // 显隐图像类型弹窗
-let showFigureTypeSelect = ref(false);
+let showGraphicsTypeSelect = ref(false);
 
 // 选择
-const selectFigureType = (index) => {
-  emits('update:figureType', figureTypeList[index].value);
-  showFigureTypeSelect.value = false;
+const selectGraphicsType = (index) => {
+  emits('update:graphicsType', graphicsTypeList[index].value);
+  showGraphicsTypeSelect.value = false;
 };
 // 点击绘制类型选择之外
 onClickOutside(selectBoxRef3, () => {
-  if (!showFigureTypeSelect.value) return;
-  showFigureTypeSelect.value = false;
+  if (!showGraphicsTypeSelect.value) return;
+  showGraphicsTypeSelect.value = false;
 });
+
+// 开启结束绘制
+const openDraw = () => {
+  emits('update:drawing', !props.drawing);
+};
+
+// 撤销绘制
+const undo = () => {
+  emits('undo', !props.drawing);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -151,7 +177,6 @@ onClickOutside(selectBoxRef3, () => {
       .select-item {
         display: flex;
         align-items: center;
-        margin-top: 5px;
         cursor: pointer;
         padding: 5px 10px;
         &:hover {
@@ -174,4 +199,7 @@ onClickOutside(selectBoxRef3, () => {
     border-radius: 3px;
   }
 }
+.active {
+  background-color: rgba(22, 73, 142, 0.5);
+}
 </style>

+ 29 - 14
src/views/globalMap/index.vue

@@ -1,9 +1,25 @@
 <template>
   <div class="global-map">
     <MapLogical v-if="activeMap === 'logical'" :mapData="mapData" />
-<!--    <YztMap v-else-if="activeMap === 'satellite2'" ref="map2Ref" :activeMap="activeMap" />-->
-    <YMap v-else-if="activeMap === 'satellite2'" ref="map2Ref" :activeMap="activeMap" />
-    <Map v-else ref="mapRef" v-model:drawing="mouseToolState.drawing" :color="mouseToolState.color" :drawType="mouseToolState.drawType" :activeMap="activeMap" />
+    <!--<YztMap v-else-if="activeMap === 'satellite2'" ref="map2Ref" :activeMap="activeMap" />-->
+    <YMap
+      v-else-if="activeMap === 'satellite2'"
+      ref="map2Ref"
+      v-model:drawing="mouseToolState.drawing"
+      :color="mouseToolState.color"
+      :drawType="mouseToolState.drawType"
+      :graphicsType="mouseToolState.graphicsType"
+      :activeMap="activeMap"
+    />
+    <Map
+      v-else
+      ref="mapRef"
+      v-model:drawing="mouseToolState.drawing"
+      :color="mouseToolState.color"
+      :drawType="mouseToolState.drawType"
+      :graphicsType="mouseToolState.graphicsType"
+      :activeMap="activeMap"
+    />
     <!--左侧菜单-->
     <LeftMenu @addMarkers="addMarkers" @click-menu="clickMenu" style="position: absolute; top: 20px; left: 20px" />
     <!--更换地图类型-->
@@ -13,12 +29,11 @@
     <DrawTools
       v-if="showDrawTools"
       class="absoluteTool"
+      v-model:drawing="mouseToolState.drawing"
       v-model:color="mouseToolState.color"
       v-model:drawType="mouseToolState.drawType"
-      v-model:figureType="mouseToolState.figureType"
-      :colorList="mouseToolState.colorList"
-      :drawTypeList="mouseToolState.drawTypeList"
-      @changDrawType="changDrawType"
+      v-model:graphicsType="mouseToolState.graphicsType"
+      @undo="undo"
     />
   </div>
 </template>
@@ -38,7 +53,7 @@ const mapData = reactive(logicalData);
 let mapRef = ref(null);
 let map2Ref = ref(null);
 // logical vectorgraph satellite
-let activeMap = ref('logical');
+let activeMap = ref('vectorgraph');
 
 const swtichMap = (key) => {
   activeMap.value = key;
@@ -75,13 +90,13 @@ const mouseToolState = reactive({
   color: '#f80102',
   // 1线框 2区域
   drawType: '1',
-  // 图形形状  1圆形 2矩形 3多边形
-  figureType: '1'
+  // 图形形状  circle圆形 rectangle矩形 polygon多边形
+  graphicsType: 'circle'
 });
-// 绘制方式变化
-const changDrawType = () => {
-  if (mouseToolState.drawing) return;
-  mouseToolState.drawing = true;
+
+const undo = () => {
+  const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;
+  dom.handleUndo();
 };
 </script>