riku
2025-03-14 5f44d21b3921abc88506a7ec46b3fe6f078664aa
新增走航融合功能(初版)
已修改10个文件
已添加2个文件
1861 ■■■■■ 文件已修改
README.md 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/gridApi.js 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/Legend.js 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/SatelliteGrid.js 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/styles/base.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/3dLayer.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/calculate.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/grid.js 410 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/map/line.js 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/realtimemode/RealtimeMode.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/underwaymix/UnderwayMixMode.vue 398 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -39,3 +39,228 @@
```sh
npm run lint
```
```
ff-ai-ep-underway-vue
├─ .env.development
├─ .env.development.jingan
├─ .env.production
├─ .env.production.jingan
├─ .eslintrc.cjs
├─ .prettierrc.json
├─ index.html
├─ jsconfig.json
├─ package-lock.json
├─ package.json
├─ public
│  â””─ favicon.ico
├─ README.md
├─ src
│  â”œâ”€ api
│  â”‚  â”œâ”€ deviceApi.js
│  â”‚  â”œâ”€ index.js
│  â”‚  â”œâ”€ missionApi.js
│  â”‚  â”œâ”€ monitorDataApi.js
│  â”‚  â”œâ”€ sceneInfoApi.js
│  â”‚  â””─ thirdPartyDataApi.js
│  â”œâ”€ App.vue
│  â”œâ”€ assets
│  â”‚  â”œâ”€ 3dmap.css
│  â”‚  â”œâ”€ base.css
│  â”‚  â”œâ”€ border.css
│  â”‚  â”œâ”€ common-style.css
│  â”‚  â”œâ”€ logo.svg
│  â”‚  â”œâ”€ main.css
│  â”‚  â””─ mipmap
│  â”‚     â”œâ”€ boat_driving.png
│  â”‚     â”œâ”€ border.png
│  â”‚     â”œâ”€ car_driving.png
│  â”‚     â”œâ”€ car_offline.png
│  â”‚     â”œâ”€ car_stop.png
│  â”‚     â”œâ”€ company.png
│  â”‚     â”œâ”€ c_level_0.png
│  â”‚     â”œâ”€ c_level_1.png
│  â”‚     â”œâ”€ c_level_2.png
│  â”‚     â”œâ”€ c_level_no.png
│  â”‚     â”œâ”€ data_chart.png
│  â”‚     â”œâ”€ device.png
│  â”‚     â”œâ”€ dust.png
│  â”‚     â”œâ”€ ic_down_white.png
│  â”‚     â”œâ”€ ic_up_white.png
│  â”‚     â”œâ”€ location.png
│  â”‚     â”œâ”€ oil_paint.png
│  â”‚     â”œâ”€ other_smell.png
│  â”‚     â”œâ”€ plastics.png
│  â”‚     â”œâ”€ pungent.png
│  â”‚     â”œâ”€ scene.png
│  â”‚     â”œâ”€ scene_1.png
│  â”‚     â”œâ”€ scene_15.png
│  â”‚     â”œâ”€ scene_16.png
│  â”‚     â”œâ”€ scene_17.png
│  â”‚     â”œâ”€ scene_18.png
│  â”‚     â”œâ”€ scene_19.png
│  â”‚     â”œâ”€ scene_20.png
│  â”‚     â”œâ”€ scene_4.png
│  â”‚     â”œâ”€ scene_5.png
│  â”‚     â”œâ”€ scene_6.png
│  â”‚     â”œâ”€ shrink_left.png
│  â”‚     â”œâ”€ shrink_right.png
│  â”‚     â”œâ”€ slider_handle.png
│  â”‚     â”œâ”€ stink.png
│  â”‚     â”œâ”€ title_bg.png
│  â”‚     â”œâ”€ underway-2.png
│  â”‚     â”œâ”€ underway-3.png
│  â”‚     â”œâ”€ underway.png
│  â”‚     â”œâ”€ å¾®ä¿¡å›¾ç‰‡_20210608110133.png
│  â”‚     â”œâ”€ å¾®ä¿¡å›¾ç‰‡_202106081101331.png
│  â”‚     â””─ å¾®ä¿¡å›¾ç‰‡_202106081101332.png
│  â”œâ”€ components
│  â”‚  â”œâ”€ animation
│  â”‚  â”‚  â”œâ”€ HistoricalTrajectory.vue
│  â”‚  â”‚  â””─ TrajectoryState.vue
│  â”‚  â”œâ”€ BaseCard.vue
│  â”‚  â”œâ”€ CardButton.vue
│  â”‚  â”œâ”€ CardDialog.vue
│  â”‚  â”œâ”€ chart
│  â”‚  â”‚  â”œâ”€ GaugeChart.vue
│  â”‚  â”‚  â”œâ”€ ProgressLineChart.vue
│  â”‚  â”‚  â””─ RealTimeLineChart.vue
│  â”‚  â”œâ”€ core
│  â”‚  â”‚  â”œâ”€ CoreHeader.vue
│  â”‚  â”‚  â””─ CoreMenu.vue
│  â”‚  â”œâ”€ device
│  â”‚  â”‚  â”œâ”€ DeviceCreate.vue
│  â”‚  â”‚  â””─ DeviceManage.vue
│  â”‚  â”œâ”€ map
│  â”‚  â”‚  â”œâ”€ BaseMap.vue
│  â”‚  â”‚  â”œâ”€ ConfigManage.vue
│  â”‚  â”‚  â”œâ”€ MapLocation.vue
│  â”‚  â”‚  â”œâ”€ MapScene.vue
│  â”‚  â”‚  â””─ MapToolbox.vue
│  â”‚  â”œâ”€ MessageBox.vue
│  â”‚  â”œâ”€ mission
│  â”‚  â”‚  â”œâ”€ MissionEdit.vue
│  â”‚  â”‚  â”œâ”€ MissionImport.vue
│  â”‚  â”‚  â””─ MissionManage.vue
│  â”‚  â”œâ”€ monitor
│  â”‚  â”‚  â”œâ”€ DataSummary.vue
│  â”‚  â”‚  â”œâ”€ DataTable.vue
│  â”‚  â”‚  â”œâ”€ FactorCheckbox.vue
│  â”‚  â”‚  â”œâ”€ FactorLegend.vue
│  â”‚  â”‚  â”œâ”€ FactorRadio.vue
│  â”‚  â”‚  â”œâ”€ FactorTrend.vue
│  â”‚  â”‚  â”œâ”€ VehicleData.vue
│  â”‚  â”‚  â”œâ”€ WeatherData-copy.vue
│  â”‚  â”‚  â””─ WeatherData.vue
│  â”‚  â”œâ”€ scene
│  â”‚  â”‚  â””─ SceneSearch.vue
│  â”‚  â”œâ”€ search
│  â”‚  â”‚  â”œâ”€ OptionDevice.vue
│  â”‚  â”‚  â”œâ”€ OptionLocation.vue
│  â”‚  â”‚  â”œâ”€ OptionLocation2.vue
│  â”‚  â”‚  â”œâ”€ OptionMission.vue
│  â”‚  â”‚  â”œâ”€ OptionTime.vue
│  â”‚  â”‚  â”œâ”€ OptionType.vue
│  â”‚  â”‚  â””─ SearchBar.vue
│  â”‚  â””─ SliderBar.vue
│  â”œâ”€ components.d.ts
│  â”œâ”€ composables
│  â”‚  â”œâ”€ defaultFactorType.js
│  â”‚  â”œâ”€ fetchData.js
│  â”‚  â”œâ”€ formConfirm.js
│  â”‚  â””─ messageBox.js
│  â”œâ”€ constant
│  â”‚  â”œâ”€ checkbox-options
│  â”‚  â”‚  â”œâ”€ options-jingan.js
│  â”‚  â”‚  â””─ options.js
│  â”‚  â”œâ”€ checkbox-options.js
│  â”‚  â”œâ”€ device-type.js
│  â”‚  â”œâ”€ factor-name.js
│  â”‚  â”œâ”€ factor-unit.js
│  â”‚  â”œâ”€ location.js
│  â”‚  â”œâ”€ radio-options
│  â”‚  â”‚  â”œâ”€ options-jingan.js
│  â”‚  â”‚  â””─ options.js
│  â”‚  â”œâ”€ radio-options.js
│  â”‚  â”œâ”€ scene-types
│  â”‚  â”‚  â”œâ”€ options-jingan.js
│  â”‚  â”‚  â””─ options.js
│  â”‚  â”œâ”€ scene-types.js
│  â”‚  â””─ wind-dir.js
│  â”œâ”€ lib
│  â”‚  â”œâ”€ jquery-3.5.1.min.js
│  â”‚  â”œâ”€ jquery.soap.js
│  â”‚  â””─ jquery.xml2json.js
│  â”œâ”€ main.js
│  â”œâ”€ model
│  â”‚  â”œâ”€ Factor.js
│  â”‚  â”œâ”€ FactorDatas.js
│  â”‚  â”œâ”€ FrameAnimation.js
│  â”‚  â””─ Legend.js
│  â”œâ”€ router
│  â”‚  â””─ index.js
│  â”œâ”€ stores
│  â”‚  â”œâ”€ device.js
│  â”‚  â”œâ”€ map-animation.js
│  â”‚  â”œâ”€ mission.js
│  â”‚  â”œâ”€ scene.js
│  â”‚  â””─ toolbox.js
│  â”œâ”€ styles
│  â”‚  â”œâ”€ base.scss
│  â”‚  â”œâ”€ elementUI.scss
│  â”‚  â””─ index.scss
│  â”œâ”€ test.xml
│  â”œâ”€ utils
│  â”‚  â”œâ”€ chart
│  â”‚  â”‚  â””─ chart-option.js
│  â”‚  â”œâ”€ color.js
│  â”‚  â”œâ”€ expand
│  â”‚  â”‚  â””─ expand.js
│  â”‚  â”œâ”€ factor
│  â”‚  â”‚  â””─ data.js
│  â”‚  â”œâ”€ file.js
│  â”‚  â”œâ”€ map
│  â”‚  â”‚  â”œâ”€ 3dLayer.js
│  â”‚  â”‚  â”œâ”€ animation.js
│  â”‚  â”‚  â”œâ”€ calculate.js
│  â”‚  â”‚  â”œâ”€ dialog.js
│  â”‚  â”‚  â”œâ”€ grid.js
│  â”‚  â”‚  â”œâ”€ index.js
│  â”‚  â”‚  â”œâ”€ index_old.js
│  â”‚  â”‚  â”œâ”€ line.js
│  â”‚  â”‚  â”œâ”€ marks.js
│  â”‚  â”‚  â”œâ”€ sector.js
│  â”‚  â”‚  â”œâ”€ security.js
│  â”‚  â”‚  â”œâ”€ toolbox.js
│  â”‚  â”‚  â””─ util.js
│  â”‚  â””─ number.js
│  â””─ views
│     â”œâ”€ electricitymode
│     â”‚  â””─ ElectricityMode.vue
│     â”œâ”€ gridmonitor
│     â”‚  â””─ GridMode.vue
│     â”œâ”€ historymode
│     â”‚  â”œâ”€ component
│     â”‚  â”‚  â”œâ”€ DataSheet.vue
│     â”‚  â”‚  â””─ TrendAnalysis.vue
│     â”‚  â”œâ”€ HistoryMode.vue
│     â”‚  â””─ HistoryMode2.vue
│     â”œâ”€ HomePage.vue
│     â”œâ”€ LoginPage.vue
│     â”œâ”€ LoginPage_Backup.vue
│     â”œâ”€ realtimemode
│     â”‚  â”œâ”€ component
│     â”‚  â”‚  â”œâ”€ DashBoard.vue
│     â”‚  â”‚  â”œâ”€ DeviceChange.vue
│     â”‚  â”‚  â””─ RealTimeTrend.vue
│     â”‚  â””─ RealtimeMode.vue
│     â”œâ”€ riskmodel
│     â”‚  â””─ RiskMode.vue
│     â””─ underwaymix
│        â””─ UnderwayMixMode.vue
├─ test
│  â””─ grid_test_data.js
├─ vite.config.js
└─ vitest.config.js
```
src/api/gridApi.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,152 @@
import { $http } from './index';
import { Base64 } from 'js-base64';
/**
 * å«æ˜Ÿé¥æµ‹ç½‘格相关接口API
 */
export default {
  fetchGridGroup(area, type, page, perPage) {
    return $http
      .post(`air/satellite/grid/group`, area, {
        params: {
          type,
          page: page,
          per_page: perPage
        }
      })
      .then((res) => res.data);
  },
  fetchGridCell(groupId) {
    return $http
      .get(`air/satellite/grid/cell`, {
        params: {
          groupId
        }
      })
      .then((res) => res.data);
  },
  /**
   * èŽ·å–ç½‘æ ¼ç»„ä¸‹çš„é¥æµ‹æ•°æ®
   * @param {*} groupId
   * @param {*} dataTime
   * @param {number} type é¥æµ‹æ•°æ®ç±»åž‹ï¼Œ0:原始卫星数据,1:融合数据
   * @returns
   */
  fetchGridData(groupId, dataTime, type) {
    return $http
      .get(`air/satellite/grid/data`, {
        params: {
          groupId,
          dataTime,
          type
        }
      })
      .then((res) => res.data);
  },
  // /**
  //  * èŽ·å–ç½‘æ ¼ç»„ä¸‹çš„é¥æµ‹aod
  //  * @param {*} groupId
  //  * @param {*} dataTime
  //  * @returns
  //  */
  // fetchGridAod(groupId, dataTime) {
  //   return $http
  //     .get(`air/satellite/grid/aod`, {
  //       params: {
  //         groupId,
  //         dataTime
  //       }
  //     })
  //     .then((res) => res.data);
  // },
  fetchGridDataDetail(dataId, groupId, cellId) {
    return $http
      .get(`air/satellite/grid/data/detail`, {
        params: {
          dataId,
          groupId,
          cellId
        }
      })
      .then((res) => res.data);
  },
  createGridDataAndDataDetail(groupId, dataTime, dataDetailList) {
    return $http
      .post(`air/satellite/grid/data/create`, {
        params: {
          groupId,
          dataTime
        },
        data: dataDetailList
      })
      .then((res) => res.data);
  },
  downloadTemplate() {
    return $http
      .get(`air/satellite/import/grid/data/download/template`, {
        responseType: 'blob'
      })
      .then((res) => {
        const name = Base64.decode(res.headers.get('fileName'));
        const blob = new Blob([res.data], {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        });
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = name;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
      });
  },
  importData(dataForm) {
    return $http
      .post(`air/satellite/import/grid/data`, dataForm)
      .then((res) => res.data);
  },
  downloadAODTemplate() {
    return $http
      .get(`air/satellite/import/grid/aod/download/template`, {
        responseType: 'blob'
      })
      .then((res) => {
        const name = Base64.decode(res.headers.get('fileName'));
        const blob = new Blob([res.data], {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        });
        const url = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = name;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(url);
      });
  },
  importAOD(dataForm) {
    return $http
      .post(`air/satellite/import/grid/aod`, dataForm)
      .then((res) => res.data);
  },
  mixGridData(dataIdList) {
    return $http
      .post(`air/satellite/grid/data/mix`, dataIdList)
      .then((res) => res.data);
  },
  buildUnderwayProduct(missionCode, groupId) {
    return $http.get(`air/satellite/import/grid/aod/download/template`, {
      responseType: 'blob'
    });
  }
};
src/components.d.ts
@@ -33,6 +33,7 @@
    ElFormItem: typeof import('element-plus/es')['ElFormItem']
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElPopover: typeof import('element-plus/es')['ElPopover']
src/model/Legend.js
@@ -142,21 +142,13 @@
    ]
  },
  // _custom: [
  //   [0.05, 0.9, 0.03, 0.75],
  //   [0.3, 0.65, 0.02, 0.75],
  //   [0.87, 0.92, 0.03, 0.75],
  //   [0.8, 0.67, 0.04, 0.75],
  //   [0.92, 0.28, 0.07, 0.75],
  //   [0.6, 0.05, 0.05, 0.75]
  // ],
  _custom: [
    [0, 0.89, 0, 0.75],
    [1, 1, 0, 0.75],
    [1, 0.49, 0, 0.75],
    [1, 0, 0, 0.75],
    [0.6, 0, 0.3, 0.75],
    [0.49, 0, 0.14, 0.75]
    [0.05, 0.9, 0.03, 0.75],
    [0.3, 0.65, 0.02, 0.75],
    [0.87, 0.92, 0.03, 0.75],
    [0.8, 0.67, 0.04, 0.75],
    [0.92, 0.28, 0.07, 0.75],
    [0.96, 0.05, 0.05, 0.75]
  ],
  getStandardRange: function (name) {
@@ -181,6 +173,9 @@
  },
  getColor: function (name, type, data, min, max) {
    if (!data) {
      return [0, 0, 0, 0];
    }
    if (type == this.S_TYPE) {
      return this.getStandardColor(name, data);
    } else {
@@ -247,6 +242,54 @@
    return colors[selected];
  },
  getStandardColorAndNext: function (name, data) {
    if (!data) {
      return {
        color: [0, 0, 0, 0],
        nextColor: [0, 0, 0, 0],
        range: 0,
        nextRange: 0
      };
    }
    let range = this._legend_r[name];
    let colors = this._legend_c[name];
    if (range == undefined) {
      range = this._legend_r['PM25'];
      colors = this._legend_c['PM25'];
    }
    let selected = undefined;
    for (let i = 0; i < range.length; i++) {
      const d = range[i];
      let d1 = d;
      if (name == 'CO') {
        d1 *= 1000;
      }
      if (data >= d1) {
        selected = i;
      } else {
        break;
      }
    }
    // é¿å…ä¸‹æ ‡è¶Šç•Œ
    if (selected >= colors.length) {
      selected = colors.length - 1;
    }
    let nextIndex = selected + 1;
    if (nextIndex >= colors.length) {
      nextIndex = colors.length - 1;
    }
    return {
      color: colors[selected],
      nextColor: colors[nextIndex],
      range: range[selected],
      nextRange: range[nextIndex]
    };
  },
  getCustomColor: function (data, min, max) {
    var per = (max - min) / this._custom.length;
    var i = parseInt((data - min) / per);
@@ -254,6 +297,35 @@
      i = this._custom.length - 1;
    }
    return this._custom[i];
  },
  getCustomColorAndNext: function (data, min, max) {
    if (!data) {
      return {
        color: [0, 0, 0, 0],
        nextColor: [0, 0, 0, 0],
        range: 0,
        nextRange: 0
      };
    }
    // å°†æ•°æ®æŒ‰ç…§é¢œè‰²æ•°é‡åˆ†éš”,求出每一段的数据偏移量
    var per = (max - min) / (this._custom.length - 1);
    // è®¡ç®—当前数据所在的分段范围
    var i = parseInt((data - min) / per);
    // å¦‚果是最大值,同样分割到最后一段
    if (i == this._custom.length - 1) i--;
    var range = min + i * per;
    let nextIndex = i + 1;
    let nextRange = min + nextIndex * per;
    return {
      color: this._custom[i],
      nextColor: this._custom[nextIndex],
      range,
      nextRange
    };
  }
};
src/model/SatelliteGrid.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,488 @@
import calculate from '@/utils/map/calculate';
import gridMapUtil from '@/utils/map/grid';
import { map, onMapMounted } from '@/utils/map/index_old';
import { useCloned } from '@vueuse/core';
/**
 * å«æ˜Ÿé¥æµ‹ç½‘格管理
 */
export class SatelliteGrid {
  constructor(name) {
    this.name = name;
  }
  // åœ°å›¾ç½‘格相关对象
  mapViews;
  mapViewsList = [];
  mapViewsMap = new Map();
  districtPolygon;
  events = new Map();
  // ç»˜åˆ¶åŒºåŽ¿è¾¹ç•Œ
  drawDistrict(districtName, isNew) {
    onMapMounted(() => {
      if (this.districtPolygon && !isNew) {
        map.remove(this.districtPolygon);
        map.add(this.districtPolygon);
      } else {
        // eslint-disable-next-line no-undef
        var district = new AMap.DistrictSearch({
          extensions: 'all', //返回行政区边界坐标等具体信息
          level: 'district' //设置查询行政区级别为区
        });
        district.search(districtName, (status, result) => {
          var bounds = result.districtList[0].boundaries; //获取朝阳区的边界信息
          if (bounds) {
            for (var i = 0; i < bounds.length; i++) {
              //生成行政区划 polygon
              // eslint-disable-next-line no-undef
              this.districtPolygon = new AMap.Polygon({
                map: map, //显示该覆盖物的地图对象
                strokeWeight: 2, //轮廓线宽度
                path: bounds[i], //多边形轮廓线的节点坐标数组
                fillOpacity: 0, //多边形填充透明度
                fillColor: '#CCF3FF', //多边形填充颜色
                // strokeColor: '#ffffff' //线条颜色
                strokeColor: '#0552f7', //线条颜色
                zIndex: 9
              });
            }
            map.setFitView(); //将覆盖物调整到合适视野
          }
        });
      }
    });
  }
  clearAll(mapViews) {
    if (mapViews) {
      if (typeof mapViews.gridViews === 'object') {
        map.remove(mapViews.gridViews);
      }
    }
    this.clearText(mapViews);
  }
  clearText(mapViews) {
    if (mapViews) {
      if (typeof mapViews.dataTxt === 'object') {
        map.remove(mapViews.dataTxt);
      }
      if (typeof mapViews.dataLayer === 'object') {
        map.remove(mapViews.dataLayer);
      }
      if (typeof mapViews.rankTxt === 'object') {
        map.remove(mapViews.rankTxt);
      }
      if (typeof mapViews.rankLayer === 'object') {
        map.remove(mapViews.rankLayer);
      }
    }
  }
  firstEvent;
  // ç»˜åˆ¶ç½‘格线
  drawPolyline(gridInfo, event) {
    this.firstEvent = event;
    // ç»˜åˆ¶ç½‘æ ¼
    const points = gridInfo.map((v) => {
      return calculate.wgs84_To_Gcj02(v.longitude, v.latitude);
      // return [v.longitude, v.latitude];
    });
    // const gridPoints = gridMapUtil.parseGridPoint(points);
    // console.log('gridPoints:', gridPoints);
    const gridPoints = gridInfo.map((v, i) => {
      return {
        path: [
          calculate.wgs84_To_Gcj02(v.point1Lon, v.point1Lat),
          calculate.wgs84_To_Gcj02(v.point2Lon, v.point2Lat),
          calculate.wgs84_To_Gcj02(v.point3Lon, v.point3Lat),
          calculate.wgs84_To_Gcj02(v.point4Lon, v.point4Lat)
          // [v.point1Lon, v.point1Lat],
          // [v.point2Lon, v.point2Lat],
          // [v.point3Lon, v.point3Lat],
          // [v.point4Lon, v.point4Lat]
        ]
          // eslint-disable-next-line no-undef
          .map((d) => new AMap.LngLat(d[0], d[1])),
        extData: {
          centerPoint: points[i],
          // gridPoints,
          gridCell: v
        }
      };
    });
    const gridViews = gridMapUtil.drawPolylines({ points: gridPoints, event });
    return { gridViews, gridPoints, points };
  }
  // ç»˜åˆ¶ç›‘测数据值
  drawDataText(points, gridDataDetail, textViews, isCustomColor, useColor) {
    const data = gridDataDetail.map((v, i) => {
      return {
        lnglat_GD: points[i],
        // data: v.pm25 ? (v.pm25 + 'μg/m³') : ''
        data: v.pm25 ? v.pm25 : ''
      };
    });
    // return gridMapUtil.drawGridTextLabel(data, textViews, labelsLayer, 'bottom');
    return gridMapUtil.drawGridText({
      points: data,
      textViews,
      anchor: 'top-center',
      type: 'data',
      isCustomColor,
      useColor
    });
  }
  // ç»˜åˆ¶ç›‘测数据排名文本
  drawRankText(points, gridDataDetail, textViews, labelsLayer) {
    const data = gridDataDetail.map((v, i) => {
      return {
        lnglat_GD: points[i],
        // data: v.pm25 ? ('排名: ' + v.rank) : ''
        data: v.pm25 ? v.rank : ''
      };
    });
    // return gridMapUtil.drawGridTextLabel(data, textViews, labelsLayer, 'top');
    return gridMapUtil.drawGridText({
      points: data,
      textViews,
      anchor: 'bottom-center',
      type: 'rank'
    });
  }
  // ç»˜åˆ¶ç›‘测数据值对应网格颜色
  drawColor({
    gridViews,
    points,
    gridDataDetail,
    lastGridViews,
    opacity,
    zIndex,
    customColor
  }) {
    // æ ¹æ®æ•°æ®ç­›é€‰æœ‰æ•°æ®çš„网格
    const res = [];
    // ä»¥åŠå¯¹åº”的中心点坐标
    const pointsRes = [];
    gridDataDetail.forEach((d) => {
      // æ ¹æ®æ•°æ®å…³è”的网格编号,找到对应网格
      const cellId = d.cellId;
      if (cellId > gridViews.length) {
        throw Error(
          '遥测数据的网格索引编号超出网格组范围,数据和网格组可能不对应'
        );
      }
      res.push(gridViews[cellId - 1]);
      pointsRes.push(points[cellId - 1]);
    });
    // æ ¹æ®ç»˜åˆ¶é¢œè‰²æ–¹å¼ç»˜åˆ¶ç½‘æ ¼
    let resGridViews;
    if (customColor) {
      resGridViews = gridMapUtil.drawGridColorCustom(
        res,
        gridDataDetail,
        opacity,
        zIndex
      );
    } else {
      resGridViews = gridMapUtil.drawGridColor(
        res,
        gridDataDetail,
        'PM25',
        opacity,
        zIndex
      );
    }
    if (lastGridViews) {
      map.remove(lastGridViews);
    }
    map.add(resGridViews);
    // map.setFitView(resGridViews);
    return { resGridViews, pointsRes };
  }
  // å«æ˜Ÿç½‘格配置准备
  gridPrepare(gridInfo, event) {
    // clearText(mapViews);
    this.clearAll(this.mapViews);
    this.mapViews = this.drawPolyline(gridInfo, event);
  }
  // ç»˜åˆ¶ç½‘格遥感数据值和网格颜色
  drawGrid({
    gridDataDetail,
    useCustomColor,
    opacity,
    zIndex,
    showDataTxt,
    showRankTxt,
    useDataTxtColor,
    mapViews
  }) {
    const _mapViews = mapViews ? mapViews : this.mapViews;
    // SatelliteProxy.clearText(mapViews);
    const { resGridViews, pointsRes } = this.drawColor({
      gridViews: _mapViews.gridViews,
      points: _mapViews.points,
      gridDataDetail: gridDataDetail,
      lastGridViews: _mapViews.lastGridViews,
      customColor: useCustomColor,
      opacity: opacity,
      zIndex: zIndex
    });
    _mapViews.lastGridViews = resGridViews;
    _mapViews.lastPoints = pointsRes;
    // æ•°æ®æ ‡è®°
    const { textViews: dataTxt, labelsLayer: dataLayer } = this.drawDataText(
      _mapViews.lastPoints,
      gridDataDetail,
      _mapViews.dataTxt,
      useCustomColor,
      useDataTxtColor
    );
    _mapViews.dataTxt = dataTxt;
    _mapViews.dataLayer = dataLayer;
    const { textViews: rankTxt, labelsLayer: rankLayer } = this.drawRankText(
      _mapViews.lastPoints,
      gridDataDetail,
      _mapViews.rankTxt,
      _mapViews.rankLayer
    );
    _mapViews.rankTxt = rankTxt;
    _mapViews.rankLayer = rankLayer;
    if (showDataTxt) {
      map.add(_mapViews.dataTxt);
    }
    if (showRankTxt) {
      map.add(_mapViews.rankTxt);
    }
  }
  //
  drawGrids({
    gridDataDetailList,
    useCustomColor,
    opacity,
    zIndex,
    showDataTxt,
    showRankTxt
  }) {
    if (this.mapViewsList.length < gridDataDetailList.length) {
      let index = this.mapViewsList.length;
      while (index < gridDataDetailList.length) {
        const newMapViews = {
          gridViews: gridMapUtil.drawPolylines({
            points: this.mapViews.gridPoints,
            event: this.firstEvent
          }),
          gridPoints: JSON.parse(JSON.stringify(this.mapViews.gridPoints)),
          points: JSON.parse(JSON.stringify(this.mapViews.points))
        };
        this.mapViewsList.push(newMapViews);
      }
    }
    this.mapViewsList.forEach((m, i) => {
      this.drawGrid({
        gridDataDetail: gridDataDetailList[i],
        useCustomColor,
        opacity,
        zIndex,
        showDataTxt,
        showRankTxt,
        mapViews: m
      });
    });
  }
  drawNewGrid({
    gridDataDetail,
    useCustomColor,
    opacity,
    zIndex,
    showDataTxt,
    showRankTxt
  }) {
    const newMapViews = {
      gridViews: gridMapUtil.drawPolylines({
        points: this.mapViews.gridPoints,
        event: this.firstEvent
      }),
      gridPoints: JSON.parse(JSON.stringify(this.mapViews.gridPoints)),
      points: JSON.parse(JSON.stringify(this.mapViews.points))
    };
    this.mapViewsList.push(newMapViews);
    this.drawGrid({
      gridDataDetail,
      useCustomColor,
      opacity,
      zIndex,
      showDataTxt,
      showRankTxt,
      mapViews: newMapViews
    });
  }
  drawTagGrid({
    tag,
    gridDataDetail,
    useCustomColor,
    opacity,
    zIndex,
    showDataTxt,
    showRankTxt
  }) {
    if (!this.mapViewsMap.has(tag)) {
      const newMapViews = {
        gridViews: gridMapUtil.drawPolylines({
          points: this.mapViews.gridPoints,
          event: this.firstEvent
        }),
        gridPoints: JSON.parse(JSON.stringify(this.mapViews.gridPoints)),
        points: JSON.parse(JSON.stringify(this.mapViews.points))
      };
      this.mapViewsMap.set(tag, newMapViews);
    }
    const _mapViews = this.mapViewsMap.get(tag);
    this.drawGrid({
      gridDataDetail,
      useCustomColor,
      opacity,
      zIndex,
      showDataTxt,
      showRankTxt,
      mapViews: _mapViews
    });
  }
  // è°ƒæ•´å„类地图覆盖物的可见性
  changeVisibility({ tag, showGridViews, showDataTxt, showRankTxt }) {
    let _mapViewsList = [];
    if (this.mapViews) {
      _mapViewsList.push(this.mapViews);
    }
    if (tag && this.mapViewsMap.has(tag)) {
      _mapViewsList.push(this.mapViewsMap.get(tag));
    } else {
      this.mapViewsMap.forEach((v) => {
        _mapViewsList.push(v);
      });
    }
    if (showGridViews != undefined) {
      if (showGridViews) {
        // map.add(this.mapViews.lastGridViews);
        _mapViewsList.forEach((v) => {
          if (v.lastGridViews) map.add(v.lastGridViews);
        });
      } else {
        // map.remove(this.mapViews.lastGridViews);
        _mapViewsList.forEach((v) => {
          if (v.lastGridViews) map.remove(v.lastGridViews);
        });
      }
    }
    if (showDataTxt != undefined) {
      if (showDataTxt) {
        // map.add(this.mapViews.dataTxt);
        _mapViewsList.forEach((v) => {
          if (v.dataTxt) map.add(v.dataTxt);
        });
      } else {
        // map.remove(this.mapViews.dataTxt);
        _mapViewsList.forEach((v) => {
          if (v.dataTxt) map.remove(v.dataTxt);
        });
      }
    }
    if (showRankTxt != undefined) {
      if (showRankTxt) {
        // map.add(this.mapViews.rankTxt);
        _mapViewsList.forEach((v) => {
          if (v.rankTxt) map.add(v.rankTxt);
        });
      } else {
        // map.remove(this.mapViews.rankTxt);
        _mapViewsList.forEach((v) => {
          if (v.rankTxt) map.remove(v.rankTxt);
        });
      }
    }
  }
  changeGridOpacity({ tag, isOpacity, opacityValue }) {
    let _mapViewsList = [];
    if (this.mapViews) {
      _mapViewsList.push(this.mapViews);
    }
    if (tag && this.mapViewsMap.has(tag)) {
      _mapViewsList.push(this.mapViewsMap.get(tag));
    } else {
      this.mapViewsMap.forEach((v) => {
        _mapViewsList.push(v);
      });
    }
    _mapViewsList.forEach((v) => {
      if (v.lastGridViews) {
        v.lastGridViews.forEach((e) => {
          e.setOptions({
            fillOpacity:
              typeof opacityValue === 'number'
                ? opacityValue
                : isOpacity
                  ? 0.1
                  : 0.7
          });
        });
      }
    });
  }
  setGridEvent(name, event) {
    if (!this.events.has(name)) {
      this.events.set(name, []);
    }
    const list = this.events.get(name);
    if (list.length > 0) {
      const lastEvent = list[list.length - 1];
      this.mapViews.gridViews.forEach((polygon) => {
        polygon.off(name, lastEvent);
      });
    }
    this.events.get(name).push(event);
    this.mapViews.gridViews.forEach((polygon) => {
      polygon.on(name, event);
    });
  }
  goBackGridEvent(name) {
    if (this.events.has(name)) {
      const eventList = this.events.get(name);
      //先移除原有的事件
      const lastEvent = eventList.pop();
      this.mapViews.gridViews.forEach((polygon) => {
        polygon.off(name, lastEvent);
      });
      //获取上一个事件
      const event = eventList.pop();
      this.mapViews.gridViews.forEach((polygon) => {
        polygon.on(name, event);
      });
    }
  }
}
src/styles/base.scss
@@ -71,7 +71,7 @@
  default: var(--el-component-size-default),
  large: var(--el-component-size-large)
);
$ws: (20, 40, 50, 60, 80, 100, 120, 150, 300);
$ws: (20, 40, 50, 60, 80, 100, 120, 150, 200, 250, 300);
@each $name, $value in $csize {
  .w-#{$name} {
    width: #{$value};
src/utils/map/3dLayer.js
@@ -168,6 +168,7 @@
export default {
  clear() {
    map.off('zoomend', onMapZoom);
    if (_cylinder != undefined) {
      object3Dlayer.remove(_cylinder);
    }
src/utils/map/calculate.js
@@ -133,6 +133,57 @@
  },
  /**
   * èŽ·å–ä¸¤ä¸ªç»çº¬åº¦ä¹‹é—´çš„è§’åº¦ï¼ˆ0度-360度)
   */
  getAngle(lng_a, lat_a, lng_b, lat_b) {
    var a = ((90 - lat_b) * Math.PI) / 180;
    var b = ((90 - lat_a) * Math.PI) / 180;
    var AOC_BOC = ((lng_b - lng_a) * Math.PI) / 180;
    var cosc =
      Math.cos(a) * Math.cos(b) + Math.sin(a) * Math.sin(b) * Math.cos(AOC_BOC);
    var sinc = Math.sqrt(1 - cosc * cosc);
    var sinA = (Math.sin(a) * Math.sin(AOC_BOC)) / sinc;
    var A = (Math.asin(sinA) * 180) / Math.PI;
    var res = 0;
    if (lng_b > lng_a && lat_b > lat_a) res = A;
    else if (lng_b > lng_a && lat_b < lat_a) res = 180 - A;
    else if (lng_b < lng_a && lat_b < lat_a) res = 180 - A;
    else if (lng_b < lng_a && lat_b > lat_a) res = 360 + A;
    else if (lng_b > lng_a && lat_b == lat_a) res = 90;
    else if (lng_b < lng_a && lat_b == lat_a) res = 270;
    else if (lng_b == lng_a && lat_b > lat_a) res = 0;
    else if (lng_b == lng_a && lat_b < lat_a) res = 180;
    return res;
  },
  /**
   * èŽ·å–ä¸¤ç»çº¬åº¦é—´çš„è·ç¦»
   */
  getDistance(lng1, lat1, lng2, lat2) {
    lat1 = lat1 || 0;
    lng1 = lng1 || 0;
    lat2 = lat2 || 0;
    lng2 = lng2 || 0;
    var rad1 = (lat1 * Math.PI) / 180.0;
    var rad2 = (lat2 * Math.PI) / 180.0;
    var a = rad1 - rad2;
    var b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0;
    var r = 6378137;
    var distance =
      r *
      2 *
      Math.asin(
        Math.sqrt(
          Math.pow(Math.sin(a / 2), 2) +
            Math.cos(rad1) * Math.cos(rad2) * Math.pow(Math.sin(b / 2), 2)
        )
      );
    return distance;
  },
  /**
   * å°†äºŒç»´æ•°ç»„形式的坐标点数组转换为高德地图中 LngLat ç±»
   * @param {*} lnglats
   * @returns
src/utils/map/grid.js
@@ -2,51 +2,419 @@
 * ç½‘格绘制
 */
import { map } from './index_old';
import calculate from './calculate';
import { Legend } from '@/model/Legend';
import { getHexColor, getColorBetweenTwoColors } from '../color';
/**
 * è§’度增减,确保角度处于0 - 360度之间
 * @param {number} angle åŽŸè§’åº¦
 * @param {number} offset åç§»é‡
 */
function plusAngle(angle, offset) {
  const result = angle + offset;
  if (result > 360) {
    return result - 360;
  } else if (result < 0) {
    return result + 360;
  } else {
    return result;
  }
}
/**
 * æ ¹æ®ç½‘格中心点,生成正方形网格4个顶点的坐标
 * @param {Array} points ç½‘格中心点经纬度数组
 */
function parseGridPoint(points) {
  if (points.length < 2) throw new Error('坐标点数量小于2');
  const p1 = points[0];
  const p2 = points[1];
  // ä¸¤ä¸­å¿ƒç‚¹é—´çš„角度
  const angle = calculate.getAngle(p1[0], p1[1], p2[0], p2[1]);
  // const angle = calculate.bearing(
  //   { latitude: p1[0], longitude: p1[1] },
  //   { latitude: p2[0], longitude: p2[1] }
  // );
  // ä¸¤ä¸­å¿ƒç‚¹é—´çš„距离
  const dis = calculate.getDistance(p1[0], p1[1], p2[0], p2[1]);
  // ç½‘格正方形对角线的一半长度
  const halfDiagonal = Math.sqrt((dis / 2) * (dis / 2) * 2);
  // è®¡ç®—首个正方形各顶点相对于中心点的角度,得到正方形各顶点的坐标
  const angle1 = plusAngle(angle, 45);
  const gp1 = calculate.getLatLon(p1, halfDiagonal, angle1);
  const angle2 = plusAngle(angle1, 90);
  const gp2 = calculate.getLatLon(p1, halfDiagonal, angle2);
  const angle3 = plusAngle(angle2, 90);
  const gp3 = calculate.getLatLon(p1, halfDiagonal, angle3);
  const angle4 = plusAngle(angle3, 90);
  const gp4 = calculate.getLatLon(p1, halfDiagonal, angle4);
  // è®¡ç®—4个顶点分别与中心点的经纬度差值
  const diff = {
    diff1: {
      dx: gp1[0] - p1[0],
      dy: gp1[1] - p1[1]
    },
    diff2: {
      dx: gp2[0] - p1[0],
      dy: gp2[1] - p1[1]
    },
    diff3: {
      dx: gp3[0] - p1[0],
      dy: gp3[1] - p1[1]
    },
    diff4: {
      dx: gp4[0] - p1[0],
      dy: gp4[1] - p1[1]
    }
  };
  // å¾—到所有正方形网格的4个顶点信息
  return points.map((p) => {
    return [
      [p[0] + diff.diff1.dx, p[1] + diff.diff1.dy],
      [p[0] + diff.diff2.dx, p[1] + diff.diff2.dy],
      [p[0] + diff.diff3.dx, p[1] + diff.diff3.dy],
      [p[0] + diff.diff4.dx, p[1] + diff.diff4.dy]
    ];
  });
}
/**
 * æ–‡æœ¬æ ‡è®°
 * å¯ä¿®æ”¹position
 */
function textMaker({ position, text, anchor, type, color }) {
  let style = {};
  if (type == 'data') {
    style = {
      'font-size': '12px',
      'text-align': 'center',
      'font-weight': 600,
      color: color ? color : 'white',
      background: '#122b54a9',
      // background: 'white',
      'text-shadow': 'black 1px 1px 1px',
      border: '0px',
      'margin-top': '4px'
    };
  } else if (type == 'rank') {
    style = {
      'font-size': '14px',
      'text-align': 'center',
      color: color ? color : 'white',
      background: 'transparent',
      'text-shadow': 'black 2px 2px 2px',
      'border-radius': '2px',
      border: '1px solid #122b54a9',
      // border: '1px solid rgba(255, 255, 255, 0.62)',
      'margin-bottom': '4px'
    };
  }
  // eslint-disable-next-line no-undef
  return new AMap.Text({
    text: text,
    anchor,
    position: position,
    style: style
  });
}
/**
 * æµ·é‡æ–‡æœ¬æ ‡æ³¨
 */
function textLabelMarker(position, text, direction, style) {
  // eslint-disable-next-line no-undef
  return new AMap.LabelMarker({
    position: position,
    zooms: [10, 20],
    opacity: 1,
    zIndex: 2,
    // icon: {
    //   image: 'https://a.amap.com/jsapi_demos/static/images/poi-marker.png',
    //   anchor: 'bottom-center',
    //   size: [25, 34],
    //   clipOrigin: [459, 92],
    //   clipSize: [50, 68]
    // },
    text: {
      // æ³¨æ„å†…容格式必须是string
      content: text ? text + '' : '',
      direction: direction ? direction : 'center',
      style: {
        'border-radius': '.25rem',
        fontSize: 12,
        fillColor: '#fff',
        strokeColor: 'rgba(0, 0, 0, 1)',
        strokeWidth: 4,
        // backgroundColor: '#122b54a9',
        padding: [3, 10],
        // backgroundColor: 'yellow',
        borderColor: '#ccc',
        borderWidth: 30
      }
    }
  });
}
/**
 * è®¡ç®—每个网格颜色
 */
function calGridColor({ factorName, data, isCustomColor }) {
  let _colorList = [];
  if (isCustomColor) {
    var max, min;
    data.forEach((t) => {
      if (!t) return;
      if (!max || t > max) {
        max = t;
      }
      if (!min || t < min) {
        min = t;
      }
    });
    data.forEach((d) => {
      if (d) {
        // æ ¹æ®é¥æµ‹æ•°æ®è®¡ç®—网格颜色
        const { color, nextColor, range, nextRange } =
          Legend.getCustomColorAndNext(d, min, max);
        const ratio = (d - range) / (nextRange - range);
        const _color = getColorBetweenTwoColors(
          color.map((v) => v * 255),
          nextColor.map((v) => v * 255),
          ratio
        );
        _colorList.push(_color);
      } else {
        _colorList.push(undefined);
      }
    });
  } else {
    data.forEach((d) => {
      if (d) {
        // æ ¹æ®é¥æµ‹æ•°æ®è®¡ç®—网格颜色
        const { color, nextColor, range, nextRange } =
          Legend.getStandardColorAndNext(factorName, d);
        const ratio = (d - range) / (nextRange - range);
        const _color = getColorBetweenTwoColors(
          color.map((v) => v * 255),
          nextColor.map((v) => v * 255),
          ratio
        );
        _colorList.push(_color);
      } else {
        _colorList.push(undefined);
      }
    });
  }
  return _colorList;
}
export default {
  parseGridPoint,
  /**
   * ç»˜åˆ¶ç½‘格风险图
   * @param {*} points
   */
  drawRectangle: function (points) {
  drawRectangle(points) {
    const gridViews = [];
    points.forEach((p) => {
      const { lb, rt, c } = p;
      let pList = [lb, rt].map((v) => {
      // eslint-disable-next-line no-undef
      let pList = [lb, rt].map((v) => new AMap.LngLat(v[0], v[1]));
        return new AMap.LngLat(v[0], v[1]);
      });
      // eslint-disable-next-line no-undef
      var bounds = new AMap.Bounds(...pList);
      // eslint-disable-next-line no-undef
      var rectangle = new AMap.Rectangle({
        bounds: bounds,
        // strokeColor: '#ffffffff',
        strokeWeight: 0,
        strokeOpacity: 0,
        strokeColor: '#ffffffff',
        strokeWeight: 1,
        strokeOpacity: 1,
        // strokeStyle还支持 solid
        strokeStyle: 'solid',
        fillColor: '990D0D',
        fillColor: '#990D0D',
        fillOpacity: 0.8,
        cursor: 'pointer',
        zIndex: 50
      });
      // var text = new AMap.Text({
      //   text: p.value,
      //   anchor: 'center', // è®¾ç½®æ–‡æœ¬æ ‡è®°é”šç‚¹
      //   draggable: false,
      //   style: {
      //     'background-color': 'transparent',
      //     'border-width': 0,
      //     'text-align': 'center',
      //     'font-size': '12px',
      //     color: 'white'
      //   },
      //   position: c
      // });
      gridViews.push(rectangle);
      // that.textView.push(text);
    });
    map.add(gridViews);
    map.setFitView(gridViews);
  },
  /**
   * ç»˜åˆ¶ä¸€ç»„多边形
   * @param {Array} points ç½‘格坐标点数组
   * @param {Boolean} draw æ˜¯å¦åˆ›å»ºå®ŒæˆåŽåŒæ—¶ç»˜åˆ¶
   */
  drawPolylines({ points, draw, event }) {
    const gridViews = [];
    points.forEach((p) => {
      //创建多边形 Polygon å®žä¾‹
      // eslint-disable-next-line no-undef
      var polygon = new AMap.Polygon({
        path: p.path, //路径
        fillColor: '#fff', //多边形填充颜色
        strokeWeight: 1, //线条宽度,默认为 2
        strokeColor: 'white', //线条颜色
        fillOpacity: 0,
        extData: p.extData
      });
      if (typeof event === 'function') {
        event(polygon);
      }
      gridViews.push(polygon);
    });
    if (draw) {
      map.add(gridViews);
      map.setFitView(gridViews);
    }
    return gridViews;
  },
  drawGridText({ points, textViews, anchor, type, isCustomColor, useColor }) {
    let colorList = [];
    if (useColor) {
      colorList = calGridColor({
        factorName: 'PM25',
        data: points.map((p) => p.data),
        isCustomColor: isCustomColor
      });
    }
    if (textViews) {
      map.remove(textViews);
    }
    const _textViews = [];
    points.forEach((p, i) => {
      const m = textMaker({
        position: p.lnglat_GD,
        text: p.data,
        anchor,
        type,
        color: useColor ? colorList[i] : 'white'
      });
      _textViews.push(m);
    });
    // map.add(_textViews);
    return { textViews: _textViews };
  },
  drawGridTextLabel(points, textViews, labelsLayer, direction) {
    if (textViews) {
      points.forEach((p, i) => {
        textViews[i].setPosition(p.lnglat_GD);
        textViews[i].setText({
          content: p.data ? p.data + '' : ''
        });
      });
      return { textViews, labelsLayer };
    } else {
      // åˆ›å»ºä¸€ä¸ª LabelsLayer å®žä¾‹æ¥æ‰¿è½½ LabelMarker,[LabelsLayer æ–‡æ¡£](https://lbs.amap.com/api/jsapi-v2/documentation#labelslayer)
      // eslint-disable-next-line no-undef
      const labelsLayer = new AMap.LabelsLayer({
        zooms: [12, 20],
        zIndex: 1000,
        // å¼€å¯æ ‡æ³¨é¿è®©ï¼Œé»˜è®¤ä¸ºå¼€å¯ï¼Œv1.4.15 æ–°å¢žå±žæ€§
        collision: false,
        // å¼€å¯æ ‡æ³¨æ·¡å…¥åŠ¨ç”»ï¼Œé»˜è®¤ä¸ºå¼€å¯ï¼Œv1.4.15 æ–°å¢žå±žæ€§
        animation: false
      });
      const _textViews = [];
      points.forEach((p) => {
        const m = textLabelMarker(p.lnglat_GD, p.data, direction);
        _textViews.push(m);
        // å°† LabelMarker å®žä¾‹æ·»åŠ åˆ° LabelsLayer ä¸Š
        labelsLayer.add(m);
      });
      map.add(labelsLayer);
      // map.on('zoomend', () => {
      //   console.log(map.getZoom());
      // });
      return { textViews: _textViews, labelsLayer };
    }
  },
  /**
   * æ ¹æ®é¥æµ‹æ•°æ®ï¼Œè®¾ç½®å¯¹åº”网格的标准色,返回有数据的网格
   * @param {Array} gridViews ç½‘格多边形对象数组
   * @param {Array} gridDataDetail å«æ˜Ÿé¥æµ‹æ•°æ®æ•°ç»„
   * @param {string} factorName ç›‘测因子名称
   * @param {number} opacity é€æ˜Žåº¦
   */
  drawGridColor(gridViews, gridDataDetail, factorName, opacity, zIndex) {
    const res = [];
    // éåŽ†å«æ˜Ÿé¥æµ‹æ•°æ®æ•°ç»„
    gridDataDetail.forEach((d, i) => {
      if (d.pm25) {
        const grid = gridViews[i];
        // æ ¹æ®é¥æµ‹æ•°æ®è®¡ç®—网格颜色
        const data = d.pm25;
        const { color, nextColor, range, nextRange } =
          Legend.getStandardColorAndNext(factorName, data);
        const ratio = (data - range) / (nextRange - range);
        const _color = getColorBetweenTwoColors(
          color.map((v) => v * 255),
          nextColor.map((v) => v * 255),
          ratio
        );
        grid.setOptions({
          zIndex: zIndex ? zIndex : 10,
          fillColor: _color,
          fillOpacity: opacity ? opacity : color[3] == 0 ? 0 : 0.7
        });
        res.push(grid);
      }
    });
    return res;
  },
  drawGridColorCustom(gridViews, gridDataDetail, opacity) {
    var max, min;
    gridDataDetail.forEach((t) => {
      if (!t.pm25) return;
      if (!max || t.pm25 > max) {
        max = t.pm25;
      }
      if (!min || t.pm25 < min) {
        min = t.pm25;
      }
    });
    const res = [];
    // éåŽ†å«æ˜Ÿé¥æµ‹æ•°æ®æ•°ç»„
    gridDataDetail.forEach((d, i) => {
      if (d.pm25) {
        const grid = gridViews[i];
        // æ ¹æ®é¥æµ‹æ•°æ®è®¡ç®—网格颜色
        const data = d.pm25;
        const { color, nextColor, range, nextRange } =
          Legend.getCustomColorAndNext(data, min, max);
        const ratio = (data - range) / (nextRange - range);
        const _color = getColorBetweenTwoColors(
          color.map((v) => v * 255),
          nextColor.map((v) => v * 255),
          ratio
        );
        grid.setOptions({
          fillColor: _color,
          fillOpacity: opacity ? opacity : color[3] == 0 ? 0 : 0.7
        });
        res.push(grid);
      }
    });
    return res;
  }
};
src/utils/map/line.js
@@ -2,7 +2,8 @@
import calculate from './calculate';
import { getHexColor } from '../color';
var _polylineArr = [];
// var _polylineArr = [];
const lineMap = new Map();
function newPolyline(path, color) {
  // eslint-disable-next-line no-undef
@@ -21,13 +22,12 @@
export default {
  drawLine(fDatas, factor) {
    const _polylineArr = [];
    const lnglats_GD = fDatas.lnglats_GD;
    const colors = factor.colors;
    if (_polylineArr) {
      map.remove(_polylineArr);
      _polylineArr = [];
    }
    // this.hideLine();
    var path = calculate.parse2LngLat(lnglats_GD);
    let sIndex = 0;
@@ -80,5 +80,26 @@
    // å°†æŠ˜çº¿æ·»åŠ è‡³åœ°å›¾å®žä¾‹
    map.add(_polylineArr);
    return _polylineArr;
  },
  drawTagLine(tag, fDatas, factor) {
    if (lineMap.has(tag)) {
      const _polylineArr = lineMap.get(tag);
      map.add(_polylineArr);
    } else {
      const _polylineArr = this.drawLine(fDatas, factor);
      lineMap.set(tag, _polylineArr);
    }
  },
  hideLine(tag) {
    if (tag && lineMap.has(tag)) {
      const _polylineArr = lineMap.get(tag);
      map.remove(_polylineArr);
    } else {
      lineMap.forEach((v) => {
        map.remove(v);
      });
    }
  }
};
src/views/realtimemode/RealtimeMode.vue
@@ -26,6 +26,7 @@
<script>
import moment from 'moment';
import mapUtil from '@/utils/map/util';
import { useFetchData } from '@/composables/fetchData';
import { TYPE0 } from '@/constant/device-type';
import { defaultOptions } from '@/constant/radio-options';
@@ -173,6 +174,7 @@
  unmounted() {
    this.clearFetchingTask();
    realTimeMapAnimation.stop();
    mapUtil.clearMap();
  }
};
</script>
src/views/underwaymix/UnderwayMixMode.vue
@@ -1,6 +1,400 @@
<template>
  <div class="p-events-none m-t-2"></div>
  <div class="p-events-none m-t-2">
    <el-row justify="space-between">
      <el-row class="wrap">
        <el-col span="2">
          <el-row>
            <BaseCard v-show="show" size="medium" direction="left">
              <template #content>
                <el-row>
                  <el-form :inline="true">
                    <el-form-item label="走航融合">
                      <el-select
                        v-model="selectedfusionData"
                        multiple
                        clearable
                        @change="handleChange"
                        placeholder="选择任务"
                        size="small"
                        class="w-250"
                        :loading="fusionLoading"
                      >
                        <el-option
                          v-for="(s, i) in fusionDataList"
                          :key="i"
                          :label="s.mixDataId"
                          :value="i"
                        />
                      </el-select>
                    </el-form-item>
                    <el-form-item>
                      <el-button
                        type="primary"
                        class="el-button-custom"
                        size="small"
                        :disabled="
                          !gridCellList || selectedfusionData.length == 0
                        "
                        @click="handleFusionClick"
                      >
                        {{ '叠加融合' }}
                      </el-button>
                    </el-form-item>
                  </el-form>
                </el-row>
                <div class="m-t-8">网格要素</div>
                <el-row class="m-t-8">
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    @click="handleGridClick"
                  >
                    {{ gridVisible ? '隐藏融合' : '显示融合' }}
                  </el-button>
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    @click="handleRankClick"
                  >
                    {{ rankVisible ? '隐藏排名' : '显示排名' }}
                  </el-button>
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    @click="handleDataClick"
                  >
                    {{ dataVisible ? '隐藏数据' : '显示数据' }}
                  </el-button>
                </el-row>
                <div class="m-t-8">网格样式</div>
                <el-row class="m-t-8">
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    @click="handleOpacityClick"
                  >
                    {{ !isOpacity ? '透明化' : '取消透明化' }}
                  </el-button>
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    @click="handleColorClick"
                  >
                    {{ isStandardColor ? '绘制对比色' : '绘制标准色' }}
                  </el-button>
                </el-row>
                <div class="m-t-8">走航要素</div>
                <el-row class="m-t-8">
                  <!-- <el-divider content-position="left"></el-divider> -->
                  <el-button
                    type="primary"
                    class="el-button-custom"
                    size="small"
                    :loading="missionLoading || loading"
                    @click="handleUnderwayClick"
                  >
                    {{ underwayVisible ? '隐藏走航路线' : '显示走航路线' }}
                  </el-button>
                </el-row>
              </template>
              <template #footer> </template>
            </BaseCard>
          </el-row>
        </el-col>
        <el-col span="2">
          <el-row>
            <CardButton
              name="走航融合"
              direction="right"
              @click="() => (show = !show)"
            ></CardButton>
          </el-row>
        </el-col>
      </el-row>
    </el-row>
  </div>
</template>
<script setup></script>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import moment from 'moment';
import gridApi from '@/api/gridApi';
import { SatelliteGrid } from '@/model/SatelliteGrid';
const satelliteGrid = new SatelliteGrid();
// å€Ÿç”¨å«æ˜Ÿé¥æµ‹æ¨¡å—中的100米网格
const props = defineProps({
  groupId: {
    type: Number,
    default: 3
  }
});
const show = ref(true);
const mission = ref(undefined);
const gridCellList = ref(undefined);
const fusionData = ref(undefined);
// èµ°èˆªèžåˆæ•°æ®
const fusionLoading = ref(false);
const fusionDataList = ref([]);
const selectedfusionData = ref([]);
const gridDataDetailMap = new Map();
const gridVisible = ref(true);
const underwayVisible = ref(false);
const rankVisible = ref(false);
const dataVisible = ref(false);
const isStandardColor = ref(true);
const isOpacity = ref(false);
function timeFormatter(value) {
  return moment(value).format('YYYY-MM-DD');
}
function fetchFusionData() {
  fusionLoading.value = true;
  gridApi
    .fetchGridData(props.groupId, undefined, 3)
    .then((res) => {
      fusionDataList.value = res.data;
    })
    .finally(() => (fusionLoading.value = false));
}
// æ£€æŸ¥èµ°èˆªæ•°æ®æ˜¯å¦å’Œ100米网格已融合
// function checkUnderwayFusionResult() {
//   const time = moment(mission.value.startTime).format('YYYY-MM-DD HH:mm:ss');
//   gridApi.fetchGridData(props.groupId, time, 3).then((res) => {
//     if (res.data.length > 0) {
//       fusionData.value = res.data[0];
//     } else {
//       fusionData.value = undefined;
//     }
//   });
// }
function resetButton() {
  gridVisible.value = true;
  underwayVisible.value = false;
  rankVisible.value = false;
  dataVisible.value = false;
  isStandardColor.value = true;
  isOpacity.value = false;
}
function prepareGrid(gridInfo) {
  satelliteGrid.gridPrepare(gridInfo);
}
// ç»˜åˆ¶ç½‘格遥感数据值和网格颜色
function drawGrid(gridDataDetail) {
  satelliteGrid.drawGrid({ gridDataDetail, opacity: 1, zIndex: 11 });
}
// watch(mission, (nV, oV) => {
//   if (nV != oV) {
//     checkUnderwayFusionResult();
//     search(nV);
//   }
// });
watch(
  () => props.groupId,
  (nV, oV) => {
    if (nV != oV) {
      gridApi.fetchGridCell(nV).then((res) => {
        gridCellList.value = res.data;
        prepareGrid(gridCellList.value);
      });
      fetchFusionData();
    }
  },
  {
    immediate: true
  }
);
function handleFusionClick() {
  // resetButton();
  satelliteGrid.changeVisibility({ showGridViews: false });
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    if (gridDataDetailMap.has(d.id)) {
      // const gdd = gridDataDetailMap.get(d.id);
      // satelliteGrid.drawTagGrid({
      //   tag: d.id,
      //   gridDataDetail: gdd,
      //   opacity: 1,
      //   zIndex: 11
      // });
      satelliteGrid.changeVisibility({ tag: d.id, showGridViews: true });
    } else {
      gridApi.fetchGridDataDetail(d.id, d.groupId).then((res) => {
        gridDataDetailMap.set(d.id, res.data);
        const gdd = res.data;
        satelliteGrid.drawTagGrid({
          tag: d.id,
          gridDataDetail: gdd,
          opacity: 1,
          zIndex: 11
        });
      });
    }
  });
}
function handleGridClick() {
  gridVisible.value = !gridVisible.value;
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    satelliteGrid.changeVisibility({
      tag: d.id,
      showGridViews: gridVisible.value
    });
  });
}
function handleUnderwayClick() {
  underwayVisible.value = !underwayVisible.value;
  underwayVisible.value ? draw() : mapLine.hideLine();
}
function handleRankClick() {
  rankVisible.value = !rankVisible.value;
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    satelliteGrid.changeVisibility({
      tag: d.id,
      showRankTxt: rankVisible.value
    });
  });
}
function handleDataClick() {
  dataVisible.value = !dataVisible.value;
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    satelliteGrid.changeVisibility({
      tag: d.id,
      showDataTxt: dataVisible.value
    });
  });
}
function handleColorClick() {
  isStandardColor.value = !isStandardColor.value;
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    if (gridDataDetailMap.has(d.id)) {
      const gdd = gridDataDetailMap.get(d.id);
      satelliteGrid.drawTagGrid({
        tag: d.id,
        gridDataDetail: gdd,
        useCustomColor: !isStandardColor.value,
        opacity: 1,
        zIndex: 11
      });
    }
  });
}
function handleOpacityClick() {
  isOpacity.value = !isOpacity.value;
  satelliteGrid.changeGridOpacity({ opacityValue: isOpacity.value ? 0.1 : 1 });
}
/**走航轨迹*******************************************************************/
import { FactorDatas } from '@/model/FactorDatas';
import sector from '@/utils/map/sector';
import mapLine from '@/utils/map/line';
import mapUtil from '@/utils/map/util';
import { fetchHistoryData } from '@/utils/factor/data';
import { useFetchData } from '@/composables/fetchData';
import { useMissionStore } from '@/stores/mission';
onMounted(() => {
  isUnmounted.value = false;
  fetchMission();
});
onUnmounted(() => {
  mapUtil.clearMap();
  isUnmounted.value = true;
});
const { loading, fetchData } = useFetchData(10000);
const missionStore = useMissionStore();
const missionLoading = ref(false);
const isUnmounted = ref(false);
const drawMode = ref(0);
// ç›‘测数据
const factorDataMap = new Map();
// pm2.5
const factorType = 6;
function fetchMission() {
  missionLoading.value = true;
  missionStore.fetchMission().finally(() => (missionLoading.value = false));
}
function search(option) {
  const { deviceType, deviceCode, startTime, endTime } = option;
  const _startTime = moment(startTime).format('YYYY-MM-DD HH:mm:ss');
  const _endTime = moment(endTime).format('YYYY-MM-DD HH:mm:ss');
  return fetchData((page, pageSize) => {
    return fetchHistoryData({
      deviceType,
      deviceCode,
      startTime: _startTime,
      endTime: _endTime,
      page,
      perPage: pageSize
    });
  });
}
function draw() {
  if (isUnmounted.value) return;
  selectedfusionData.value.forEach((i) => {
    const d = fusionDataList.value[i];
    const mission = missionStore.missionList.find((v) => {
      return v.missionCode == d.mixDataId;
    });
    if (factorDataMap.has(mission.missionCode)) {
      const fd = factorDataMap.get(mission.missionCode);
      drawLine(mission.missionCode, fd);
    } else {
      search(mission).then((res) => {
        const fd = new FactorDatas();
        fd.setData(res.data, drawMode.value, () => {
          fd.refreshHeight(factorType.value);
          factorDataMap.set(mission.missionCode, fd);
          drawLine(mission.missionCode, fd);
        });
      });
    }
  });
}
function drawLine(missionCode, fd) {
  // åˆ·æ–°å›¾ä¾‹
  const factor = fd.factor[factorType];
  sector.clearSector();
  fd.refreshHeight(factorType);
  mapLine.drawTagLine(missionCode, fd, factor);
}
</script>
<style scoped></style>