Browse Source

实时标绘 绘制功能

Hwf 3 months ago
parent
commit
b1dd3c4da1

+ 58 - 0
src/directive/common/dialogDrag.ts

@@ -0,0 +1,58 @@
+/*
+ * @Author: shenmei
+ * @Date: 2022-08-24 13:54:39
+ * @LastEditors: Please set LastEditors
+ * @LastEditTime: 2023-02-20 20:47:34
+ * @Description: 弹框拖拽指令
+ * @FilePath: \ioc_cooperation_2\src\directives\dialogDrag.js
+ */
+import Vue from 'vue';
+Vue.directive('dialogDrag', {
+  bind(el, binding, vnode, oldVnode) {
+    const dialogHeaderEl = el.querySelector('.dialogDrag-target');
+    const dragDom = el;
+    dialogHeaderEl.style.cursor = 'move';
+
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+
+    dialogHeaderEl.onmousedown = (e) => {
+      // 鼠标按下,计算当前元素距离可视区的距离
+      const disX = e.clientX - dialogHeaderEl.offsetLeft;
+      const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+      // 获取到的值带px 正则匹配替换
+      let styL, styT;
+
+      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+      if (sty.left.includes('%')) {
+        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+      } else {
+        styL = +sty.left.replace(/\px/g, '');
+        styT = +sty.top.replace(/\px/g, '');
+      }
+
+      document.onmousemove = function(e) {
+        // 通过事件委托,计算移动的距离
+        const l = e.clientX - disX;
+        const t = e.clientY - disY;
+
+        // 移动当前元素
+        dragDom.style.left = `${l + styL}px`;
+        dragDom.style.top = `${t + styT}px`;
+        // 将此时的位置传出去
+        // binding.value({x:e.pageX,y:e.pageY})
+      };
+
+      document.onmouseup = function(e) {
+        if(binding.expression) {
+          console.log("鼠标拖完触发",binding);
+          vnode.context[binding.expression]();
+        }
+        document.onmousemove = null;
+        document.onmouseup = null;
+      };
+    };
+  }
+});

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

@@ -97,6 +97,7 @@ declare module 'vue' {
     VideoContainer2: typeof import('./../components/HKVideo/video-container2.vue')['default']
     VideoTagEdit: typeof import('./../components/VideoTagEdit/index.vue')['default']
     YMap: typeof import('./../components/Map/YMap.vue')['default']
+    YMapold: typeof import('./../components/Map/YMapold.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/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;

+ 176 - 69
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>
@@ -215,7 +215,16 @@ import html2canvas from 'html2canvas';
 import LayerDetail from './LayerDetail.vue';
 import { createWebSocket } from '@/utils/websocket';
 import { showSuccessMsg } from '@/utils/notification';
+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 getMapUtils = inject('getMapUtils');
 const getDrawTool = inject('getDrawTool');
@@ -250,7 +259,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);
@@ -368,10 +377,10 @@ const clickTab3 = (item, index) => {
       mouseToolState.value.title = item.name;
     }
     const drawTool = getDrawTool();
-    const newOptions = drawTool.drawGraphics(mouseToolState.value);
     if (mouseToolState.value.graphicsType === 'anyLine') {
       drawTool.setDrawEndMethod(handleEndDraw);
     } else if (mouseToolState.value.graphicsType !== 'text') {
+      const newOptions = drawTool.drawGraphics(mouseToolState.value);
       // 绘制完成事件
       initDrawMethod(newOptions);
     }
@@ -400,7 +409,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) => {
@@ -427,7 +440,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();
 };
@@ -443,7 +460,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);
+      }
     }
   },
   {
@@ -454,72 +475,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

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

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

@@ -73,7 +73,7 @@ let map2Ref = ref(null);
 let leftMenuRef = ref(null);
 let showMask = ref(true);
 //  vectorgraph satellite imageMap imageMap3 logical satellite2 satellite3
-let activeMap = ref('satellite');
+let activeMap = ref('satellite3');
 // 附近视频菜单数据
 let tempMenu = ref({
   name: '',