Browse Source

实时标绘 颜色、线宽

Hwf 8 months ago
parent
commit
3f116ddcaf

+ 6 - 0
src/assets/styles/element-ui.scss

@@ -169,3 +169,9 @@
     background: #081b41;
   }
 }
+
+.custom-color-picker {
+  .el-color-dropdown__link-btn {
+    display: none;
+  }
+}

+ 1 - 0
src/components/FooterSection/index.vue

@@ -7,5 +7,6 @@
   width: 8960px;
   height: 256px;
   background: url('@/assets/images/footer.png') no-repeat 100% 100%;
+  pointer-events: none;
 }
 </style>

+ 12 - 6
src/components/Map/YztMap/index.vue

@@ -7,13 +7,13 @@
 <script setup lang="ts">
 import 'ol/ol.css';
 import { olMap } from '@/utils/olMap/olMap';
+import { PointType } from '@/api/globalMap/type';
 
 interface Props {
   activeMap: string;
   drawing: boolean;
-  color: string;
-  drawType: string;
-  graphicsType: string;
+  mouseToolState: MouseTool;
+  pointType: PointType[];
 }
 const containerScale = inject('containerScale');
 const props = withDefaults(defineProps<Props>(), {});
@@ -80,17 +80,19 @@ const init = () => {
             emits('selectGraphics', data);
           }
         });
-
       }
     },
     // 加载完成事件
-    onLoadCompleted: (map) => {
-      yztMap = map;
+    onLoadCompleted: (yMap) => {
+      yztMap = yMap;
       // initMouseTool(map);
       handleResize();
     }
   });
 };
+const addMarker = (points) => {
+  map.addMarker(points);
+};
 // 设置地图层级
 const setMapZoom = (value) => {
   if (!yztMap) return;
@@ -119,6 +121,10 @@ const switchThreeDimensional = () => {
   const pitch = mapState.isThreeDimensional ? 45 : 0;
   view.setPitch(pitch);
 };
+const clearMarker = () => {
+  this.map.clearMarker();
+};
+defineExpose({ addMarker, clearMarker });
 const handleResize = () => {
   const containerWidth = containerRef.value.clientWidth * containerScale().scaleX;
   const containerHeight = containerRef.value.clientHeight * containerScale().scaleY;

+ 8 - 27
src/components/Map/index.vue

@@ -39,9 +39,7 @@ import { pointDetailTemplate } from '@/views/globalMap/data/mapData';
 interface Props {
   activeMap: string;
   drawing: boolean;
-  color: string;
-  drawType: string;
-  graphicsType: string;
+  mouseToolState: MouseTool;
   pointType: PointType[];
 }
 
@@ -68,10 +66,11 @@ const mapState = reactive({
 let AMap, map, scale;
 
 // 鼠标绘制工具
-const { initMouseTool, drawGraphics, setColor, setDrawType, setGraphicsType, closeDraw, handleUndo } = useDrawTool({
-  color: props.color,
-  drawType: props.drawType,
-  graphicsType: props.graphicsType,
+const { initMouseTool, drawGraphics, setOptions, closeDraw, handleUndo } = useDrawTool({
+  color: props.mouseToolState.color,
+  lineWidth: props.mouseToolState.lineWidth,
+  drawType: props.mouseToolState.drawType,
+  graphicsType: props.mouseToolState.graphicsType,
   // 绘制完成事件
   onDrawCompleted: (data, overlaysData, obj) => {
     if (overlaysData.length === 1) {
@@ -89,7 +88,7 @@ const { initMouseTool, drawGraphics, setColor, setDrawType, setGraphicsType, clo
   }
 });
 // 测距工具
-const { initRuler, isRanging, toggleRangingTool } = useRuler();
+const { initRuler } = useRuler();
 // 初始化地图
 const { getAMap, getMap, switchMap, addMarker, addSearchMarker, clearMarker, getMarkers, getScale, showInfo } = useAMap({
   key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
@@ -189,30 +188,12 @@ watch(
   () => props.drawing,
   (value) => {
     if (value) {
-      drawGraphics(props.graphicsType);
+      drawGraphics(props.mouseToolState);
     } else {
       closeDraw();
     }
   }
 );
-watch(
-  () => props.color,
-  () => {
-    setColor(props.color);
-  }
-);
-watch(
-  () => props.drawType,
-  () => {
-    setDrawType(props.drawType);
-  }
-);
-watch(
-  () => props.graphicsType,
-  () => {
-    setGraphicsType(props.graphicsType);
-  }
-);
 // 缩放级别变化
 const zoomChangeHandler = () => {
   mapState.zoom = map.getZoom();

+ 1 - 1
src/hooks/AMap/useAMap.ts

@@ -111,7 +111,7 @@ export function useAMap(options) {
       map, //地图实例
       points, //海量点数据,数据中需包含经纬度信息字段 lnglat
       {
-        gridSize: 20, //数据聚合计算时网格的像素大小
+        gridSize: 30, //数据聚合计算时网格的像素大小
         renderClusterMarker: _renderClusterMarker, //上述步骤的自定义聚合点样式
         renderMarker: _renderMarker //上述步骤的自定义非聚合点样式
       }

+ 10 - 8
src/hooks/AMap/useDrawTool.ts

@@ -6,6 +6,7 @@ import { countCircleArea, countRectangleArea } from '@/utils/geometryUtil';
 interface DrawToolOptions {
   color: string;
   drawType: string;
+  lineWidth: string;
   graphicsType: string;
   onDrawCompleted?: Function;
 }
@@ -13,7 +14,7 @@ export function useDrawTool(options: DrawToolOptions) {
   const drawOptions = {
     strokeColor: options.color,
     strokeOpacity: 1,
-    strokeWeight: 2,
+    strokeWeight: options.lineWidth,
     fillColor: options.color,
     fillOpacity: options.drawType === '1' ? 0 : 0.5,
     strokeStyle: 'solid'
@@ -104,14 +105,15 @@ export function useDrawTool(options: DrawToolOptions) {
     return res;
   };
   // 绘制图形
-  const drawGraphics = (type: string) => {
-    if (type === 'circle') {
+  const drawGraphics = (newOptions: MouseTool) => {
+    options = deepClone(newOptions);
+    if (options.graphicsType === 'circle') {
       // 绘制圆形
       mouseTool.circle(drawOptions);
-    } else if (type === 'rectangle') {
+    } else if (options.graphicsType === 'rectangle') {
       // 绘制矩形
       mouseTool.rectangle(drawOptions);
-    } else if (type === 'polygon') {
+    } else if (options.graphicsType === 'polygon') {
       // 绘制多边形
       mouseTool.polygon(drawOptions);
     }
@@ -159,7 +161,7 @@ export function useDrawTool(options: DrawToolOptions) {
       radius: options.radius, //半径
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'
@@ -176,7 +178,7 @@ export function useDrawTool(options: DrawToolOptions) {
       bounds: bounds,
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'
@@ -194,7 +196,7 @@ export function useDrawTool(options: DrawToolOptions) {
       path: path,
       strokeColor: options.color,
       strokeOpacity: 1,
-      strokeWeight: 2,
+      strokeWeight: options.lineWidth,
       fillColor: options.color,
       fillOpacity: options.drawType === '1' ? 0 : 0.5,
       strokeStyle: 'solid'

+ 6 - 0
src/types/map.d.ts

@@ -0,0 +1,6 @@
+interface MouseTool {
+  color: string,
+  lineWidth: string,
+  drawType: string,
+  graphicsType: string,
+}

+ 16 - 0
src/utils/index.ts

@@ -360,3 +360,19 @@ export const getTransformScale = (element) => {
   // 如果没有找到scale变换,返回默认的缩放比例
   return { scaleX: 1, scaleY: 1 };
 };
+
+export const setRGBAOpacityToOne = (rgbaString) => {
+  // 使用正则表达式匹配rgba的各个部分
+  const match = rgbaString.match(/^rgba\(\s*(\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\s*\)$/);
+  if (!match) {
+    // 如果匹配失败,返回原始字符串或抛出错误
+    console.error('Invalid RGBA format:', rgbaString);
+    return rgbaString; // 或者抛出错误
+  }
+
+  // 提取出红、绿、蓝和原始透明度
+  const [r, g, b, originalOpacity] = match.slice(1).map(Number);
+
+  // 返回新的RGBA字符串,其中透明度被设置为1
+  return `rgba(${r}, ${g}, ${b}, 1)`;
+}

+ 101 - 18
src/utils/olMap/olMap.ts

@@ -1,15 +1,24 @@
 // 引入OpenLayers的主模块
 import Map from 'ol/Map';
 import View from 'ol/View';
+import Feature from 'ol/Feature';
+import Point from 'ol/geom/Point';
+import VectorLayer from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import Style from 'ol/style/Style';
+import Icon from 'ol/style/Icon';
+import Text from 'ol/style/Text';
 import Projection from 'ol/proj/Projection';
 import { getWidth, getTopLeft } from 'ol/extent';
 import TileLayer from 'ol/layer/Tile';
 import WMTS from 'ol/source/WMTS';
 import WMTSTileGrid from 'ol/tilegrid/WMTS';
 import WMTSCapabilities from 'ol/format/WMTSCapabilities';
+import { Fill, Stroke } from 'ol/style';
 import proj4 from 'proj4';
 import { register } from 'ol/proj/proj4';
 import { defaults } from 'ol/control';
+import { fromLonLat } from 'ol/proj';
 import axios from 'axios';
 // import olPlot from 'ol-plot';
 // import { activate } from '../ol-plot/ol-plot'
@@ -31,6 +40,8 @@ for (let z = 2; z < 22; ++z) {
 
 export class olMap {
   private map;
+  private options;
+  private markers = [];
   private drawOptions = {
     graphicsType: 'circle',
     strokeColor: '#f80102',
@@ -45,8 +56,10 @@ export class olMap {
   private overlaysData = [];
   private graphicsType = '';
   private plot;
+  private vectorLayer;
 
   constructor(options) {
+    this.options = options;
     this.map = new Map({
       controls: defaults({
         zoom: false,
@@ -66,20 +79,31 @@ export class olMap {
     if (options.showScale) {
       // map.addControl(new AMap.Scale());
     }
-    if (options.drawTool.use) {
+    if (options.drawTool?.use) {
       this.initMouseTool(options.drawTool);
     }
-    if (typeof options.onLoadCompleted === 'function') {
-      options.onLoadCompleted(this.map);
-    }
+    this.initLayer(options);
+  }
+  async initLayer(options) {
     // 添加新的图层
     if (Array.isArray(options.id)) {
-      options.id.forEach((layer)=> {
-        this.formatXml(layer);
-      });
+      for (const layer of options.id) {
+        await this.formatXml(layer); // 等待当前 layer 处理完成
+      }
     } else if (options.id) {
-      // 如果newLayers不是数组,但确实是一个图层,则直接添加
-      this.formatXml(options.id);
+      // 如果 options.id 不是数组,但确实是一个图层,则直接处理
+      await this.formatXml(options.id);
+    }
+    console.log('wan')
+    // 创建Vector层并添加到地图上
+    this.vectorLayer = new VectorLayer({
+      source: new VectorSource({
+        features: []
+      })
+    });
+    this.map.addLayer(this.vectorLayer);
+    if (typeof this.options.onLoadCompleted === 'function') {
+      this.options.onLoadCompleted(this.map);
     }
   }
 
@@ -88,7 +112,7 @@ export class olMap {
     // axios.post(commonUrl + 'YZT1723547712680', '<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs"service="WFS"version="1.0.0"outputFormat="GeoJson"maxFeatures="99999"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.opengis.net/wfshttp://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd">\n' +
     //   '<wfs:QuerytypeName="GDSMMSZJGZDTJX_2023"userecent="true"/>\n' +
     //   '</wfs:GetFeature>');
-    this.getCapabilities(code).then((lists) => {
+    return this.getCapabilities(code).then((lists) => {
       const mapData = xml.read(lists.data);
       const layerParam = {
         layerName: mapData.Contents.Layer[0].Title,
@@ -99,10 +123,12 @@ export class olMap {
       this.createWmsLayer(code, layerParam, minZoom, maxZoom, zIndex, visible);
     });
   }
+
   // 请求接口获取地图信息
   getCapabilities(code) {
     return axios.get(commonUrl + code + '?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetCapabilities');
   }
+
   // 请求地图图片加载图层
   createWmsLayer(code, layerParam, minZoom = 0, maxZoom, zIndex = -1, visible = true) {
     const source = new WMTS({
@@ -129,7 +155,9 @@ export class olMap {
     layer.set('layerName', code);
 
     this.map.addLayer(layer);
-  };
+    console.log(code)
+  }
+
   // 初始化绘画工具
   initMouseTool(options) {
     this.drawOptions = {
@@ -148,6 +176,7 @@ export class olMap {
     // });
     // this.plot.plotDraw.on('drawEnd', this.onDrawEnd.bind(this));
   }
+
   // 绘制结束事件
   onDrawEnd(event) {
     const feature = event.feature;
@@ -155,11 +184,12 @@ export class olMap {
     // 继续编辑编辑
     this.drawGraphics(this.drawOptions.graphicsType);
   }
+
   // 绘制图形
   drawGraphics(type: string) {
     if (type === 'circle') {
       // 绘制圆形
-      activate('Circle');
+      // activate('Circle');
     } else if (type === 'rectangle') {
       // 绘制矩形
       this.plot.plotDraw.activate('RectAngle');
@@ -171,12 +201,14 @@ export class olMap {
       this.plot.plotDraw.activate('FreePolygon');
     }
   }
+
   // 关闭绘制
   closeDraw() {
     this.plot.plotDraw.deactivate();
   }
+
   // 切换图层
-  replaceLayers(newLayers) {
+  async replaceLayers(newLayers) {
     // 遍历当前的所有图层并移除它们
     this.map.getLayers().forEach((layer) => {
       this.map.removeLayer(layer);
@@ -184,12 +216,63 @@ export class olMap {
 
     // 添加新的图层
     if (Array.isArray(newLayers)) {
-      newLayers.forEach((layer)=> {
-        this.formatXml(layer);
-      });
+      for (const layer of newLayers) {
+        await this.formatXml(layer); // 等待当前 layer 处理完成
+      }
     } else if (newLayers) {
-      // 如果newLayers不是数组,但确实是一个图层,则直接添加
-      this.formatXml(newLayers);
+      // 如果 options.id 不是数组,但确实是一个图层,则直接处理
+      await this.formatXml(newLayers);
     }
+    // 创建Vector层并添加到地图上
+    this.vectorLayer = new VectorLayer({
+      source: new VectorSource({
+        features: []
+      })
+    });
+    this.map.addLayer(this.vectorLayer);
+    const point = JSON.parse(JSON.stringify(this.markers));
+    this.markers = [];
+    this.addMarker(point)
+  }
+
+  addMarker(points) {
+
+    points.forEach((point) => {
+      // 创建标注点
+      const feature = new Feature({
+        geometry: new Point([point.longitude, point.latitude]),
+        name: point.name
+      });
+
+      // 定义样式
+      const style = new Style({
+        image: new Icon({
+          anchor: [0.5, point.size[1]],
+          anchorXUnits: 'fraction',
+          anchorYUnits: 'pixels',
+          src: point.image
+        }),
+        text: new Text({
+          text: point.name,
+          fill: new Fill({
+            color: '#000'
+          }),
+          stroke: new Stroke({
+            color: '#fff',
+            width: 3
+          })
+        })
+      });
+
+      feature.setStyle(style);
+      this.markers.push(point);
+      this.vectorLayer.getSource().addFeature(feature);
+    });
+  }
+
+  // 清除所有标加
+  clearMarker(id) {
+    if (!this.vectorLayer) return;
+    this.vectorLayer.getSource().clear();
   }
 }

+ 97 - 1
src/views/globalMap/RightMenu/OnlinePlotting.vue

@@ -1,11 +1,107 @@
 <template>
-
+  <div class="gradient-text title">实时标绘</div>
+  <el-select v-model="mouseToolState.lineWidth" placeholder="Select" style="width: 240px">
+    <el-option
+      v-for="item in lineWidthOptions"
+      :key="item.value"
+      :label="item.name"
+      :value="item.value"
+    >
+      <span >{{ item.name }}</span>
+    </el-option>
+  </el-select>
+  <el-color-picker v-model="mouseToolState.color" popper-class="custom-color-picker" show-alpha size="large" />
+  <el-button @click="changeDrawing" size="large">{{ drawing ? '关闭' : '开启' }}</el-button>
+  <el-tabs
+    v-model="activeName"
+    type="card"
+    class="demo-tabs"
+    @tab-click="handleClick"
+  >
+    <el-tab-pane  v-for="(item, index) in menu" :key="index" :label="User" name="first">User</el-tab-pane>
+  </el-tabs>
 </template>
 
 <script lang="ts" setup>
+interface Props {
+  drawing: boolean;
+  mouseToolState: MouseTool;
+}
+
+const props = withDefaults(defineProps<Props>(), {});
+const emits = defineEmits(['updateDrawing']);
+
+const menu = ref([
+  {
+    name: '标绘工具',
+    value: '1',
+    children: [
+      {
+        name: '基本工具',
+        children: [
+          {
+            name: '直箭头',
+            value: 'straightArrow'
+          },
+          {
+            name: '矩形',
+            value: 'rectangle'
+          },
+          {
+            name: '任意面',
+            value: 'polygon'
+          },
+          {
+            name: '任意线',
+            value: ''
+          },
+          {
+            name: '圆',
+            value: 'circle'
+          },
+          {
+            name: '直线',
+            value: 'straightLine'
+          },
+          {
+            name: '文字',
+            value: 'text'
+          },
+          {
+            name: '面积',
+            value: ''
+          }
+        ]
+      }
+    ]
+  },
+  {
+    name: '历史记录',
+    value: 'history',
+    children: []
+  }
+])
+const lineWidthOptions = reactive([
+  { name: '1像素', value: '1px' },
+  { name: '2像素', value: '2px' },
+  { name: '3像素', value: '3px' }
+])
 
+const changeDrawing = () => {
+  debugger
+  emits('updateDrawing', !props.drawing)
+}
 </script>
 
 <style lang="scss" scoped>
+.title {
+  font-size: 60px;
+  position: absolute;
+  top: 12px;
+  left: 210px;
+}
 
+:deep(.el-color-dropdown__link-btn) {
+  display: none;
+}
 </style>

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

@@ -33,7 +33,7 @@
         <!--河道监测-->
         <RiverMonitor v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '河道监测'" />
         <!--实时标绘-->
-        <OnlinePlotting v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '实时标绘'" />
+        <OnlinePlotting v-if="menuState.showMenu && menuState.menuData[menuState.activeIndex]?.name === '实时标绘'" :mouseToolState="mouseToolState" :drawing="drawing" @updateDrawing="updateDrawing" />
       </div>
     </div>
   </div>
@@ -49,10 +49,13 @@ import LayerAnalysis from '@/views/globalMap/RightMenu/LayerAnalysis.vue';
 import OnlinePlotting from '@/views/globalMap/RightMenu/OnlinePlotting.vue';
 
 interface Props {
+  drawing: boolean;
   pointType: string[];
+  mouseToolState: MouseTool;
 }
 
 const props = withDefaults(defineProps<Props>(), {});
+const emits = defineEmits(['update:drawing']);
 const scrollListRef = ref();
 const menuState = reactive({
   showMenu: false,
@@ -117,6 +120,10 @@ const updateMenu = (type, menu) => {
   }
 };
 
+const updateDrawing = (value: boolean) => {
+  debugger
+  emits('update:drawing', value);
+}
 defineExpose({ handleMenu, clickContractMenu, setAnalysisData, updateMenu });
 </script>
 

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

@@ -5,21 +5,17 @@
       <YztMap
         v-else-if="['satellite2', 'satellite3'].includes(activeMap)"
         ref="map2Ref"
-        v-model:drawing="mouseToolState.drawing"
+        v-model:drawing="drawing"
+        :mouseToolState="mouseToolState"
         :active-map="activeMap"
-        :color="mouseToolState.color"
-        :draw-type="mouseToolState.drawType"
-        :graphics-type="mouseToolState.graphicsType"
         :pointType="pointType"
         @select-graphics="analysisSpatial"
       />
       <Map
         v-else
         ref="mapRef"
-        v-model:drawing="mouseToolState.drawing"
-        :color="mouseToolState.color"
-        :draw-type="mouseToolState.drawType"
-        :graphics-type="mouseToolState.graphicsType"
+        v-model:drawing="drawing"
+        :mouseToolState="mouseToolState"
         :active-map="activeMap"
         :pointType="pointType"
         @select-graphics="analysisSpatial"
@@ -28,7 +24,7 @@
       <!--左侧菜单-->
       <LeftMenu style="position: absolute; top: 20px; left: 20px" @click-menu="clickMenu" @select-search-marker="selectSearchMarker" />
       <!--右侧菜单-->
-      <RightMenu ref="rightMenuRef" :analysis-data="selectedScope" :pointType="pointType" />
+      <RightMenu ref="rightMenuRef" :analysis-data="selectedScope" v-model:drawing="drawing" :mouseToolState="mouseToolState" :pointType="pointType" />
       <!--更换地图类型-->
       <SwitchMapTool :active-map="activeMap" class="tool-box" @switch-map="switchMap" />
       <!--时间轴-->
@@ -163,14 +159,20 @@ const selectSearchMarker = (item) => {
   dom.addSearchMarker(item2);
 };
 
-const mouseToolState = reactive({
-  drawing: false,
+// 实时标绘
+let drawing = ref(false);
+const mouseToolState = reactive<MouseTool>({
   color: '#f80102',
-  // 1线框 2区域
+  lineWidth: '1px',
+  // 1基本图形 2图标
   drawType: '1',
   // 图形形状  circle圆形 rectangle矩形 polygon多边形
   graphicsType: 'circle'
 });
+// 绘制工具配置变化
+watch(mouseToolState, () => {
+  console.log(mouseToolState);
+}, { immediate: true })
 // 点击撤销
 const undo = () => {
   const dom = activeMap.value === 'satellite2' ? map2Ref.value : mapRef.value;