/**
|
* 高德地图点标记绘制相关
|
*/
|
|
import { map, AMap } from './index'
|
import { useToolboxStore } from '@/stores/toolbox'
|
import util from './util'
|
import * as echarts from 'echarts'
|
import exceedIcon from '@/assets/exceed.png'
|
import exceptionIcon from '@/assets/exception.png'
|
import offlineIcon from '@/assets/offline.png'
|
|
const toolboxStore = useToolboxStore()
|
|
var _massMarks = undefined
|
|
// 状态图标配置
|
const statusIcons = {
|
exceed: exceedIcon, // 油烟浓度超标
|
exception: exceptionIcon, // 供电异常
|
offline: offlineIcon, // 设备或网络异常
|
online: createCustomMarkerOnline(), // 在线状态
|
offlineStatus: createCustomMarkerOffline(), // 离线状态
|
}
|
|
/**
|
* 绘制海量点标记
|
* @param {Array} shops 店铺对象数组
|
* @param {Object} shops[].shop 店铺基本信息
|
* @param {string} shops[].shop.name 店铺名称
|
* @param {string} shops[].shop.address 店铺地址
|
* @param {number} shops[].shop.latitude 纬度
|
* @param {number} shops[].shop.longitude 经度
|
* @param {string} shops[].shop.ringCodeLevel 最新环信码等级
|
* @param {string} shops[].shop.ringCodePublishTime 最新环信码发布时间
|
* @param {boolean} shops[].shop.isOnline 在线状态
|
* @param {number} shops[].shop.exceptionStatus 异常状态(0: 油烟浓度超标, 1: 供电异常, 2: 设备或网络异常, 3: 无异常)
|
* @param {Array} shops[].recentData 近1小时的监测数据
|
* @param {string} shops[].recentData[].sampleTime 数据采样时间
|
* @param {number} shops[].recentData[].oilSmokeConcentration 油烟浓度
|
* @param {number} shops[].recentData[].purifierCurrent 净化器电流
|
* @param {number} shops[].recentData[].fanCurrent 风机电流
|
*/
|
function drawMassMarks(shops) {
|
// 配置样式
|
const massMarksStyle = [
|
{
|
url: statusIcons.exceed,
|
size: new AMap.Size(20, 20),
|
anchor: new AMap.Pixel(10, 10),
|
},
|
{
|
url: statusIcons.exception,
|
size: new AMap.Size(20, 20),
|
anchor: new AMap.Pixel(10, 10),
|
},
|
{
|
url: statusIcons.offline,
|
size: new AMap.Size(20, 20),
|
anchor: new AMap.Pixel(10, 10),
|
},
|
{
|
url: statusIcons.online,
|
size: new AMap.Size(32, 32),
|
anchor: new AMap.Pixel(10, 10),
|
},
|
{
|
url: statusIcons.offlineStatus,
|
size: new AMap.Size(32, 32),
|
anchor: new AMap.Pixel(10, 10),
|
},
|
]
|
// 准备海量点数据
|
const massMarksData = shops.map((shop, index) => {
|
// 根据异常状态和在线状态获取样式索引
|
let styleIndex = 3 // 默认在线状态
|
|
if (shop.shop.exceptionStatus !== undefined) {
|
switch (shop.shop.exceptionStatus) {
|
case 0: // 油烟浓度超标
|
styleIndex = 0
|
break
|
case 1: // 供电异常
|
styleIndex = 1
|
break
|
case 2: // 设备或网络异常
|
styleIndex = 2
|
break
|
case 3: // 无异常,根据在线状态决定
|
styleIndex = shop.shop.isOnline ? 3 : 4
|
break
|
default:
|
styleIndex = shop.shop.isOnline ? 3 : 4
|
}
|
} else {
|
// 没有异常状态时,根据在线状态决定
|
styleIndex = shop.shop.isOnline ? 3 : 4
|
}
|
|
return {
|
id: index,
|
name: shop.shop.name,
|
lnglat: [shop.shop.longitude, shop.shop.latitude],
|
style: styleIndex, // 样式索引,对应 massMarksStyle
|
extData: shop, // 存储完整的店铺信息
|
}
|
})
|
|
// 清除现有的海量点标记
|
if (window.massMarks) {
|
window.massMarks.clear()
|
}
|
|
// 创建新的海量点标记
|
window.massMarks = new AMap.MassMarks(massMarksData, {
|
zIndex: 111,
|
cursor: 'pointer',
|
style: massMarksStyle,
|
})
|
|
// 添加点击事件
|
window.massMarks.on('click', function (e) {
|
const shop = e.data.extData
|
showShopInfoWindow(shop)
|
})
|
|
var marker = new AMap.Marker({
|
content: ' ',
|
map: map,
|
offset: new AMap.Pixel(13, 12),
|
})
|
|
var timeout
|
window.massMarks.on('mouseover', (e) => {
|
if (timeout) {
|
clearTimeout(timeout)
|
}
|
marker.setPosition(e.data.lnglat)
|
marker.setLabel({ content: e.data.name })
|
map.add(marker)
|
timeout = setTimeout(() => {
|
map.remove(marker)
|
}, 2000)
|
})
|
|
// 添加到地图
|
window.massMarks.setMap(map)
|
util.setBound(massMarksData.map((item) => item.lnglat))
|
}
|
|
/**
|
* 根据环信码等级获取颜色
|
* @param {string} level 环信码等级
|
* @returns {string} 颜色值
|
*/
|
function getColorByRingCodeLevel(level) {
|
switch (level + '') {
|
case '0':
|
return '#52c41a' // 绿色
|
case '1':
|
return '#faad14' // 黄色
|
case '2':
|
return '#f5222d' // 红色
|
default:
|
return '#8c8c8c' // 灰色
|
}
|
}
|
|
/**
|
* 根据环信码等级获取中文
|
* @param {string} level 环信码等级
|
* @returns {string} 中文表示
|
*/
|
function getRingCodeLevelText(level) {
|
switch (level + '') {
|
case '0':
|
return '绿色'
|
case '1':
|
return '黄色'
|
case '2':
|
return '红色'
|
default:
|
return '未知'
|
}
|
}
|
|
/**
|
* 根据异常状态获取中文
|
* @param {number} status 异常状态
|
* @returns {string} 中文表示
|
*/
|
function getExceptionStatusText(status) {
|
switch (status + '') {
|
case '0':
|
return '油烟浓度超标'
|
case '1':
|
return '供电异常'
|
case '2':
|
return '设备或网络异常'
|
case '3':
|
return '无异常'
|
default:
|
return '未知状态'
|
}
|
}
|
|
/**
|
* 根据异常状态获取颜色
|
* @param {number} status 异常状态
|
* @returns {string} 颜色值
|
*/
|
function getColorByExceptionStatus(status) {
|
switch (status + '') {
|
case '0':
|
return '#f5222d' // 油烟浓度超标 - 红色
|
case '1':
|
return '#faad14' // 供电异常 - 黄色
|
case '2':
|
return '#8c8c8c' // 设备或网络异常 - 灰色
|
case '3':
|
return '#52c41a' // 无异常 - 绿色
|
default:
|
return '#8c8c8c' // 灰色
|
}
|
}
|
|
/**
|
* 创建自定义标记
|
* @param {string} color 标记颜色
|
* @returns {string} 标记的SVG URL
|
*/
|
function createCustomMarker(color) {
|
const svg = `
|
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
<circle cx="10" cy="10" r="8" fill="${color}" stroke="white" stroke-width="2"/>
|
<circle cx="10" cy="10" r="3" fill="white"/>
|
</svg>
|
`
|
return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
|
}
|
|
/**
|
* 创建在线状态的SVG标记(油烟监测设备形象)
|
* @returns {string} 标记的SVG URL
|
*/
|
function createCustomMarkerOnline() {
|
const svg = `
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
<!-- 设备主体 - 圆角矩形 -->
|
<rect x="5" y="8" width="22" height="16" rx="3" fill="#52c41a" stroke="white" stroke-width="2"/>
|
|
<!-- 设备顶部 - 弧形 -->
|
<path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#389e0d"/>
|
|
<!-- 设备底部 - 弧形 -->
|
<path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#389e0d"/>
|
|
<!-- 设备显示屏 -->
|
<rect x="8" y="11" width="16" height="10" rx="2" fill="white"/>
|
|
<!-- 显示屏数据 -->
|
<path d="M11 14 L21 14" stroke="#52c41a" stroke-width="1.5"/>
|
<path d="M11 17 L18 17" stroke="#52c41a" stroke-width="1.5"/>
|
<path d="M11 20 L15 20" stroke="#52c41a" stroke-width="1.5"/>
|
|
<!-- 设备天线 -->
|
<line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/>
|
<circle cx="16" cy="3" r="1.5" fill="white"/>
|
|
<!-- 设备指示灯 -->
|
<circle cx="27" cy="16" r="3" fill="#ffffff"/>
|
<circle cx="27" cy="16" r="1.5" fill="#52c41a"/>
|
|
<!-- 装饰线条 -->
|
<line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/>
|
<line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/>
|
</svg>
|
`
|
return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
|
}
|
|
/**
|
* 创建离线状态的SVG标记(油烟监测设备形象)
|
* @returns {string} 标记的SVG URL
|
*/
|
function createCustomMarkerOffline() {
|
const svg = `
|
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
<!-- 设备主体 - 圆角矩形 -->
|
<rect x="5" y="8" width="22" height="16" rx="3" fill="#8c8c8c" stroke="white" stroke-width="2"/>
|
|
<!-- 设备顶部 - 弧形 -->
|
<path d="M5 8 Q16 3 27 8" stroke="white" stroke-width="2" fill="#666666"/>
|
|
<!-- 设备底部 - 弧形 -->
|
<path d="M5 24 Q16 29 27 24" stroke="white" stroke-width="2" fill="#666666"/>
|
|
<!-- 设备显示屏 -->
|
<rect x="8" y="11" width="16" height="10" rx="2" fill="white"/>
|
|
<!-- 显示屏无数据 - 交叉线 -->
|
<line x1="11" y1="12" x2="21" y2="22" stroke="#8c8c8c" stroke-width="2"/>
|
<line x1="11" y1="22" x2="21" y2="12" stroke="#8c8c8c" stroke-width="2"/>
|
|
<!-- 设备天线 -->
|
<line x1="16" y1="8" x2="16" y2="3" stroke="white" stroke-width="1.5"/>
|
<circle cx="16" cy="3" r="1.5" fill="white"/>
|
|
<!-- 设备指示灯 -->
|
<circle cx="27" cy="16" r="3" fill="#ffffff"/>
|
<circle cx="27" cy="16" r="1.5" fill="#8c8c8c"/>
|
|
<!-- 装饰线条 -->
|
<line x1="5" y1="13" x2="6" y2="13" stroke="white" stroke-width="1.5"/>
|
<line x1="5" y1="19" x2="6" y2="19" stroke="white" stroke-width="1.5"/>
|
</svg>
|
`
|
return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))
|
}
|
|
/**
|
* 显示店铺信息窗口
|
* @param {Object} shop 店铺对象
|
*/
|
function showShopInfoWindow(shop) {
|
// 准备信息窗口内容
|
// 获取在线状态文本
|
const onlineStatusText = shop.shop.isOnline ? '在线' : '离线'
|
// 获取异常状态文本
|
const exceptionStatusText = getExceptionStatusText(shop.shop.exceptionStatus)
|
// 获取异常状态颜色
|
const exceptionStatusColor = getColorByExceptionStatus(shop.shop.exceptionStatus)
|
|
// 根据状态获取对应的图标
|
let statusIcon = statusIcons.online // 默认在线图标
|
if (shop.shop.exceptionStatus !== undefined) {
|
switch (shop.shop.exceptionStatus) {
|
case 0:
|
statusIcon = statusIcons.exceed
|
break
|
case 1:
|
statusIcon = statusIcons.exception
|
break
|
case 2:
|
statusIcon = statusIcons.offline
|
break
|
case 3:
|
statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
|
break
|
default:
|
statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
|
}
|
} else {
|
statusIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
|
}
|
|
// 根据在线状态获取图标
|
const onlineIcon = shop.shop.isOnline ? statusIcons.online : statusIcons.offlineStatus
|
|
// 根据异常状态获取图标
|
let exceptionIcon = statusIcons.online // 默认在线图标
|
if (shop.shop.exceptionStatus !== undefined) {
|
switch (shop.shop.exceptionStatus) {
|
case 0:
|
exceptionIcon = statusIcons.exceed
|
break
|
case 1:
|
exceptionIcon = statusIcons.exception
|
break
|
case 2:
|
exceptionIcon = statusIcons.offline
|
break
|
case 3:
|
exceptionIcon = statusIcons.online
|
break
|
default:
|
exceptionIcon = statusIcons.online
|
}
|
}
|
|
const content = `
|
<div style="padding: 10px; width: 400px;">
|
<h3 style="margin: 0 0 10px 0; color: #333;">${shop.shop.name}</h3>
|
<div style="font-size: 14px; line-height: 1.5;">
|
<p><strong>地址:</strong>${shop.shop.address}</p>
|
<p><strong>在线状态:</strong><span style="color: ${shop.shop.isOnline ? '#52c41a' : '#8c8c8c'}">${onlineStatusText}</span> </p>
|
<p><strong>异常状态:</strong><span style="color: ${exceptionStatusColor}">${exceptionStatusText}</span></p>
|
<p><strong>环信码等级:</strong><span style="color: ${getColorByRingCodeLevel(shop.shop.ringCodeLevel)}">${getRingCodeLevelText(shop.shop.ringCodeLevel)}</span></p>
|
<p><strong>环信码发布时间:</strong>${shop.shop.ringCodePublishTime}</p>
|
<h4 style="margin: 10px 0 5px 0; color: #666;">近1小时监测数据</h4>
|
<div id="infowindowChartContainer" style="width: 100%; height: 250px;"></div>
|
</div>
|
</div>
|
`
|
|
// 清除现有的信息窗口
|
if (window.infoWindow) {
|
window.infoWindow.close()
|
}
|
|
// 创建新的信息窗口
|
window.infoWindow = new AMap.InfoWindow({
|
content: content,
|
size: new AMap.Size(400, 400),
|
offset: new AMap.Pixel(-24, -80),
|
})
|
|
// 打开信息窗口
|
window.infoWindow.open(map, [shop.shop.longitude, shop.shop.latitude])
|
|
// 等待信息窗口加载完成后初始化图表
|
setTimeout(() => {
|
const chartdom = document.getElementById('infowindowChartContainer')
|
if (chartdom) {
|
initChart(chartdom, shop.recentData)
|
}
|
}, 100)
|
}
|
|
/**
|
* 初始化监测数据图表
|
* @param {Array} data 监测数据
|
*/
|
function initChart(chartdom, data) {
|
// 准备图表数据
|
const times = data.map((item) => item.sampleTime)
|
const oilSmokeData = data.map((item) => item.oilSmokeConcentration)
|
const purifierData = data.map((item) => item.purifierCurrent)
|
const fanData = data.map((item) => item.fanCurrent)
|
|
// 初始化图表
|
const chart = echarts.init(chartdom)
|
|
// 图表配置
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross',
|
label: {
|
backgroundColor: '#6a7985',
|
},
|
},
|
},
|
legend: {
|
data: ['油烟浓度', '净化器电流', '风机电流'],
|
top: 0,
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true,
|
},
|
xAxis: [
|
{
|
type: 'category',
|
boundaryGap: false,
|
data: times.map((time) => time.split(' ')[1]),
|
axisLabel: {
|
rotate: 0,
|
fontSize: 10,
|
},
|
},
|
],
|
yAxis: [
|
{
|
type: 'value',
|
name: '油烟浓度 (mg/m³)',
|
position: 'left',
|
left: '30%',
|
},
|
{
|
type: 'value',
|
name: '电流 (A)',
|
position: 'right',
|
},
|
],
|
series: [
|
{
|
name: '油烟浓度',
|
type: 'line',
|
data: oilSmokeData,
|
yAxisIndex: 0,
|
smooth: true,
|
},
|
{
|
name: '净化器电流',
|
type: 'line',
|
data: purifierData,
|
yAxisIndex: 1,
|
smooth: true,
|
},
|
{
|
name: '风机电流',
|
type: 'line',
|
data: fanData,
|
yAxisIndex: 1,
|
smooth: true,
|
},
|
],
|
}
|
|
// 应用配置
|
chart.setOption(option)
|
|
// 响应式处理
|
window.addEventListener('resize', function () {
|
chart.resize()
|
})
|
}
|
|
/**
|
* 清除海量点标记
|
*/
|
function clearMassMarks() {
|
if (window.massMarks) {
|
window.massMarks.clear()
|
window.massMarks.setMap(null)
|
window.massMarks = null
|
}
|
|
if (window.infoWindow) {
|
window.infoWindow.close()
|
window.infoWindow = null
|
}
|
}
|
|
export default {
|
drawMassMarks,
|
clearMassMarks,
|
// /**
|
// * 绘制海量点标记
|
// * @param fDatas 完整监测数据
|
// * @param _factor 当前展示的监测因子对象
|
// */
|
// drawMassMarks(fDatas, _factor, onClick) {
|
// if (!toolboxStore.dataMarkerStatus) {
|
// return;
|
// }
|
// this.clearMassMarks();
|
// const lnglats = fDatas.lnglats_GD;
|
// var data = [];
|
// for (let i = 0; i < lnglats.length; i++) {
|
// data.push({
|
// lnglat: lnglats[i], //点标记位置
|
// name: `${fDatas.times[i]}<br/>${_factor.factorName}: ${_factor.datas[i].factorData} μg/m³`,
|
// id: i
|
// });
|
// }
|
|
// // 创建样式对象
|
// var styleObject = {
|
// url: 'https://a.amap.com/jsapi_demos/static/images/mass1.png',
|
// // url: './asset/mipmap/ic_up_white.png', // 图标地址
|
|
// size: new AMap.Size(11, 11), // 图标大小
|
|
// anchor: new AMap.Pixel(5, 5) // 图标显示位置偏移量,基准点为图标左上角
|
// };
|
|
// var massMarks = new AMap.MassMarks(data, {
|
// zIndex: 5, // 海量点图层叠加的顺序
|
// zooms: [15, 18], // 在指定地图缩放级别范围内展示海量点图层
|
// style: styleObject // 设置样式对象
|
// });
|
// massMarks.on('click', (event) => {
|
// const i = event.data.id;
|
// // 3. 自定义点击事件
|
// onClick(i);
|
// });
|
|
// var marker = new AMap.Marker({
|
// content: ' ',
|
// map: map,
|
|
// offset: new AMap.Pixel(13, 12)
|
// });
|
// var timeout;
|
// massMarks.on('mouseover', (e) => {
|
// if (timeout) {
|
// clearTimeout(timeout);
|
// }
|
// marker.setPosition(e.data.lnglat);
|
// marker.setLabel({ content: e.data.name });
|
// map.add(marker);
|
// timeout = setTimeout(() => {
|
// map.remove(marker);
|
// }, 2000);
|
// });
|
// _massMarks = massMarks;
|
// map.add(massMarks);
|
// },
|
// clearMassMarks() {
|
// if (_massMarks) {
|
// map.remove(_massMarks)
|
// _massMarks = undefined
|
// }
|
// },
|
|
/**
|
* 创建标记点
|
* @param {string | Array} img 图标或图标数组
|
* @param {Array} dataList 监测数据
|
* @param {boolean} collision 标注避让
|
* @returns
|
*/
|
createLabelMarks(img, dataList, collision = true, showTxt = true) {
|
const layer = new AMap.LabelsLayer({
|
zooms: [3, 20],
|
zIndex: 1000,
|
// 开启标注避让,默认为开启,v1.4.15 新增属性
|
collision: collision,
|
// 开启标注淡入动画,默认为开启,v1.4.15 新增属性
|
animation: true,
|
})
|
|
map.add(layer)
|
|
// var markers = [];
|
for (var i = 0; i < dataList.length; i++) {
|
const data = dataList[i]
|
var curData = {
|
name: data.name,
|
position: [data.longitude, data.latitude],
|
zooms: [10, 20],
|
opacity: 1,
|
zIndex: 10,
|
icon: {
|
type: 'image',
|
image: typeof img === 'string' ? img : img[i],
|
// clipOrigin: [14, 92],
|
// clipSize: [50, 68],
|
size: [30, 30],
|
anchor: 'bottom-center',
|
angel: 0,
|
retina: true,
|
},
|
text: {
|
content: showTxt ? data.name : '',
|
direction: 'top',
|
offset: [0, -5],
|
style: {
|
fontSize: 16,
|
fontWeight: 'normal',
|
fillColor: '#fff',
|
strokeColor: '#333',
|
strokeWidth: 0,
|
backgroundColor: '#122b54a9',
|
},
|
},
|
}
|
curData.extData = {
|
index: i,
|
}
|
|
var labelMarker = new AMap.LabelMarker(curData)
|
|
// markers.push(labelMarker);
|
|
layer.add(labelMarker)
|
}
|
|
return layer
|
},
|
|
createMarker({ position, img, title, content, label = '', extData }) {
|
//创建 AMap.Icon 实例:
|
const icon = new AMap.Icon({
|
size: new AMap.Size(30, 30), //图标尺寸
|
image: img, //Icon 的图像
|
// imageOffset: new AMap.Pixel(-9, -3), //图像相对展示区域的偏移量,适于雪碧图等
|
imageSize: new AMap.Size(30, 30), //根据所设置的大小拉伸或压缩图片
|
})
|
const marker = new AMap.Marker({
|
position: position,
|
// offset: new AMap.Pixel(-13, -30),
|
icon: icon, //添加 icon 图标 URL
|
content: content,
|
title: title,
|
label: {
|
content: label,
|
direction: 'bottom',
|
},
|
extData,
|
})
|
// map.add(marker);
|
return marker
|
},
|
}
|