Browse Source

实现地图遮罩切换、粤政图比例尺、缩放

Hwf 6 months ago
parent
commit
6dddcf1397

+ 91 - 73
src/components/Map/YztMap/index.vue

@@ -1,22 +1,35 @@
 <template>
   <div ref="containerRef" class="map-container">
     <div ref="mapRef" id="YztMap" class="map-container" :style="{ width: width, height: height }"></div>
+    <!-- 右下工具  -->
+        <div v-show="mapState.showScale" class="zoom-text">{{ mapState.zoom }}级</div>
+    <div class="right-tool">
+      <!-- 快捷缩放 -->
+      <QuickZoom v-model:zoom="mapState.zoom" @change-step="setMapZoom" />
+      <div class="flex" style="margin-top: 5px; justify-content: center">
+        <div class="mask-btn" @click="handleShowMask">{{ showMask ? '显' : '隐' }}</div>
+        <!--        <div class="model-btn" @click="switchThreeDimensional">{{ mapState.isThreeDimensional ? '3D' : '2D' }}</div>-->
+        <div class="ruler-icon" style="margin-left: 5px" @click="changeScaleControl"></div>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import 'ol/ol.css';
-import mmJson from '@/assets/json/mm.json'
+import mmJson from '@/assets/json/mm.json';
 import { olMap } from '@/utils/olMap/olMap';
 import { PointType } from '@/api/globalMap/type';
+import { ScaleLine } from 'ol/control';
 
 interface Props {
   activeMap: string;
   pointType: PointType[];
+  showMask: boolean;
 }
 const containerScale = inject('containerScale');
 const props = withDefaults(defineProps<Props>(), {});
-const emits = defineEmits(['update:drawing', 'selectGraphics']);
+const emits = defineEmits(['update:drawing', 'update:showMask', 'selectGraphics']);
 
 const mapRef = ref(null);
 const mapState = reactive({
@@ -56,10 +69,19 @@ watch(
   () => {
     if (!map) return;
     map.replaceLayers(mapList[props.activeMap]);
-    map.createVecByJson(mmJson, '茂名市');
   }
 );
-
+watch(
+  () => props.showMask,
+  () => {
+    if (props.showMask) {
+      map.createVecByJson(mmJson, '茂名市');
+    } else {
+      map.removeMask();
+    }
+  }
+);
+let scaleLine;
 const init = () => {
   map = new olMap({
     dom: mapRef.value,
@@ -89,7 +111,14 @@ const init = () => {
     onLoadCompleted: (yMap) => {
       yztMap = yMap;
       // initMouseTool(map);
-      map.createVecByJson(mmJson, '茂名市');
+      scaleLine = new ScaleLine();
+      yztMap.addControl(scaleLine);
+      yztMap.getView().on('change:resolution', () => {
+        mapState.zoom = yztMap.getView().getZoom().toFixed(2);
+      });
+      if (props.showMask) {
+        map.createVecByJson(mmJson, '茂名市');
+      }
       handleResize();
     }
   });
@@ -100,22 +129,19 @@ const addMarker = (points) => {
 // 设置地图层级
 const setMapZoom = (value) => {
   if (!yztMap) return;
-  const view = yztMap.map.getView();
+  const view = yztMap.getView();
   if (value === 1) {
-    view.setCenter([113.280637, 23.125178]);
-    view.setZoom(7);
-  } else if (value === 2) {
     view.setCenter([110.925175, 21.678955]);
-    view.setZoom(11);
+    view.setZoom(7.9);
+  } else if (value === 2) {
+    view.setCenter([110.925175, 21.6789558]);
+    view.setZoom(9.21);
   } else if (value === 3) {
     view.setCenter([110.925175, 21.678955]);
-    view.setZoom(12);
+    view.setZoom(11.38);
   } else if (value === 4) {
     view.setCenter([110.925175, 21.678955]);
-    view.setZoom(15);
-  } else if (value === 5) {
-    view.setCenter([110.925175, 21.678955]);
-    view.setZoom(16);
+    view.setZoom(12.83);
   }
 };
 // 切换2D、3D
@@ -125,8 +151,20 @@ const switchThreeDimensional = () => {
   const pitch = mapState.isThreeDimensional ? 45 : 0;
   view.setPitch(pitch);
 };
+// 切换比例尺
+const changeScaleControl = () => {
+  mapState.showScale = !mapState.showScale;
+  if (mapState.showScale) {
+    yztMap.addControl(scaleLine);
+  } else {
+    yztMap.removeControl(scaleLine);
+  }
+};
 const clearMarker = () => {
-  this.map.clearMarker();
+  map.clearMarker();
+};
+const handleShowMask = () => {
+  emits('update:showMask', !props.showMask);
 };
 defineExpose({ addMarker, clearMarker });
 const handleResize = () => {
@@ -154,75 +192,55 @@ onUnmounted(() => {
   position: relative;
   .zoom-text {
     position: absolute;
-    bottom: 8px;
-    right: 75px;
-    font-size: 14px;
+    bottom: 120px;
+    right: 170px;
+    color: #eaf3fc;
+    font-size: 25.73px;
   }
   .right-tool {
     position: absolute;
-    bottom: 50px;
-    right: 5px;
-  }
-  .model-btn {
-    background-color: #022577;
-    font-size: 14px;
-    color: #889ee9;
+    bottom: 160px;
+    right: 175px;
+    z-index: 2;
+  }
+  .mask-btn {
+    background-color: #04327c;
+    font-size: 25.73px;
+    border: 1px solid #91cfff;
+    color: #eaf3fc;
     cursor: pointer;
     padding: 3px 3px;
     border-radius: 5px;
+    //margin-right: 5px;
+    cursor: pointer;
   }
-  :deep(.amap-scalecontrol) {
-    left: unset !important;
-    background-color: unset !important;
-    right: 74px;
-  }
-  :deep(.amap-scale-text) {
-    width: 230px !important;
-    text-align: left !important;
-    font-size: 14px;
-    padding-left: 20px;
-  }
-  :deep(.amap-scale-middle) {
-    width: 228px !important;
-  }
-  :deep(.amap-scale-edgeright) {
-    left: 228px !important;
-  }
-  :deep(.amap-logo),
-  :deep(.amap-copyright) {
-    display: none !important;
-  }
-  /* 自定义测距工具样式 */
-  :deep(.amap-ranger) {
-    background-color: #ffffff;
-    border: 1px solid #888888;
+  .model-btn {
+    background-color: #04327c;
+    font-size: 25.73px;
+    font-family: 'SourceHanSansCN';
+    border: 1px solid #91cfff;
+    color: #eaf3fc;
+    cursor: pointer;
+    padding: 3px 3px;
     border-radius: 5px;
-    padding: 5px;
-  }
-
-  :deep(.amap-ranger .amap-toolbar) {
-    color: #333;
-    font-size: 12px;
-    padding: 5px;
   }
-
-  :deep(.amap-ranger .amap-toolbar button) {
-    background-color: #022577;
-    color: #fff;
-    border: none;
-    border-radius: 4px;
-    padding: 5px;
-    margin-right: 5px;
+  .ruler-icon {
+    width: 42px;
+    height: 42px;
+    background: url('@/assets/images/map/ruler.png') no-repeat;
+    background-size: 100% 100%;
     cursor: pointer;
   }
-
-  :deep(.amap-ranger .amap-toolbar button:hover) {
-    background-color: #1a3c75;
+  :deep(.ol-scale-line) {
+    left: unset;
+    bottom: 16px;
+    right: 48px;
+    background-color: transparent;
   }
-
-  :deep(.amap-ranger .amap-toolbar .amap-toolbar-text) {
-    color: #444;
-    font-size: 14px;
+  :deep(.ol-scale-line-inner) {
+    color: #ffffff;
+    border: 1px solid #fff;
+    border-top: none;
   }
 }
 </style>

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

@@ -7,6 +7,7 @@
       <!-- 快捷缩放 -->
       <QuickZoom v-model:zoom="mapState.zoom" @change-step="setMapZoom" />
       <div class="flex" style="margin-top: 5px">
+        <div class="mask-btn" @click="handleShowMask">{{ showMask ? '显' : '隐' }}</div>
         <div class="model-btn" @click="switchThreeDimensional">{{ mapState.isThreeDimensional ? '3D' : '2D' }}</div>
         <div class="ruler-icon" style="margin-left: 5px" @click="changeScaleControl"></div>
       </div>
@@ -46,15 +47,19 @@ import {
   getEmergencyDisasterInfoOfficerDetails,
   getMidmapDzzhDetails,
   getVehicleDetails,
-  getMDPUnitDetails, getWaterList, getVideoDrowning, getVideoForestFire, getVideoDisasterPrevention
-} from "@/api/globalMap/spatialAnalysis";
+  getMDPUnitDetails,
+  getWaterList,
+  getVideoDrowning,
+  getVideoForestFire,
+  getVideoDisasterPrevention
+} from '@/api/globalMap/spatialAnalysis';
 import { pointDetailTemplate } from '@/views/globalMap/data/mapData';
-import ElementResizeDetectorMaker from 'element-resize-detector';
 import useAppStore from '@/store/modules/app';
 import { getRescueTeamsInfo } from '@/api/globalMap/rescueTeam';
 interface Props {
   activeMap: string;
   pointType: PointType[];
+  showMask: boolean;
 }
 
 const containerScale = inject('containerScale');
@@ -65,6 +70,7 @@ const appStore = useAppStore();
 
 const emits = defineEmits([
   'update:drawing',
+  'update:showMask',
   'selectGraphics',
   'unSelectGraphics',
   'showTextEditBox',
@@ -120,9 +126,9 @@ const mapUtils = useAMap({
     });
     map.on('zoomchange', zoomChangeHandler);
     // 添加遮罩
-    // , { strokeColor: '#ff0000',strokeWeight: 2, offset }
-    //   { strokeColor: '#2a8797', strokeOpacity: 0.6,strokeWeight: 6, fillOpacity: 0},
-    creatMask([{ strokeWeight: 2 }]);
+    if (props.showMask) {
+      creatMask([{ strokeWeight: 2 }]);
+    }
     drawTool.initMouseTool({ container: 'aMap', map, AMap });
     handleResize();
   },
@@ -192,6 +198,7 @@ const {
   hideInfo,
   handleHover,
   creatMask,
+  removeMask,
   trackPlayback
 } = { ...mapUtils };
 const handlePointDetails = (data) => {
@@ -405,7 +412,17 @@ watch(
     switchMap(props.activeMap);
   }
 );
-// watch(
+watch(
+  () => props.showMask,
+  () => {
+    if (props.showMask) {
+      creatMask([{ strokeWeight: 2 }]);
+    } else {
+      removeMask();
+    }
+  }
+);
+// watc h(
 //   () => props.mouseToolState,
 //   () => {
 //     if (props.drawing) {
@@ -468,7 +485,22 @@ const getMapUtils = () => {
 const getPlaceSearch = () => {
   return placeSearch;
 };
-defineExpose({ addMarker, addSearchMarker, setCenter, getMarkers, clearMarker, getMap, drawTool, handleHover, trackPlayback, getMapUtils, getPlaceSearch });
+const handleShowMask = () => {
+  emits('update:showMask', !props.showMask);
+};
+defineExpose({
+  addMarker,
+  addSearchMarker,
+  setCenter,
+  getMarkers,
+  clearMarker,
+  getMap,
+  drawTool,
+  handleHover,
+  trackPlayback,
+  getMapUtils,
+  getPlaceSearch
+});
 const handleResize = () => {
   const containerWidth = containerRef.value.clientWidth * containerScale().scaleX;
   const containerHeight = containerRef.value.clientHeight * containerScale().scaleY;
@@ -528,6 +560,17 @@ onUnmounted(() => {
     right: 175px;
     z-index: 2;
   }
+  .mask-btn {
+    background-color: #04327c;
+    font-size: 25.73px;
+    border: 1px solid #91cfff;
+    color: #eaf3fc;
+    cursor: pointer;
+    padding: 3px 3px;
+    border-radius: 5px;
+    margin-right: 5px;
+    cursor: pointer;
+  }
   .model-btn {
     background-color: #04327c;
     font-size: 25.73px;

+ 11 - 5
src/hooks/AMap/useAMap.ts

@@ -232,7 +232,7 @@ export function useAMap(options) {
       }
     }
   };
-
+  let maskPolygon;
   const creatMask = (options, name = '茂名市') => {
     new AMap.DistrictSearch({
       extensions: 'all',
@@ -255,7 +255,7 @@ export function useAMap(options) {
         // }
         let pathArray = [outer];
         pathArray.push.apply(pathArray, holes);
-        const polygon = new AMap.Polygon({
+        maskPolygon = new AMap.Polygon({
           pathL: pathArray,
           strokeColor: option.strokeColor ? option.strokeColor : '#268ab9',
           strokeOpacity: option.strokeOpacity ? option.strokeOpacity : 1,
@@ -263,12 +263,17 @@ export function useAMap(options) {
           fillColor: option.fillColor ? option.fillColor : '#10243b',
           fillOpacity: option.fillOpacity ? option.fillOpacity : 0.65
         });
-        polygon.setPath(pathArray);
-        map.add(polygon);
+        maskPolygon.setPath(pathArray);
+        map.add(maskPolygon);
       });
     });
   };
-
+  const removeMask = () => {
+    if (!!maskPolygon) {
+      map.remove(maskPolygon);
+      maskPolygon = null;
+    }
+  };
   let moveMarker, movePolyline, movePassedPolyline, timerId;
   const trackPlayback = (lineArr) => {
     if (timerId) {
@@ -467,6 +472,7 @@ export function useAMap(options) {
     hideInfo,
     handleHover,
     creatMask,
+    removeMask,
     trackPlayback,
     drawData
   };

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

@@ -21,6 +21,7 @@ declare module 'vue' {
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
@@ -37,22 +38,34 @@ declare module 'vue' {
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElLink: typeof import('element-plus/es')['ElLink']
     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']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
     ElSlider: typeof import('element-plus/es')['ElSlider']
+    ElStep: typeof import('element-plus/es')['ElStep']
+    ElSteps: typeof import('element-plus/es')['ElSteps']
     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']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
     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']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
     FooterSection: typeof import('./../components/FooterSection/index.vue')['default']
@@ -63,6 +76,7 @@ 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']
+    IEpUploadFilled: typeof import('~icons/ep/upload-filled')['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']
@@ -99,4 +113,7 @@ declare module 'vue' {
     YMapold: typeof import('./../components/Map/YMapold.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/index.vue')['default']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 9 - 68
src/utils/olMap/olMap.ts

@@ -288,10 +288,12 @@ export class olMap {
    * @param {String} layerName 图层名称
    * @returns VectorLayer
    */
+
   createVecByJson(json, layerName = '') {
     const format = new GeoJSON();
     const fs = format.readFeatures(json);
-    const converLayer = new VectorLayer({
+    this.removeMask();
+    this.maskLayer = new VectorLayer({
       source: new VectorSource(),
       style: new Style({
         fill: new Fill({
@@ -304,7 +306,7 @@ export class olMap {
       }),
       zIndex: 99
     });
-    this.map.addLayer(converLayer);
+    this.map.addLayer(this.maskLayer);
     const extent = [-180, -90, 180, 90];
     const polygonRing = fromExtent(extent);
     fs.forEach((x) => {
@@ -318,73 +320,12 @@ export class olMap {
     const convertFt = new Feature({
       geometry: polygonRing
     });
-    converLayer.getSource().addFeature(convertFt);
-  }
-
-  /**
-   * @description 创建矢量图层
-   * @param {String} layerName 图层名称
-   * @param {Number} zIndex 地图层级默认是0
-   * @returns
-   */
-  createVecLayer(layerName = '', zIndex = 0) {
-    const source = new SourceVector({
-      crossOrigin: 'anonymous'
-    });
-    const layer = new Vector({
-      source,
-      zIndex
-    });
-    layer.set('layerName', layerName);
-    return layer;
-  }
-  // 分布图遮罩层
-  createMask(data) {
-    this.removeMask();
-    if (!data || data.length === 0) return;
-    data.forEach((item) => {
-      if (!item.points || item.points.length === 0) return;
-      // 遮罩图层的样式
-      const maskStyle = new Style({
-        fill: new Fill({
-          color: item.color // 红色遮罩,50%透明度
-        }),
-        stroke: new Stroke({
-          color: 'rgba(159,159,159,0.7)',
-          width: 1
-        })
-      });
-
-      // 遮罩图层的矢量数据源(初始为空)
-      const maskSource = new VectorSource();
-      // 创建一个多边形特征
-      const polygonFeature = new Feature({
-        geometry: new Polygon(item.points)
-      });
-      const maskLayer = new VectorLayer({
-        source: maskSource,
-        style: maskStyle,
-        properties: {
-          name: 'mask'
-        }
-      });
-      this.map.addLayer(maskLayer);
-      // 将多边形特征添加到遮罩数据源中
-      maskSource.addFeature(polygonFeature);
-    });
+    this.maskLayer.getSource().addFeature(convertFt);
   }
   removeMask() {
-    //移除图层
-    const layersArray = this.map.getLayers().getArray();
-    const layers = [];
-    layersArray.forEach((layer) => {
-      // 检查图层是否有自定义属性,并且该属性是否匹配你要移除的图层的标识符
-      if (layer.get('name') === 'mask') {
-        layers.push(layer);
-      }
-    });
-    layers.forEach((layer) => {
-      this.map.removeLayer(layer);
-    });
+    if (this.maskLayer) {
+      this.map.removeLayer(this.maskLayer);
+      this.maskLayer = null;
+    }
   }
 }

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

@@ -7,15 +7,17 @@
         ref="map2Ref"
         :active-map="activeMap"
         :point-type="pointType"
+        v-model:showMask="showMask"
       />
       <Map
         v-else
         ref="mapRef"
         :active-map="activeMap"
         :point-type="pointType"
+        v-model:showMask="showMask"
         @handle-show-video="handleShowVideo"
         @handle-show-warehouse="handleShowWarehouse"
-        @handleShowPeople="handleShowPeople"
+        @handle-show-people="handleShowPeople"
       />
       <!--左侧菜单-->
       <LeftMenu
@@ -35,7 +37,7 @@
       <GridPointRainfall v-if="showRainfall" v-model="showRainfall" :location="location" />
       <MaterialDetail v-if="showWarehouse" v-model="showWarehouse" :warehouse-data="warehouseData" />
       <CommunicationSupport v-if="communicationSupport.show" @close="handleHideCommunicationSupport" />
-      <EmergencyCrew v-if="showPeople" v-model="showPeople" :id="teamId" />
+      <EmergencyCrew v-if="showPeople" :id="teamId" v-model="showPeople" />
     </div>
   </div>
 </template>
@@ -64,6 +66,7 @@ let map;
 let mapRef = ref(null);
 let map2Ref = ref(null);
 let leftMenuRef = ref(null);
+let showMask = ref(false);
 //  vectorgraph satellite imageMap 废弃:logical satellite2 satellite3
 let activeMap = ref('satellite');
 // 附近视频菜单数据