Explorar o código

空间分析面积计算

Hwf hai 1 ano
pai
achega
1e2b0176ef

+ 9 - 0
package-lock.json

@@ -20,6 +20,7 @@
         "autofit.js": "^3.1.1",
         "await-to-js": "3.0.0",
         "axios": "1.6.8",
+        "bignumber.js": "^9.1.2",
         "bpmn-js": "16.4.0",
         "crypto-js": "4.2.0",
         "diagram-js": "12.3.0",
@@ -5663,6 +5664,14 @@
         "node": "*"
       }
     },
+    "node_modules/bignumber.js": {
+      "version": "9.1.2",
+      "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.1.2.tgz",
+      "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
     "autofit.js": "^3.1.1",
     "await-to-js": "3.0.0",
     "axios": "1.6.8",
+    "bignumber.js": "^9.1.2",
     "bpmn-js": "16.4.0",
     "crypto-js": "4.2.0",
     "diagram-js": "12.3.0",

+ 10 - 3
src/components/Map/index.vue

@@ -19,12 +19,12 @@
 </template>
 
 <script setup lang="ts" name="Map">
-import { onMounted, onUnmounted, reactive, watch, defineProps, withDefaults, ref } from 'vue';
 import QuickZoom from './quickZoom.vue';
 import { useAMap } from '@/hooks/AMap/useAMap';
-import { useHistory } from '@/hooks/useHistory';
 import { useDrawTool } from '@/hooks/AMap/useDrawTool';
 import { useRuler } from '@/hooks/AMap/useRuler';
+import { countCircleArea, countRectangleArea } from "@/utils/geometryUtil";
+import { deepClone } from "@/utils";
 
 interface Props {
   activeMap: string;
@@ -35,7 +35,7 @@ interface Props {
 }
 
 const props = withDefaults(defineProps<Props>(), {});
-const emits = defineEmits(['update:drawing']);
+const emits = defineEmits(['update:drawing', 'selectGraphics']);
 
 const mapState = reactive({
   center: [110.93154257997, 21.669064031332],
@@ -70,6 +70,7 @@ const { getAMap, getMap, switchMap, addMarker, clearMarker, getMarkers } = useAM
   scrollWheel: true,
   showScale: true,
   enableMouseTool: true,
+  // 加载完成事件
   onLoadCompleted: () => {
     AMap = getAMap();
     map = getMap();
@@ -81,6 +82,12 @@ const { getAMap, getMap, switchMap, addMarker, clearMarker, getMarkers } = useAM
     map.on('zoomchange', zoomChangeHandler);
     initMouseTool({ map, AMap });
     initRuler(map, AMap);
+  },
+  // 绘制完成事件
+  onDrawCompleted: (data, overlaysData) => {
+    if (overlaysData.length === 1) {
+      emits('selectGraphics', data);
+    }
   }
 });
 // 监听地图类型变化

+ 109 - 27
src/hooks/AMap/useDrawTool.ts

@@ -1,6 +1,7 @@
 import { nanoid } from 'nanoid';
 import { useHistory } from '@/hooks/useHistory';
-import { deepClone } from "@/utils";
+import { deepClone } from '@/utils';
+import { countCircleArea, countRectangleArea } from '@/utils/geometryUtil';
 
 interface DrawToolOptions {
   color: string;
@@ -57,13 +58,56 @@ export function useDrawTool(options: DrawToolOptions) {
       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);
+        const area = countCircleArea(data.center, data.radius);
+        // 计算区域面积
+        const text = new AMap.Text({
+          position: new AMap.LngLat(data.center[0], data.center[1]),
+          text: '区域面积' + area + '平方米',
+          offset: new AMap.Pixel(-20, -20)
+        });
+        map.add(text);
+      } else if (data.type === 'rectangle') {
+        const bounds = obj.getBounds();
+        // 从bounds中获取西南角和东北角的坐标
+        const southWest = bounds.getSouthWest();
+        const northEast = bounds.getNorthEast();
+        data.southWest = [southWest.lng, southWest.lat];
+        data.northEast = [northEast.lng, northEast.lat];
+        const pathArr = [
+          [
+            [southWest.lng, northEast.lat],
+            [northEast.lng, northEast.lat],
+            [northEast.lng, southWest.lat],
+            [southWest.lng, southWest.lat],
+            [southWest.lng, northEast.lat]
+          ]
+        ];
+        const area = countRectangleArea(pathArr);
+        // 计算区域面积
+        const text = new AMap.Text({
+          position: new AMap.LngLat(southWest.lng, northEast.lat),
+          text: '区域面积' + area + '平方米',
+          offset: new AMap.Pixel(-20, -20)
+        });
+        map.add(text);
+      } else if (data.type === 'polygon') {
+        const path = obj.getPath();
+        // 将AMap.LngLat对象数组转换为经纬度数组
+        const pathArr = path.map((lngLat) => {
+          // 返回经度和纬度的数组
+          return [lngLat.lng, lngLat.lat];
+        });
+        data.path = pathArr;
+        const newPathArr = deepClone(pathArr);
+        newPathArr.push(newPathArr[0]);
+        const area = countRectangleArea([newPathArr]);
       }
       overlays.push(obj);
       overlaysData.push(data);
       commit(deepClone(overlaysData));
+      if (typeof options.onDrawCompleted === 'function') {
+        options.onDrawCompleted(data, overlaysData, event);
+      }
       // 右击进入编辑
       obj.on('rightclick', handleRightClick);
     });
@@ -141,10 +185,10 @@ export function useDrawTool(options: DrawToolOptions) {
       createCircle(data);
     } else if (data.type === 'rectangle') {
       // 绘制矩形
-      mouseTool.rectangle(drawOptions);
+      createRectangle(data);
     } else if (data.type === 'polygon') {
       // 绘制多边形
-      mouseTool.polygon(drawOptions);
+      createPolygon(data);
     }
   };
   // 创建圆形
@@ -164,36 +208,70 @@ export function useDrawTool(options: DrawToolOptions) {
   };
   // 创建矩形
   const createRectangle = (options: any) => {
-
+    const southWest = new AMap.LngLat(options.southWest[0], options.southWest[1]);
+    const northEast = new AMap.LngLat(options.northEast[0], options.northEast[1]);
+    const bounds = new AMap.Bounds(southWest, northEast);
+    const rectangle = new AMap.Rectangle({
+      bounds: bounds,
+      strokeColor: options.color,
+      strokeOpacity: 1,
+      strokeWeight: 2,
+      fillColor: options.color,
+      fillOpacity: options.drawType === '1' ? 0 : 0.5,
+      strokeStyle: 'solid'
+    });
+    overlays.push(rectangle);
+    map.add(rectangle);
+  };
+  // 创建矩形
+  const createPolygon = (options: any) => {
+    // 将数组转换为AMap.LngLat对象的数组
+    const path = options.path.map((coord) => {
+      return new AMap.LngLat(coord[0], coord[1]);
+    });
+    const polygon = new AMap.Polygon({
+      path: path,
+      strokeColor: options.color,
+      strokeOpacity: 1,
+      strokeWeight: 2,
+      fillColor: options.color,
+      fillOpacity: options.drawType === '1' ? 0 : 0.5,
+      strokeStyle: 'solid'
+    });
+    overlays.push(polygon);
+    map.add(polygon);
   };
   // 处理撤销
   const handleUndo = () => {
-    const len = currentState.value.length;
-    undo();
-    if (len > currentState.value.length) {
-      // 撤销新增
-      removeOverlayByIndex(currentState.value.length);
-    } else {
-      let restoreData;
-      for (let i = 0; i < currentState.value.length; i++) {
-        let index = 0;
-        for (let k = 0; k < overlays.length; k++) {
-          if (currentState.value[i].id !== overlays[k].getExtData()?.id) {
-            index++;
-          } else {
+    const previous = history.value[history.value.length - 2];
+    if (history.value.length > 1) {
+      if (currentState.value.length > previous.length) {
+        // 撤销新增
+        removeOverlayByIndex(currentState.value.length - 1);
+      } else {
+        let restoreData;
+        for (let i = 0; i < previous.length; i++) {
+          let index = 0;
+          for (let k = 0; k < currentState.value.length; k++) {
+            if (previous[i].id !== currentState.value[k].id) {
+              index++;
+            } else {
+              break;
+            }
+          }
+          if (index === previous.length - 1) {
+            restoreData = previous[i];
             break;
           }
         }
-        if (index === currentState.value.length - 1) {
-          restoreData = currentState.value[i];
-          break;
+        if (restoreData) {
+          createGraphics(restoreData);
         }
       }
-      if (restoreData) {
-        createGraphics(restoreData);
-      }
+      undo();
+
+      console.log(history.value, future.value, currentState.value);
     }
-    console.log(history.value, future.value, currentState.value);
   };
   // 根据索引移除覆盖物
   const removeOverlayByIndex = (index: number) => {
@@ -202,6 +280,10 @@ export function useDrawTool(options: DrawToolOptions) {
     overlays.splice(index, 1);
     overlaysData.splice(index, 1);
   };
+  // // 图层分析显示信息
+  // const addText = (text) => {
+  //
+  // }
   return {
     overlays,
     getMouseTool,

+ 13 - 0
src/utils/geometryUtil.ts

@@ -0,0 +1,13 @@
+import * as turf from '@turf/turf';
+import BigNumber from 'bignumber.js';
+
+export function countRectangleArea(path, decimal = 4) {
+  const polygon = turf.polygon(path);
+  return new BigNumber(turf.area(polygon)).dp(decimal).toNumber();
+}
+
+export function countCircleArea(center, radius, decimal = 4, options = { steps: 64, units: 'meters' }) {
+  // 创建圆形
+  const circle = turf.circle(center, radius, options);
+  return new BigNumber(turf.area(circle)).dp(decimal).toNumber();
+}

+ 64 - 0
src/views/globalMap/AnalyzeDataDialog.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="analyze-data-container">
+    <div class="item">
+      <div class="item-label">行政镇</div>
+      <div class="item-value">{{ analysisSpatialData.townName }}个</div>
+    </div>
+    <div class="item">
+      <div class="item-label">人口</div>
+      <div class="item-value">{{ analysisSpatialData.populationNum }}个</div>
+    </div>
+    <div class="item">
+      <div class="item-label">面积</div>
+      <div class="item-value">{{ analysisSpatialData.area }}平方米</div>
+    </div>
+    <div class="item">
+      <div class="item-label">最近一年GDP</div>
+      <div class="item-value">{{ analysisSpatialData.gdp }}元</div>
+    </div>
+    <div class="item">
+      <div class="item-label">易涝点</div>
+      <div class="item-value">{{ analysisSpatialData.easyFloodPoint }}个</div>
+    </div>
+    <div class="item">
+      <div class="item-label">医院</div>
+      <div class="item-value">{{ analysisSpatialData.medicalInstitutionNum }}个</div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup name="AnalyzeDataDialog">
+interface AnalysisSpatialData {
+  townName: string;
+  area: string;
+  populationNum: string;
+  gdp: string;
+  easyFloodPoint: string;
+  medicalInstitutionNum: string;
+}
+interface Props {
+  analysisSpatialData: AnalysisSpatialData;
+}
+const props = withDefaults(defineProps<Props>(), {});
+</script>
+
+<style lang="scss" scoped>
+.analyze-data-container {
+  width: 280px;
+  height: 400px;
+  position: absolute;
+  top: 30px;
+  right: 30px;
+  background-color: #041d55;
+  color: #fff;
+  font-size: 16px;
+  padding: 15px;
+  .item {
+    display: flex;
+    justify-content: space-between;
+    .item-label {
+      margin-right: 8px;
+    }
+  }
+}
+</style>

+ 1 - 1
src/views/globalMap/drawTools.vue → src/views/globalMap/DrawTools.vue

@@ -55,7 +55,7 @@
   </div>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup name="DrawTools">
 interface ListItem {
   label: string;
   value: string;

+ 45 - 2
src/views/globalMap/index.vue

@@ -19,6 +19,7 @@
       :drawType="mouseToolState.drawType"
       :graphicsType="mouseToolState.graphicsType"
       :activeMap="activeMap"
+      @selectGraphics="analysisSpatial"
     />
     <!--左侧菜单-->
     <LeftMenu @addMarkers="addMarkers" @click-menu="clickMenu" style="position: absolute; top: 20px; left: 20px" />
@@ -35,6 +36,7 @@
       v-model:graphicsType="mouseToolState.graphicsType"
       @undo="undo"
     />
+    <AnalyzeDataDialog :analysisSpatialData="analysisSpatialData" />
   </div>
 </template>
 
@@ -47,7 +49,10 @@ import { logicalData } from './data/mapData';
 import SwitchMapTool from '@/views/globalMap/SwitchMapTool.vue';
 import LeftMenu from './LeftMenu.vue';
 import TimeAxis from '@/components/TimeAxis/index.vue';
-import DrawTools from '@/views/globalMap/drawTools.vue';
+import DrawTools from '@/views/globalMap/DrawTools.vue';
+import AnalyzeDataDialog from '@/views/globalMap/AnalyzeDataDialog.vue';
+import { countCircleArea, countRectangleArea } from "@/utils/geometryUtil";
+import { deepClone } from "@/utils";
 
 const mapData = reactive(logicalData);
 let mapRef = ref(null);
@@ -93,11 +98,49 @@ const mouseToolState = reactive({
   // 图形形状  circle圆形 rectangle矩形 polygon多边形
   graphicsType: 'circle'
 });
-
+// 点击撤销
 const undo = () => {
   const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;
   dom.handleUndo();
 };
+interface AnalysisSpatialData {
+  townName: string;
+  area: string;
+  populationNum: string;
+  gdp: string;
+  easyFloodPoint: string;
+  medicalInstitutionNum: string;
+}
+// 空间分析数据
+const analysisSpatialData = reactive<AnalysisSpatialData>({
+  townName: '',
+  area: '',
+  populationNum: '',
+  gdp: '',
+  easyFloodPoint: '',
+  medicalInstitutionNum: ''
+});
+// 空间分析数据
+const analysisSpatial = (data) => {
+  if (data.type === 'circle') {
+    analysisSpatialData.area = countCircleArea(data.center, data.radius);
+  } else if (data.type === 'rectangle') {
+    const pathArr = [
+      [
+        [data.southWest[0], data.northEast[1]],
+        data.northEast,
+        [data.northEast[0], data.southWest[1]],
+        data.southWest,
+        [data.southWest[0], data.northEast[1]]
+      ]
+    ];
+    analysisSpatialData.area = countRectangleArea(pathArr);
+  } else if (data.type === 'polygon') {
+    const newPathArr = deepClone(data.path);
+    newPathArr.push(newPathArr[0]);
+    analysisSpatialData.area = countRectangleArea([newPathArr]);
+  }
+};
 </script>
 
 <style lang="scss" scoped>