فهرست منبع

实时标绘绘制功能

Hwf 3 ماه پیش
والد
کامیت
ee09ec5ca1
4فایلهای تغییر یافته به همراه302 افزوده شده و 94 حذف شده
  1. 0 8
      src/types/components.d.ts
  2. 123 15
      src/utils/olMap/olMap.ts
  3. 178 70
      src/views/globalMap/RightMenu/OnlinePlotting/index.vue
  4. 1 1
      src/views/globalMap/RightMenu/index.vue

+ 0 - 8
src/types/components.d.ts

@@ -44,29 +44,23 @@ declare module 'vue' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
     ElPopover: typeof import('element-plus/es')['ElPopover']
-    ElRadio: typeof import('element-plus/es')['ElRadio']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSlider: typeof import('element-plus/es')['ElSlider']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
-    ElTable: typeof import('element-plus/es')['ElTable']
-    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElText: typeof import('element-plus/es')['ElText']
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
-    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     ExcelEditor: typeof import('./../components/ExcelEditor/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
@@ -78,8 +72,6 @@ declare module 'vue' {
     HikvisionPlayer: typeof import('./../components/HKVideo/hikvision-player.vue')['default']
     HKVideo: typeof import('./../components/HKVideo/index.vue')['default']
     IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
-    IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
-    IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']

+ 123 - 15
src/utils/olMap/olMap.ts

@@ -33,8 +33,10 @@ import Overlay from 'ol/Overlay';
 import { Draw, Select } from 'ol/interaction';
 import { click } from 'ol/events/condition';
 import Circle from 'ol/geom/Circle';
-import { hexToRgba } from '@/utils';
+import { deepClone, hexToRgba } from '@/utils';
 import { createBox } from 'ol/interaction/Draw';
+import * as turf from '@turf/turf';
+import { nanoid } from 'nanoid';
 
 const tk = 'a8df87f1695d224d2679aa805c1268d9';
 const commonUrl = import.meta.env.VITE_APP_BASE_API2 + 'api/oneShare/proxyHandler/gd/';
@@ -70,7 +72,9 @@ export class olMap {
     strokeWeight: 2,
     fillColor: '#f80102',
     fillOpacity: 0,
-    strokeStyle: 'solid'
+    strokeStyle: 'solid',
+    icon: '',
+    iconName: ''
   };
   private plot;
   private drawVector;
@@ -302,20 +306,98 @@ export class olMap {
   }
 
   // 绘制图形
-  drawGraphics(type: string) {
-    if (type === 'circle') {
-      // 绘制圆形
-      // activate('Circle');
-    } else if (type === 'rectangle') {
-      // 绘制矩形
-      this.plot.plotDraw.activate('RectAngle');
-    } else if (type === 'polygon') {
-      // 绘制多边形
-      this.plot.plotDraw.activate('Polygon');
-    } else if (type === 'freePolygon') {
-      // 绘制索套
-      this.plot.plotDraw.activate('FreePolygon');
+  drawGraphics(newOptions: MouseTool) {
+    const typeList = {
+      circle: 'Circle',
+      rectangle: 'Circle',
+      polygon: 'Polygon',
+      measureArea: 'Polygon',
+      straightLine: 'LineString',
+      marker: 'Point',
+      text: 'Point'
+    };
+    let geometryFunction = null;
+    if (newOptions.graphicsType === 'rectangle') {
+      // 绘制矩形的方法
+      geometryFunction = createBox();
+    }
+    if (!!typeList[newOptions.graphicsType]) {
+      if (newOptions.graphicsType === 'text') {
+        this.drawOptions = {
+          type: newOptions.graphicsType,
+          title: newOptions.title,
+          text: newOptions.text,
+          fontSize: newOptions.fontSize,
+          fontColor: newOptions.fontColor,
+          lnglat: newOptions.lnglat
+        };
+        // 绘制文字
+        return this.addText(this.drawOptions);
+      } else {
+        this.drawOptions = {
+          type: newOptions.graphicsType,
+          strokeColor: newOptions.color,
+          strokeOpacity: 1,
+          strokeWeight: 1,
+          fillColor: newOptions.color,
+          fillOpacity: newOptions.drawType === '1' ? '0' : '0.5',
+          strokeStyle: 'solid'
+        };
+        if (newOptions.graphicsType === 'marker') {
+          this.drawOptions.icon = newOptions.icon;
+          this.drawOptions.iconName = newOptions.iconName;
+          this.drawOptions.size = newOptions.size;
+        }
+        this.closeDraw();
+        // 创建绘制交互
+        let style = new Style({
+          stroke: new Stroke({
+            color: hexToRgba(this.drawOptions.strokeColor, this.drawOptions.strokeOpacity),
+            width: this.drawOptions.strokeWeight
+          }),
+          fill: new Fill({
+            color: hexToRgba(this.drawOptions.fillColor, this.drawOptions.fillOpacity)
+          })
+        });
+        this.drawTool = new Draw({
+          source: this.drawVector.getSource(),
+          type: typeList[newOptions.graphicsType],
+          geometryFunction: geometryFunction,
+          style: style
+        });
+        // 添加绘制交互到地图
+        this.map.addInteraction(this.drawTool);
+        // 监听绘制结束事件
+        this.drawTool.on('drawend', (event) => {
+          const feature = event.feature;
+          // 获取几何对象
+          if (newOptions.graphicsType !== 'marker') {
+            if (newOptions.graphicsType === 'measureArea') {
+              const geometry = feature.getGeometry();
+              const coordinates = geometry.getCoordinates();
+              const pathArr = coordinates[0];
+              const area = turf.area(turf.polygon([pathArr]));
+              style = new Style({
+                stroke: style.getStroke(),
+                fill: style.getFill(),
+                text: new Text({
+                  text: '区域面积' + area.toFixed(2) + '平方米',
+                  font: '14px Calibri,sans-serif',
+                  fill: new Fill({ color: '#000' }),
+                  stroke: new Stroke({
+                    color: '#fff',
+                    width: 3
+                  }),
+                  overflow: true
+                })
+              });
+            }
+            feature.setStyle(style);
+          }
+        });
+      }
     }
+    return this.drawOptions;
   }
   // 空间分析绘制图形
   drawGraphics2(newOptions: MouseTool) {
@@ -366,6 +448,32 @@ export class olMap {
       });
     }
   }
+  addText(options) {
+    // 创建文本覆盖物
+    const style = new Style({
+      text: new Text({
+        text: options.text,
+        font: options.fontSize + ' Calibri,sans-serif',
+        fill: new Fill({ color: options.fontColor }),
+        // stroke: new Stroke({
+        //   color: '#fff',
+        //   width: 3
+        // }),
+        overflow: true
+      })
+    });
+    const text = new Feature({
+      geometry: new Point(options.lnglat)
+    });
+    text.setStyle(style);
+    // 将文本覆盖物添加到地图
+    this.drawVector.getSource().addFeature(text);
+    const id = nanoid();
+    text.set('id', id);
+    const data: any = deepClone(options);
+    data.id = id;
+    return { text, data };
+  }
   // 关闭绘制
   closeDraw() {
     if (!this.drawTool) return;

+ 178 - 70
src/views/globalMap/RightMenu/OnlinePlotting/index.vue

@@ -13,7 +13,7 @@
       </div>
       <div class="btn-box">
         <div class="btn1" @click="handleScreenshot">
-          <div class="icon1"></div>
+          <div class="icon1" />
           当前地图截图导出
         </div>
         <div v-show="!collaboration" class="btn2" @click="handleShare('1')">协同标绘</div>
@@ -207,6 +207,16 @@ import { Search } from '@element-plus/icons-vue';
 import html2canvas from 'html2canvas';
 import LayerDetail from './LayerDetail.vue';
 import { createWebSocket } from '@/utils/websocket';
+import * as turf from '@turf/turf';
+import Style from 'ol/style/Style';
+import Icon from 'ol/style/Icon';
+import { Fill, Stroke } from 'ol/style';
+import Text from 'ol/style/Text';
+
+const props = defineProps({
+  activeMap: String
+});
+const AMapType = ['vectorgraph', 'satellite'];
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const getDrawTool = inject('getDrawTool');
@@ -242,7 +252,7 @@ const lineWidthOptions = reactive([
   { name: '2像素', value: '2' },
   { name: '3像素', value: '3' }
 ]);
-let showTextEdit = ref();
+let showTextEdit = ref(false);
 let lnglat = ref([]);
 // 协同
 let collaboration = ref(false);
@@ -361,10 +371,10 @@ const clickTab3 = (item, index) => {
       mouseToolState.value.title = item.name;
     }
     const drawTool = getDrawTool();
-    const newOptions = drawTool.drawGraphics(mouseToolState.value);
-    if (mouseToolState.value.graphicsType !== 'anyLine') {
+    if (mouseToolState.value.graphicsType === 'anyLine') {
       drawTool.setDrawEndMethod(handleEndDraw);
     } else if (mouseToolState.value.graphicsType !== 'text') {
+      const newOptions = drawTool.drawGraphics(mouseToolState.value);
       // 绘制完成事件
       initDrawMethod(newOptions);
     }
@@ -393,7 +403,11 @@ const handleTextEdit = () => {
 };
 const handleClickMap = (e) => {
   // 获取点击位置的经纬度
-  lnglat.value = [e.lnglat.lng, e.lnglat.lat];
+  if (AMapType.includes(props.activeMap)) {
+    lnglat.value = [e.lnglat.lng, e.lnglat.lat];
+  } else {
+    lnglat.value = e.coordinate;
+  }
   showTextEdit.value = true;
 };
 const addText = (textEditState) => {
@@ -420,7 +434,11 @@ const addText = (textEditState) => {
   res.text.on('rightclick', handleRightClick);
   const map = drawTool.getMap();
   // 监听地图点击事件
-  map.off('click', handleClickMap);
+  if (AMapType.includes(props.activeMap)) {
+    map.off('click', handleClickMap);
+  } else {
+    map.un('click', handleClickMap);
+  }
   sendWebSocket(data);
   close();
 };
@@ -436,7 +454,11 @@ watch(
   () => {
     if (mouseToolState.value.graphicsType !== 'text') {
       const map = getMap();
-      map.off('click', handleClickMap);
+      if (AMapType.includes(props.activeMap)) {
+        map.off('click', handleClickMap);
+      } else {
+        map.un('click', handleClickMap);
+      }
     }
   },
   {
@@ -447,72 +469,158 @@ watch(
 const initDrawMethod = (options) => {
   const drawTool = getDrawTool();
   const mouseTool = drawTool.getMouseTool();
-  const onDraw = (event) => {
-    mouseTool.off('draw', onDraw);
-    close();
-    const obj = event.obj;
-    const id = nanoid();
-    obj._opts.extData = {
-      id: id
+  let onDraw;
+  if (AMapType.includes(props.activeMap)) {
+    onDraw = (event) => {
+      mouseTool.off('draw', onDraw);
+      close();
+      const obj = event.obj;
+      const id = nanoid();
+      obj._opts.extData = {
+        id: id
+      };
+      const data: any = deepClone(options);
+      data.id = id;
+      if (options.type == 'marker') {
+        const position = obj.getPosition();
+        data.lnglat = [position.lng, position.lat];
+        data.latitude = position.lat;
+        data.longitude = position.lng;
+        data.icon = data.iconName;
+        data.image = data.iconName;
+        data.imageHover = data.iconName;
+        data.visible = true;
+        obj.setLabel({
+          content: '<div>' + data.title + '</div>',
+          direction: 'top'
+        });
+      } else {
+        if (options.type == 'circle') {
+          data.center = [obj.getCenter().lng, obj.getCenter().lat];
+          data.radius = obj.getRadius();
+        } else {
+          const path = obj.getPath();
+          // 将AMap.LngLat对象数组转换为经纬度数组
+          const pathArr = path.map((lngLat) => {
+            // 返回经度和纬度的数组
+            return [lngLat.lng, lngLat.lat];
+          });
+          if (options.type !== 'straightLine') {
+            pathArr.push(pathArr[0]);
+          }
+          data.path = pathArr;
+        }
+      }
+      if (options.type == 'measureArea') {
+        const AMap = getDrawTool().getAMap();
+        const map = getMap();
+        // 计算区域面积
+        const area = Math.round(AMap.GeometryUtil.ringArea(data.path));
+        const text = new AMap.Text({
+          position: data.path[data.path.length - 1],
+          text: '区域面积' + area + '平方米',
+          offset: new AMap.Pixel(-20, -20)
+        });
+        data.area = area;
+        map.add(text);
+        overlays.push([obj, text]);
+        overlaysData.push(data);
+        commit(deepClone(overlaysData));
+      } else {
+        overlays.push(obj);
+        overlaysData.push(data);
+        commit(deepClone(overlaysData));
+        console.log(overlaysData);
+      }
+      // 右击进入编辑
+      obj.on('rightclick', handleRightClick);
+      // 发送
+      sendWebSocket(data);
     };
-    const data: any = deepClone(options);
-    data.id = id;
-    if (options.type == 'marker') {
-      const position = obj.getPosition();
-      data.lnglat = [position.lng, position.lat];
-      data.latitude = position.lat;
-      data.longitude = position.lng;
-      data.icon = data.iconName;
-      data.image = data.iconName;
-      data.imageHover = data.iconName;
-      data.visible = true;
-      obj.setLabel({
-        content: '<div>' + data.title + '</div>',
-        direction: 'top'
-      });
-    } else {
-      const path = obj.getPath();
-      // 将AMap.LngLat对象数组转换为经纬度数组
-      const pathArr = path.map((lngLat) => {
-        // 返回经度和纬度的数组
-        return [lngLat.lng, lngLat.lat];
-      });
-      if (options.type !== 'straightLine') {
-        pathArr.push(pathArr[0]);
+    mouseTool.on('draw', onDraw);
+  } else {
+    onDraw = (event) => {
+      mouseTool.un('draw', onDraw);
+      close();
+      const feature = event.feature;
+      const geometry = feature.getGeometry();
+      const id = nanoid();
+      feature.set('id', id);
+      const data: any = deepClone(options);
+      data.id = id;
+      if (options.type == 'marker') {
+        const img = new Image();
+        img.onload = () => {
+          // 图片加载完成后,可以访问其 width 和 height 属性
+          const width = img.width;
+          const height = img.height;
+          const style = new Style({
+            image: new Icon({
+              src: options.icon,
+              anchor: [0.5, 0.5], // 图标锚点,通常是图标的中心点
+              anchorXUnits: 'fraction',
+              anchorYUnits: 'fraction',
+              size: [width, height],
+              scale: !!options.size[0] ? options.size[0] / width : 1
+            }),
+            // text: new Text({
+            //   text: data.title, // 使用属性中的值作为文本
+            //   font: '14px sans-serif',
+            //   fill: new Fill({
+            //     color: '#ffff'
+            //   })
+            // })
+          });
+          feature.setStyle(style);
+          const position = geometry.getCoordinates();
+          data.lnglat = position;
+          data.latitude = position[0];
+          data.longitude = position[1];
+          data.icon = data.iconName;
+          data.image = data.iconName;
+          data.imageHover = data.iconName;
+          data.visible = true;
+        };
+        img.src = options.icon; // 设置图片的 URL,触发加载
+      } else {
+        if (options.type == 'circle') {
+          data.center = [geometry.getCenter().lng, geometry.getCenter().lat];
+          data.radius = geometry.getRadius();
+        } else {
+          const coordinates = geometry.getCoordinates();
+          const pathArr = coordinates[0];
+          data.path = pathArr;
+        }
       }
-      data.path = pathArr;
-      if (options.type == 'circle') {
-        data.center = [obj.getCenter().lng, obj.getCenter().lat];
-        data.radius = obj.getRadius();
+      if (options.type == 'measureArea') {
+        // 计算区域面积
+        const area = turf.area(turf.polygon([data.path]));
+
+
+        // const text = new AMap.Text({
+        //   position: data.path[data.path.length - 1],
+        //   text: '区域面积' + area + '平方米',
+        //   offset: new AMap.Pixel(-20, -20)
+        // });
+        data.area = area;
+        // map.add(text);
+        // overlays.push([feature, text]);
+        overlays.push(feature);
+        overlaysData.push(data);
+        commit(deepClone(overlaysData));
+      } else {
+        overlays.push(feature);
+        overlaysData.push(data);
+        commit(deepClone(overlaysData));
+        console.log(overlaysData);
       }
-    }
-    if (options.type == 'measureArea') {
-      const AMap = getDrawTool().getAMap();
-      const map = getMap();
-      // 计算区域面积
-      const area = Math.round(AMap.GeometryUtil.ringArea(data.path));
-      const text = new AMap.Text({
-        position: data.path[data.path.length - 1],
-        text: '区域面积' + area + '平方米',
-        offset: new AMap.Pixel(-20, -20)
-      });
-      data.area = area;
-      map.add(text);
-      overlays.push([obj, text]);
-      overlaysData.push(data);
-      commit(deepClone(overlaysData));
-    } else {
-      overlays.push(obj);
-      overlaysData.push(data);
-      commit(deepClone(overlaysData));
-      console.log(overlaysData);
-    }
-    // 右击进入编辑
-    obj.on('rightclick', handleRightClick);
-    // 发送
-    sendWebSocket(data);
-  };
-  mouseTool.on('draw', onDraw);
+      // 右击进入编辑
+      feature.on('rightclick', handleRightClick);
+      // 发送
+      sendWebSocket(data);
+    };
+    mouseTool.on('drawend', onDraw);
+  }
 };
 const handleEndDraw = (options, obj) => {
   const drawTool = getDrawTool();

+ 1 - 1
src/views/globalMap/RightMenu/index.vue

@@ -51,7 +51,7 @@
         <!--无人机-->
         <UAV v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '无人机'" @handle-menu="handleMenu" />
         <!--实时标绘-->
-        <OnlinePlotting v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '实时标绘'" />
+        <OnlinePlotting v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '实时标绘'" :activeMap="activeMap" />
         <!--定点分析-->
         <FixedPointAnalysis v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '定点分析'" :location="location2" />
         <!--雨情监测-->