|
|
@@ -0,0 +1,1023 @@
|
|
|
+<template>
|
|
|
+ <div class="amap-container">
|
|
|
+ <div class="control-panel">
|
|
|
+ <button class="heatmap-toggle-btn" @click="toggleHeatmap" :class="{ active: heatmapVisible }">
|
|
|
+ {{ heatmapVisible ? '隐藏热力图' : '显示热力图' }}
|
|
|
+ </button>
|
|
|
+ <!-- 新增:切换多边形显示/隐藏按钮 -->
|
|
|
+ <button class="polygon-toggle-btn" @click="togglePolygon" :class="{ active: polygonVisible }">
|
|
|
+ {{ polygonVisible ? '隐藏危险等级' : '显示危险等级' }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div id="container" class="map-box"></div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'AmapHeatMap',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ map: null,
|
|
|
+ heatmap: null,
|
|
|
+ borderPolygon: null,
|
|
|
+ isMapLoaded: false,
|
|
|
+ borderColor: '#3dfcfc', // 默认青色
|
|
|
+ isAlarm: false,
|
|
|
+ animationInterval: null,
|
|
|
+ heatmapVisible: false, // 热力图默认为关闭状态
|
|
|
+ polygonVisible: false, // 多边形默认为显示状态
|
|
|
+ polygons: [],
|
|
|
+ infoWindow: null // 高德地图信息窗口
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.initMap();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 清理动画
|
|
|
+ this.stopBorderAnimation();
|
|
|
+
|
|
|
+ // 移除信息窗口
|
|
|
+ if (this.infoWindow) {
|
|
|
+ this.infoWindow.close();
|
|
|
+ this.infoWindow = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优化销毁逻辑:先移除热力图层和边框,再销毁地图,防止内存泄漏
|
|
|
+ if (this.heatmap && this.map) {
|
|
|
+ this.map.remove(this.heatmap);
|
|
|
+ }
|
|
|
+ if (this.borderPolygon && this.map) {
|
|
|
+ this.map.remove(this.borderPolygon);
|
|
|
+ }
|
|
|
+ if (this.polygons && this.polygons.length > 0) {
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.remove(polygon);
|
|
|
+ });
|
|
|
+ this.polygons = [];
|
|
|
+ }
|
|
|
+ if (this.map) {
|
|
|
+ this.map.destroy();
|
|
|
+ this.map = null;
|
|
|
+ this.heatmap = null;
|
|
|
+ this.borderPolygon = null;
|
|
|
+ this.polygons = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取所有定义的区域配置
|
|
|
+ * 可以在这里轻松添加新区域
|
|
|
+ */
|
|
|
+ getAreaConfigurations() {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ id: 'main',
|
|
|
+ name: '主要区域',
|
|
|
+ westLat: 116.989366,
|
|
|
+ eastLat: 116.990562,
|
|
|
+ northLng: 34.20432,
|
|
|
+ southLng: 34.203846,
|
|
|
+ cols: 18,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'new',
|
|
|
+ name: '新区域',
|
|
|
+ westLat: 116.989367,
|
|
|
+ eastLat: 116.990559,
|
|
|
+ northLng: 34.204871,
|
|
|
+ southLng: 34.204353,
|
|
|
+ cols: 18,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'third',
|
|
|
+ name: '第三区域',
|
|
|
+ westLat: 116.989373,
|
|
|
+ eastLat: 116.990562,
|
|
|
+ northLng: 34.203811,
|
|
|
+ southLng: 34.20335,
|
|
|
+ cols: 18,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 最新添加的区域
|
|
|
+ {
|
|
|
+ id: 'fourth',
|
|
|
+ name: '第四区域',
|
|
|
+ westLat: 116.989373,
|
|
|
+ eastLat: 116.990572,
|
|
|
+ northLng: 34.203306,
|
|
|
+ southLng: 34.202892,
|
|
|
+ cols: 18,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 再次添加的新区域
|
|
|
+ {
|
|
|
+ id: 'fifth',
|
|
|
+ name: '第五区域',
|
|
|
+ westLat: 116.989375,
|
|
|
+ eastLat: 116.990559,
|
|
|
+ northLng: 34.202867,
|
|
|
+ southLng: 34.202407,
|
|
|
+ cols: 18,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 新增的第六个区域
|
|
|
+ {
|
|
|
+ id: 'sixth',
|
|
|
+ name: '第六区域',
|
|
|
+ westLat: 116.988406,
|
|
|
+ eastLat: 116.989257,
|
|
|
+ northLng: 34.204885,
|
|
|
+ southLng: 34.204355,
|
|
|
+ cols: 13,
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 新增的第七个区域
|
|
|
+ {
|
|
|
+ id: 'seventh',
|
|
|
+ name: '第七区域',
|
|
|
+ westLat: 116.9884,
|
|
|
+ eastLat: 116.989245,
|
|
|
+ northLng: 34.204324,
|
|
|
+ southLng: 34.203857,
|
|
|
+ cols: 13, // 这个区域较窄,使用13列
|
|
|
+ rows: 7
|
|
|
+ }
|
|
|
+ ,
|
|
|
+ // 新增的第八个区域
|
|
|
+ {
|
|
|
+ id: 'eighth',
|
|
|
+ name: '第八区域',
|
|
|
+ westLat: 116.988396,
|
|
|
+ eastLat: 116.989241,
|
|
|
+ northLng: 34.203805,
|
|
|
+ southLng: 34.203354,
|
|
|
+ cols: 13, // 这个区域较窄,使用13列
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 新增的第九个区域
|
|
|
+ {
|
|
|
+ id: 'ninth',
|
|
|
+ name: '第九区域',
|
|
|
+ westLat: 116.988392,
|
|
|
+ eastLat: 116.989234,
|
|
|
+ northLng: 34.203338,
|
|
|
+ southLng: 34.202895,
|
|
|
+ cols: 13, // 这个区域较窄,使用13列
|
|
|
+ rows: 7
|
|
|
+ },
|
|
|
+ // 新增的第十个区域
|
|
|
+ {
|
|
|
+ id: 'tenth',
|
|
|
+ name: '第十区域',
|
|
|
+ westLat: 116.98838,
|
|
|
+ eastLat: 116.989232,
|
|
|
+ northLng: 34.202859,
|
|
|
+ southLng: 34.20242,
|
|
|
+ cols: 13, // 这个区域较窄,使用13列
|
|
|
+ rows: 7
|
|
|
+ }
|
|
|
+ // 后续可以在这里继续添加新区域
|
|
|
+ // {
|
|
|
+ // id: 'another_area',
|
|
|
+ // name: '另一个区域',
|
|
|
+ // westLat: 经度西,
|
|
|
+ // eastLat: 经度东,
|
|
|
+ // northLng: 纬度北,
|
|
|
+ // southLng: 纬度南,
|
|
|
+ // cols: 18,
|
|
|
+ // rows: 7
|
|
|
+ // }
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 为单个区域生成网格
|
|
|
+ * @param {Object} area - 区域配置对象
|
|
|
+ * @param {number} baseId - ID起始值,避免重复
|
|
|
+ */
|
|
|
+ generateSingleAreaGrid(area, baseId = 0) {
|
|
|
+ const {
|
|
|
+ westLat, eastLat, northLng, southLng,
|
|
|
+ cols, rows, id: areaId, name: areaName
|
|
|
+ } = area;
|
|
|
+
|
|
|
+ const colWidth = (eastLat - westLat) / cols;
|
|
|
+ const rowHeight = (northLng - southLng) / rows;
|
|
|
+
|
|
|
+ // 风险等级数组
|
|
|
+ const riskLevels = ['healthy', 'low_risk', 'high_risk'];
|
|
|
+
|
|
|
+ const polygonsData = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < rows; i++) {
|
|
|
+ for (let j = 0; j < cols; j++) {
|
|
|
+ // 计算当前小方块的四个角点坐标
|
|
|
+ const westCorner = westLat + j * colWidth;
|
|
|
+ const eastCorner = westLat + (j + 1) * colWidth;
|
|
|
+ const northCorner = northLng - i * rowHeight;
|
|
|
+ const southCorner = northLng - (i + 1) * rowHeight;
|
|
|
+
|
|
|
+ // 创建小方块的路径(顺时针方向)
|
|
|
+ const path = [
|
|
|
+ [westCorner, northCorner], // 左上角
|
|
|
+ [eastCorner, northCorner], // 右上角
|
|
|
+ [eastCorner, southCorner], // 右下角
|
|
|
+ [westCorner, southCorner], // 左下角
|
|
|
+ [westCorner, northCorner] // 回到起点
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 随机选择风险等级
|
|
|
+ const riskLevel = riskLevels[Math.floor(Math.random() * riskLevels.length)];
|
|
|
+
|
|
|
+ // 创建多边形数据
|
|
|
+ const polygonData = {
|
|
|
+ id: baseId + i * cols + j + 1,
|
|
|
+ name: `${areaName}-${i * cols + j + 1}`,
|
|
|
+ riskLevel: riskLevel,
|
|
|
+ path: path, // 注意:这里不需要嵌套数组
|
|
|
+ areaType: areaId,
|
|
|
+ areaName: areaName
|
|
|
+ };
|
|
|
+
|
|
|
+ polygonsData.push(polygonData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return polygonsData;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换多边形显示/隐藏
|
|
|
+ */
|
|
|
+ /* togglePolygon() {
|
|
|
+ if (!this.polygons || this.polygons.length === 0) {
|
|
|
+ console.warn('多边形尚未初始化');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.polygonVisible) {
|
|
|
+ // 隐藏多边形
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.remove(polygon);
|
|
|
+ });
|
|
|
+ this.polygonVisible = false;
|
|
|
+ console.log('多边形已隐藏');
|
|
|
+ } else {
|
|
|
+ // 显示多边形
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.add(polygon);
|
|
|
+ });
|
|
|
+ this.polygonVisible = true;
|
|
|
+ console.log('多边形已显示');
|
|
|
+ }
|
|
|
+ }, */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换多边形显示/隐藏
|
|
|
+ */
|
|
|
+ togglePolygon() {
|
|
|
+ if (!this.polygons || this.polygons.length === 0) {
|
|
|
+ console.warn('多边形尚未初始化');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.polygonVisible) {
|
|
|
+ // 隐藏多边形
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.remove(polygon);
|
|
|
+ });
|
|
|
+ this.polygonVisible = false;
|
|
|
+ console.log('多边形已隐藏');
|
|
|
+ } else {
|
|
|
+ // 显示多边形
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.add(polygon);
|
|
|
+ });
|
|
|
+ this.polygonVisible = true;
|
|
|
+ console.log('多边形已显示');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改 generateGridPolygons 方法,使其能够生成多个区域的网格
|
|
|
+ /* generateGridPolygons() {
|
|
|
+ // 主要区域坐标(原有的)
|
|
|
+ const mainWestLat = 116.989366;
|
|
|
+ const mainEastLat = 116.990562;
|
|
|
+ const mainNorthLng = 34.20432;
|
|
|
+ const mainSouthLng = 34.203846;
|
|
|
+
|
|
|
+ // 新区域坐标
|
|
|
+ const newWestLat = 116.989367;
|
|
|
+ const newEastLat = 116.990559;
|
|
|
+ const newNorthLng = 34.204871;
|
|
|
+ const newSouthLng = 34.204353;
|
|
|
+
|
|
|
+ // 定义区域配置
|
|
|
+ const areas = [
|
|
|
+ {
|
|
|
+ name: 'main',
|
|
|
+ westLat: mainWestLat,
|
|
|
+ eastLat: mainEastLat,
|
|
|
+ northLng: mainNorthLng,
|
|
|
+ southLng: mainSouthLng
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: 'new',
|
|
|
+ westLat: newWestLat,
|
|
|
+ eastLat: newEastLat,
|
|
|
+ northLng: newNorthLng,
|
|
|
+ southLng: newSouthLng
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 计算每列和每行的宽度和高度(对于两个区域都使用相同的行列数)
|
|
|
+ const cols = 18;
|
|
|
+ const rows = 7;
|
|
|
+
|
|
|
+ // 风险等级数组
|
|
|
+ const riskLevels = ['healthy', 'low_risk', 'high_risk'];
|
|
|
+
|
|
|
+ // 生成所有小方块的数据
|
|
|
+ const polygonsData = [];
|
|
|
+
|
|
|
+ // 遍历每个区域
|
|
|
+ areas.forEach((area, areaIndex) => {
|
|
|
+ const colWidth = (area.eastLat - area.westLat) / cols;
|
|
|
+ const rowHeight = (area.northLng - area.southLng) / rows;
|
|
|
+
|
|
|
+ for (let i = 0; i < rows; i++) {
|
|
|
+ for (let j = 0; j < cols; j++) {
|
|
|
+ // 计算当前小方块的四个角点坐标
|
|
|
+ const westCorner = area.westLat + j * colWidth;
|
|
|
+ const eastCorner = area.westLat + (j + 1) * colWidth;
|
|
|
+ const northCorner = area.northLng - i * rowHeight;
|
|
|
+ const southCorner = area.northLng - (i + 1) * rowHeight;
|
|
|
+
|
|
|
+ // 创建小方块的路径(顺时针方向)
|
|
|
+ const path = [
|
|
|
+ [westCorner, northCorner], // 左上角
|
|
|
+ [eastCorner, northCorner], // 右上角
|
|
|
+ [eastCorner, southCorner], // 右下角
|
|
|
+ [westCorner, southCorner], // 左下角
|
|
|
+ [westCorner, northCorner] // 回到起点
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 随机选择风险等级
|
|
|
+ const riskLevel = riskLevels[Math.floor(Math.random() * riskLevels.length)];
|
|
|
+
|
|
|
+ // 创建多边形数据
|
|
|
+ const polygonData = {
|
|
|
+ id: areaIndex * cols * rows + i * cols + j + 1,
|
|
|
+ name: `${area.name}区域-${i * cols + j + 1}`,
|
|
|
+ riskLevel: riskLevel,
|
|
|
+ path: path, // 注意:这里不需要嵌套数组,因为createMultiplePolygons会自动处理
|
|
|
+ areaType: area.name
|
|
|
+ };
|
|
|
+
|
|
|
+ polygonsData.push(polygonData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return polygonsData;
|
|
|
+ }, */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成所有区域的网格
|
|
|
+ */
|
|
|
+ generateGridPolygons() {
|
|
|
+ const areas = this.getAreaConfigurations();
|
|
|
+ let allPolygonsData = [];
|
|
|
+ let currentBaseId = 1;
|
|
|
+
|
|
|
+ areas.forEach(area => {
|
|
|
+ const areaPolygons = this.generateSingleAreaGrid(area, currentBaseId - 1);
|
|
|
+ allPolygonsData = allPolygonsData.concat(areaPolygons);
|
|
|
+
|
|
|
+ // 更新下一个区域的ID基数
|
|
|
+ currentBaseId += area.rows * area.cols;
|
|
|
+ });
|
|
|
+
|
|
|
+ return allPolygonsData;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 动态添加新区域
|
|
|
+ * @param {Object} newArea - 新区域配置
|
|
|
+ */
|
|
|
+ addNewArea(newArea) {
|
|
|
+ // 生成新区域的网格
|
|
|
+ const baseId = this.polygons.length + 1; // 使用当前多边形数量作为基础ID
|
|
|
+ const newAreaPolygons = this.generateSingleAreaGrid(newArea, baseId - 1);
|
|
|
+
|
|
|
+ // 创建并添加新的多边形
|
|
|
+ newAreaPolygons.forEach(polygonData => {
|
|
|
+ const colors = this.getPolygonColorByRiskLevel(polygonData.riskLevel || 'healthy');
|
|
|
+
|
|
|
+ const polygon = new AMap.Polygon({
|
|
|
+ path: [polygonData.path], // 多边形路径
|
|
|
+ fillColor: colors.fillColor, // 根据风险等级确定填充颜色
|
|
|
+ strokeOpacity: polygonData.strokeOpacity || 1, // 线条透明度
|
|
|
+ fillOpacity: polygonData.fillOpacity || 0.5, // 填充透明度
|
|
|
+ strokeColor: colors.strokeColor, // 根据风险等级确定边框颜色
|
|
|
+ strokeWeight: polygonData.strokeWeight || 1, // 线条宽度
|
|
|
+ strokeStyle: polygonData.strokeStyle || "solid", // 线样式
|
|
|
+ strokeDasharray: polygonData.strokeDasharray || [5, 5], // 虚线样式
|
|
|
+ extData: {
|
|
|
+ id: polygonData.id,
|
|
|
+ riskLevel: polygonData.riskLevel || 'healthy', // 风险等级
|
|
|
+ name: polygonData.name, // 区域名称
|
|
|
+ areaType: polygonData.areaType,
|
|
|
+ areaName: polygonData.areaName,
|
|
|
+ ...polygonData.extData
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为每个多边形添加交互事件
|
|
|
+ this.addPolygonEvents(polygon);
|
|
|
+
|
|
|
+ // 添加到地图
|
|
|
+ this.map.add(polygon);
|
|
|
+
|
|
|
+ // 存储多边形实例以便后续操作
|
|
|
+ this.polygons.push(polygon);
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`成功添加新区域: ${newArea.name}`);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过配置添加新区域的便捷方法
|
|
|
+ */
|
|
|
+ addAreaFromConfig(areaId, areaName, coordinates, cols = 18, rows = 7) {
|
|
|
+ const newArea = {
|
|
|
+ id: areaId,
|
|
|
+ name: areaName,
|
|
|
+ westLat: coordinates.westLat,
|
|
|
+ eastLat: coordinates.eastLat,
|
|
|
+ northLng: coordinates.northLng,
|
|
|
+ southLng: coordinates.southLng,
|
|
|
+ cols: cols,
|
|
|
+ rows: rows
|
|
|
+ };
|
|
|
+
|
|
|
+ this.addNewArea(newArea);
|
|
|
+ },
|
|
|
+ // 获取多边形颜色根据风险等级
|
|
|
+ getPolygonColorByRiskLevel(level) {
|
|
|
+ switch (level) {
|
|
|
+ case 'healthy': // 健康
|
|
|
+ return {
|
|
|
+ fillColor: '#90EE90', // 浅绿色
|
|
|
+ strokeColor: '#228B22', // 深绿色边框
|
|
|
+ hoverColor: '#32CD32' // 鼠标悬停时的颜色
|
|
|
+ };
|
|
|
+ case 'low_risk': // 轻度风险
|
|
|
+ return {
|
|
|
+ fillColor: '#FFFF99', // 浅黄色
|
|
|
+ strokeColor: '#FFA500', // 橙色边框
|
|
|
+ hoverColor: '#FFD700' // 鼠标悬停时的颜色
|
|
|
+ };
|
|
|
+ case 'high_risk': // 重度风险
|
|
|
+ return {
|
|
|
+ fillColor: '#FF6347', // 番茄红
|
|
|
+ strokeColor: '#DC143C', // 深红色边框
|
|
|
+ hoverColor: '#FF0000' // 鼠标悬停时的颜色
|
|
|
+ };
|
|
|
+ default:
|
|
|
+ return {
|
|
|
+ fillColor: '#ccebc5', // 默认颜色
|
|
|
+ strokeColor: '#2b8cbe',
|
|
|
+ hoverColor: '#7bccc4'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 在 methods 中添加以下方法
|
|
|
+ // 修改 createMultiplePolygons 方法以兼容新的路径格式
|
|
|
+ createMultiplePolygons(polygonsData) {
|
|
|
+ this.polygons = []; // 存储所有多边形实例
|
|
|
+
|
|
|
+ polygonsData.forEach((polygonData, index) => {
|
|
|
+ const colors = this.getPolygonColorByRiskLevel(polygonData.riskLevel || 'healthy');
|
|
|
+
|
|
|
+ // 检查路径格式并相应处理
|
|
|
+ const path = Array.isArray(polygonData.path[0]) ? polygonData.path : [polygonData.path];
|
|
|
+
|
|
|
+ const polygon = new AMap.Polygon({
|
|
|
+ path: path, // 多边形路径
|
|
|
+ fillColor: colors.fillColor, // 根据风险等级确定填充颜色
|
|
|
+ strokeOpacity: polygonData.strokeOpacity || 1, // 线条透明度
|
|
|
+ fillOpacity: polygonData.fillOpacity || 0.5, // 填充透明度
|
|
|
+ strokeColor: colors.strokeColor, // 根据风险等级确定边框颜色
|
|
|
+ strokeWeight: polygonData.strokeWeight || 1, // 线条宽度
|
|
|
+ strokeStyle: polygonData.strokeStyle || "solid", // 线样式
|
|
|
+ strokeDasharray: polygonData.strokeDasharray || [5, 5], // 虚线样式
|
|
|
+ extData: {
|
|
|
+ id: polygonData.id || index, // 可以添加标识符
|
|
|
+ riskLevel: polygonData.riskLevel || 'healthy', // 风险等级
|
|
|
+ name: polygonData.name || `区域${index + 1}`, // 区域名称
|
|
|
+ areaType: polygonData.areaType || 'default',
|
|
|
+ ...polygonData.extData
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为每个多边形添加交互事件
|
|
|
+ this.addPolygonEvents(polygon);
|
|
|
+
|
|
|
+ // 添加到地图
|
|
|
+ this.map.add(polygon);
|
|
|
+
|
|
|
+ // 存储多边形实例以便后续操作
|
|
|
+ this.polygons.push(polygon);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加多边形交互事件
|
|
|
+ addPolygonEvents(polygon) {
|
|
|
+ // 鼠标移入更改样式
|
|
|
+ polygon.on("mouseover", () => {
|
|
|
+ const currentRiskLevel = polygon.getExtData().riskLevel;
|
|
|
+ const colors = this.getPolygonColorByRiskLevel(currentRiskLevel);
|
|
|
+
|
|
|
+ polygon.setOptions({
|
|
|
+ fillOpacity: 0.7, // 多边形填充透明度
|
|
|
+ fillColor: colors.hoverColor,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 鼠标移出恢复样式
|
|
|
+ polygon.on("mouseout", () => {
|
|
|
+ const currentRiskLevel = polygon.getExtData().riskLevel;
|
|
|
+ const colors = this.getPolygonColorByRiskLevel(currentRiskLevel);
|
|
|
+
|
|
|
+ polygon.setOptions({
|
|
|
+ fillOpacity: 0.5,
|
|
|
+ fillColor: colors.fillColor,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 鼠标点击事件
|
|
|
+ polygon.on("click", (e) => {
|
|
|
+ const extData = polygon.getExtData();
|
|
|
+ console.log(`点击了区域: ${extData.name}, 风险等级: ${extData.riskLevel}`);
|
|
|
+
|
|
|
+ // 创建或更新信息窗口
|
|
|
+ this.showInfoWindow(e.lnglat, extData);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示高德地图信息窗口
|
|
|
+ showInfoWindow(position, extData) {
|
|
|
+ // 如果信息窗口不存在,则创建它
|
|
|
+ if (!this.infoWindow) {
|
|
|
+ this.infoWindow = new AMap.InfoWindow({
|
|
|
+ offset: new AMap.Pixel(0, -10),
|
|
|
+ closeWhenClickMap: true, // 点击地图关闭信息窗体
|
|
|
+ autoMove: true, // 自动调整位置,防止被地图遮挡
|
|
|
+ isCustom: false, // 使用默认样式
|
|
|
+ showShadow: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建信息窗口内容
|
|
|
+ const content = `
|
|
|
+ <div style="padding: 10px; min-width: 180px;">
|
|
|
+ <h4 style="margin: 0 0 8px 0; font-size: 16px; color: #333;">${extData.name}</h4>
|
|
|
+ <div style="display: flex; align-items: center; margin-bottom: 5px;">
|
|
|
+ <span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${extData.riskLevel === 'healthy' ? '#228B22' :
|
|
|
+ extData.riskLevel === 'low_risk' ? '#FFA500' :
|
|
|
+ '#DC143C'
|
|
|
+ }; margin-right: 8px;"></span>
|
|
|
+ <strong>风险等级:</strong>
|
|
|
+ <span>${this.getRiskLevelText(extData.riskLevel)}</span>
|
|
|
+ </div>
|
|
|
+ <div style="font-size: 12px; color: #666; margin-top: 8px;">
|
|
|
+ 点击坐标: ${position.lng.toFixed(6)}, ${position.lat.toFixed(6)}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 设置内容并打开信息窗口
|
|
|
+ this.infoWindow.setContent(content);
|
|
|
+ this.infoWindow.open(this.map, position);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取风险等级文字描述
|
|
|
+ getRiskLevelText(riskLevel) {
|
|
|
+ switch (riskLevel) {
|
|
|
+ case 'healthy':
|
|
|
+ return '健康';
|
|
|
+ case 'low_risk':
|
|
|
+ return '轻度风险';
|
|
|
+ case 'high_risk':
|
|
|
+ return '重度风险';
|
|
|
+ default:
|
|
|
+ return '未知';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 清理所有多边形
|
|
|
+ clearAllPolygons() {
|
|
|
+ if (this.polygons && this.polygons.length > 0) {
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.remove(polygon);
|
|
|
+ });
|
|
|
+ this.polygons = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换热力图显示/隐藏
|
|
|
+ */
|
|
|
+ toggleHeatmap() {
|
|
|
+ if (!this.heatmap) {
|
|
|
+ console.warn('热力图尚未初始化');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.heatmapVisible) {
|
|
|
+ // 隐藏热力图
|
|
|
+ this.heatmap.hide();
|
|
|
+ this.heatmapVisible = false;
|
|
|
+ console.log('热力图已隐藏');
|
|
|
+ } else {
|
|
|
+ // 显示热力图
|
|
|
+ this.heatmap.show();
|
|
|
+ this.heatmapVisible = true;
|
|
|
+ console.log('热力图已显示');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ initMap() {
|
|
|
+ if (this.isMapLoaded) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (typeof AMap === 'undefined') {
|
|
|
+ console.error('高德地图API未加载,请检查script标签引入');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化3D地图
|
|
|
+ this.map = new AMap.Map("container", {
|
|
|
+ center: [116.991074, 34.203813],
|
|
|
+ zoom: 18.5,
|
|
|
+ zooms: [3, 20], // 设置缩放级别范围
|
|
|
+ pitch: 40.0, // 俯仰角设置为42度
|
|
|
+ rotation: 4.9, // 旋转角设置为4.9度
|
|
|
+ resizeEnable: true,
|
|
|
+ viewMode: '3D',
|
|
|
+ buildingAnimation: true,
|
|
|
+ skyColor: '#f0f9ff',
|
|
|
+ showLabel: true,
|
|
|
+ layers: [
|
|
|
+ new AMap.TileLayer.Satellite({ zIndex: 1, opacity: 1 }),
|
|
|
+ new AMap.TileLayer.RoadNet({ zIndex: 2, opacity: 0.9 })
|
|
|
+ ]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加地图点击事件监听
|
|
|
+ this.map.on('click', (event) => {
|
|
|
+ const lnglat = event.lnglat; // 获取点击的经纬度坐标
|
|
|
+ const lng = lnglat.lng; // 经度
|
|
|
+ const lat = lnglat.lat; // 纬度
|
|
|
+
|
|
|
+ console.log('地图点击坐标:', {
|
|
|
+ lng: lng,
|
|
|
+ lat: lat,
|
|
|
+ formatted: `${lng.toFixed(6)}, ${lat.toFixed(6)}`,
|
|
|
+ timestamp: new Date().toISOString()
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 初始化并添加边框
|
|
|
+ this.addBorderPolygon();
|
|
|
+
|
|
|
+ // 开始边框动画效果
|
|
|
+ this.startBorderAnimation();
|
|
|
+
|
|
|
+
|
|
|
+ // 准备多边形数据 - 根据风险等级着色
|
|
|
+ // 在 initMap 方法中替换原来的 polygonDataList
|
|
|
+ const polygonDataList = this.generateGridPolygons(); // 使用生成的网格数据
|
|
|
+
|
|
|
+ // 批量创建多边形
|
|
|
+ this.createMultiplePolygons(polygonDataList);
|
|
|
+
|
|
|
+ // 如果多边形设置为隐藏,则立即从地图上移除
|
|
|
+ if (!this.polygonVisible) {
|
|
|
+ this.polygons.forEach(polygon => {
|
|
|
+ this.map.remove(polygon);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载热力图插件
|
|
|
+ AMap.plugin(['AMap.HeatMap'], () => {
|
|
|
+ console.log('热力图插件手动加载完成✅');
|
|
|
+
|
|
|
+ // 使用真实葡萄园数据
|
|
|
+ const heatmapData = [
|
|
|
+ { lng: 116.989156, lat: 34.203689, count: 82 },
|
|
|
+ { lng: 116.988789, lat: 34.204215, count: 47 },
|
|
|
+ { lng: 116.990023, lat: 34.202987, count: 91 },
|
|
|
+ { lng: 116.989678, lat: 34.203456, count: 29 },
|
|
|
+ { lng: 116.988654, lat: 34.203987, count: 76 },
|
|
|
+ { lng: 116.989876, lat: 34.204678, count: 63 },
|
|
|
+ { lng: 116.988345, lat: 34.202876, count: 58 },
|
|
|
+ { lng: 116.989456, lat: 34.203123, count: 88 },
|
|
|
+ { lng: 116.989987, lat: 34.204111, count: 37 },
|
|
|
+ { lng: 116.988987, lat: 34.202765, count: 95 },
|
|
|
+ { lng: 116.989234, lat: 34.204345, count: 42 },
|
|
|
+ { lng: 116.988567, lat: 34.203789, count: 71 },
|
|
|
+ { lng: 116.989765, lat: 34.203098, count: 66 },
|
|
|
+ { lng: 116.990123, lat: 34.203876, count: 28 },
|
|
|
+ { lng: 116.988432, lat: 34.204567, count: 89 },
|
|
|
+
|
|
|
+ { lng: 116.989098, lat: 34.202543, count: 85 },
|
|
|
+ { lng: 116.988678, lat: 34.203987, count: 41 },
|
|
|
+ { lng: 116.989432, lat: 34.204098, count: 97 },
|
|
|
+ { lng: 116.990345, lat: 34.202876, count: 25 },
|
|
|
+ { lng: 116.988543, lat: 34.203654, count: 74 },
|
|
|
+ { lng: 116.989789, lat: 34.204234, count: 56 },
|
|
|
+ { lng: 116.989123, lat: 34.202987, count: 81 },
|
|
|
+ { lng: 116.988901, lat: 34.204654, count: 39 },
|
|
|
+ { lng: 116.989876, lat: 34.203765, count: 69 },
|
|
|
+ { lng: 116.990098, lat: 34.204321, count: 90 },
|
|
|
+ { lng: 116.988456, lat: 34.203123, count: 45 },
|
|
|
+ { lng: 116.989567, lat: 34.202789, count: 77 },
|
|
|
+ { lng: 116.988890, lat: 34.204456, count: 59 },
|
|
|
+ { lng: 116.989345, lat: 34.203890, count: 87 },
|
|
|
+ { lng: 116.990198, lat: 34.204012, count: 31 },
|
|
|
+ { lng: 116.988790, lat: 34.203543, count: 62 }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 创建热力图实例
|
|
|
+ this.heatmap = new AMap.HeatMap(this.map, {
|
|
|
+ radius: 100,
|
|
|
+ opacity: [0.4, 0.8],
|
|
|
+ gradient: {
|
|
|
+ 1: '#FF4C2F',
|
|
|
+ 0.8: '#FAA53F',
|
|
|
+ 0.6: '#FFF100',
|
|
|
+ 0.5: '#7DF675',
|
|
|
+ 0.4: '#5CE182',
|
|
|
+ 0.2: '#29CF6F',
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 设置热力图数据
|
|
|
+ this.heatmap.setDataSet({
|
|
|
+ data: heatmapData,
|
|
|
+ max: 100
|
|
|
+ });
|
|
|
+
|
|
|
+ // 确保热力图初始状态为隐藏
|
|
|
+ this.heatmap.hide();
|
|
|
+
|
|
|
+ console.log('3D热力图初始化成功✅');
|
|
|
+ });
|
|
|
+
|
|
|
+ this.isMapLoaded = true;
|
|
|
+ console.log('3D地图初始化成功✅');
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+ console.error("高德地图3D加载异常:", e);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加3D发光边框(使用Polygon)
|
|
|
+ */
|
|
|
+ addBorderPolygon() {
|
|
|
+ // 使用你提供的点击坐标点形成边界
|
|
|
+ const borderCoordinates = [
|
|
|
+ [116.988285, 34.204935],
|
|
|
+ [116.988215, 34.202335],
|
|
|
+ [116.990618, 34.202346],
|
|
|
+ [116.993520, 34.204017],
|
|
|
+ [116.993547, 34.204941]
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 创建多边形边框
|
|
|
+ this.borderPolygon = new AMap.Polygon({
|
|
|
+ path: borderCoordinates, // 多边形轮廓的节点坐标数组
|
|
|
+ strokeColor: this.borderColor, // 线条颜色
|
|
|
+ strokeWeight: 3, // 线条宽度
|
|
|
+ strokeOpacity: 0.8, // 线条透明度
|
|
|
+ fillOpacity: 0, // 填充透明度,设为0表示不填充
|
|
|
+ strokeStyle: 'solid', // 线样式为实线
|
|
|
+ extData: {
|
|
|
+ type: 'border'
|
|
|
+ },
|
|
|
+ height: 100, // 设置多边形高度,实现墙体效果
|
|
|
+ zIndex: 10
|
|
|
+ });
|
|
|
+
|
|
|
+ // 将边框添加到地图上
|
|
|
+ this.map.add(this.borderPolygon);
|
|
|
+
|
|
|
+ console.log('3D发光边框初始化成功✅');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始边框动画效果(发光闪烁)
|
|
|
+ */
|
|
|
+ startBorderAnimation() {
|
|
|
+ // 清理之前的动画
|
|
|
+ this.stopBorderAnimation();
|
|
|
+
|
|
|
+ let opacity = 0.6;
|
|
|
+ let increasing = true;
|
|
|
+ let hue = 180; // 青色的色相值
|
|
|
+
|
|
|
+ this.animationInterval = setInterval(() => {
|
|
|
+ // 控制透明度变化,实现闪烁效果
|
|
|
+ if (increasing) {
|
|
|
+ opacity += 0.02;
|
|
|
+ if (opacity >= 1.0) {
|
|
|
+ increasing = false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ opacity -= 0.02;
|
|
|
+ if (opacity <= 0.4) {
|
|
|
+ increasing = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 控制颜色变化,实现彩虹发光效果
|
|
|
+ hue = (hue + 0.5) % 360;
|
|
|
+ const animatedColor = this.hslToHex(hue, 100, 70); // HSL to Hex
|
|
|
+
|
|
|
+ if (this.borderPolygon) {
|
|
|
+ this.borderPolygon.setOptions({
|
|
|
+ strokeColor: animatedColor,
|
|
|
+ strokeOpacity: opacity
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 50); // 每50毫秒更新一次动画
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止边框动画效果
|
|
|
+ */
|
|
|
+ stopBorderAnimation() {
|
|
|
+ if (this.animationInterval) {
|
|
|
+ clearInterval(this.animationInterval);
|
|
|
+ this.animationInterval = null;
|
|
|
+ }
|
|
|
+ // 恢复原始颜色和透明度
|
|
|
+ if (this.borderPolygon) {
|
|
|
+ this.borderPolygon.setOptions({
|
|
|
+ strokeColor: this.borderColor,
|
|
|
+ strokeOpacity: 0.8
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * HSL颜色值转换为十六进制
|
|
|
+ * @param {number} h - 色相 (0-360)
|
|
|
+ * @param {number} s - 饱和度 (0-100)
|
|
|
+ * @param {number} l - 亮度 (0-100)
|
|
|
+ * @returns {string} 十六进制颜色值
|
|
|
+ */
|
|
|
+ hslToHex(h, s, l) {
|
|
|
+ h /= 360;
|
|
|
+ s /= 100;
|
|
|
+ l /= 100;
|
|
|
+
|
|
|
+ let r, g, b;
|
|
|
+
|
|
|
+ if (s === 0) {
|
|
|
+ r = g = b = l; // achromatic
|
|
|
+ } else {
|
|
|
+ const hue2rgb = (p, q, t) => {
|
|
|
+ if (t < 0) t += 1;
|
|
|
+ if (t > 1) t -= 1;
|
|
|
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
|
+ if (t < 1 / 2) return q;
|
|
|
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
|
+ return p;
|
|
|
+ };
|
|
|
+
|
|
|
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
|
+ const p = 2 * l - q;
|
|
|
+
|
|
|
+ r = hue2rgb(p, q, h + 1 / 3);
|
|
|
+ g = hue2rgb(p, q, h);
|
|
|
+ b = hue2rgb(p, q, h - 1 / 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ const toHex = x => {
|
|
|
+ const hex = Math.round(x * 255).toString(16);
|
|
|
+ return hex.length === 1 ? '0' + hex : hex;
|
|
|
+ };
|
|
|
+
|
|
|
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换边框颜色(正常状态与告警状态)
|
|
|
+ * @param {boolean} isAlarm - 是否为告警状态
|
|
|
+ */
|
|
|
+ toggleBorderColor(isAlarm = false) {
|
|
|
+ this.isAlarm = isAlarm;
|
|
|
+ this.borderColor = isAlarm ? '#FF0000' : '#3dfcfc'; // 红色或青色
|
|
|
+
|
|
|
+ if (this.borderPolygon) {
|
|
|
+ this.borderPolygon.setOptions({
|
|
|
+ strokeColor: this.borderColor
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新边框样式
|
|
|
+ * @param {object} options - 样式选项
|
|
|
+ */
|
|
|
+ updateBorderOptions(options) {
|
|
|
+ if (this.borderPolygon) {
|
|
|
+ this.borderPolygon.setOptions(options);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.amap-container {
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100vh - 80px);
|
|
|
+}
|
|
|
+
|
|
|
+.control-panel {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ left: 20px;
|
|
|
+ z-index: 1000;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.heatmap-toggle-btn {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.heatmap-toggle-btn:hover {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.heatmap-toggle-btn.active {
|
|
|
+ background-color: #409eff;
|
|
|
+ color: white;
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.map-box {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+#container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.polygon-toggle-btn {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.polygon-toggle-btn:hover {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ transform: translateY(-1px);
|
|
|
+}
|
|
|
+
|
|
|
+.polygon-toggle-btn.active {
|
|
|
+ background-color: #67c23a;
|
|
|
+ /* 绿色背景表示激活状态 */
|
|
|
+ color: white;
|
|
|
+ border-color: #67c23a;
|
|
|
+}
|
|
|
+</style>
|