可视化大屏系列(4):高德地图集成与动态热区渲染

可视化大屏系列(4):高德地图集成与动态热区渲染

在这里插入图片描述

在智慧工地可视化大屏中,地图模块是信息密度最高、交互频率最高的关键组件。本节将围绕“高德地图集成”与“动态热区渲染”展开,构建一套支持区域划分、实时定位、状态标注、交互控制的地图系统。


一、地图组件集成方案

1. 安装高德地图 Vue 插件

在 Vue3 项目中推荐使用 @vuemap/vue-amap,它对高德地图进行了 Vue3 的适配封装,使用方便、功能全面。

✅ 安装插件
npm install @vuemap/vue-amap
✅ 引入并初始化(main.ts)
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import AMapLoader from '@vuemap/vue-amap'
import '@vuemap/vue-amap/dist/style.css' // 样式必须引入

const app = createApp(App)

app.use(AMapLoader, {
  key: '你的高德Key', // 替换为你申请的高德 Key
  version: '2.0',
  plugins: [
    'AMap.Marker',
    'AMap.Polygon',
    'AMap.InfoWindow',
    'AMap.ToolBar',
    'AMap.Scale'
  ]
})

app.mount('#app')

2. 封装地图基础容器组件 MapPanel.vue

地图组件作为容器,需要对地图大小、响应式处理、初始视图进行封装。

<!-- src/components/map/MapPanel.vue -->
<template>
  <div class="map-container">
    <el-amap
      ref="amapRef"
      :center="center"
      :zoom="zoom"
      class="w-full h-full"
    >
      <slot />
    </el-amap>
  </div>
</template>

<script setup>
import { ref, defineProps } from 'vue'

defineProps({
  center: {
    type: Array,
    default: () => [116.397428, 39.90923] // 默认北京天安门
  },
  zoom: {
    type: Number,
    default: 17
  }
})

const amapRef = ref()
</script>

<style scoped>
.map-container {
  width: 100%;
  height: 100%;
  position: relative;
}
</style>

3. 动态创建地图 Polygon、Marker 的组件封装

为了模块化开发,可以将区域绘制、人员设备定位拆分成独立子组件。

示例:区域绘制组件 MapPolygon.vue
<!-- src/components/map/MapPolygon.vue -->
<template>
  <el-amap-polygon
    v-for="(area, index) in areas"
    :key="index"
    :path="area.path"
    :stroke-color="getStrokeColor(area.status)"
    :fill-color="getFillColor(area.status)"
    :fill-opacity="0.4"
    :stroke-weight="2"
    @click="() => handleAreaClick(area)"
  />
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  areas: Array
})

const emit = defineEmits(['areaClick'])

const getStrokeColor = (status) => {
  return status === 'error' ? '#F56C6C' : '#409EFF'
}

const getFillColor = (status) => {
  const map = {
    normal: '#67C23A',
    warning: '#E6A23C',
    error: '#F56C6C',
    off: '#909399'
  }
  return map[status] || '#a0cfff'
}

const handleAreaClick = (area) => {
  emit('areaClick', area)
}
</script>
示例:设备定位组件 MapDevice.vue
<!-- src/components/map/MapDevice.vue -->
<template>
  <el-amap-marker
    v-for="(device, index) in devices"
    :key="index"
    :position="device.position"
    :title="device.name"
    :icon="deviceIcon"
  />
</template>

<script setup>
import { defineProps } from 'vue'
import deviceIconUrl from '@/assets/device-icon.png'

const props = defineProps({
  devices: Array
})

const deviceIcon = {
  image: deviceIconUrl,
  size: [32, 32]
}
</script>

4. 实际使用示例

<template>
  <MapPanel :center="[116.39, 39.91]" :zoom="17">
    <MapPolygon :areas="areaList" @areaClick="handleAreaClick" />
    <MapDevice :devices="deviceList" />
  </MapPanel>
</template>

<script setup>
import MapPanel from '@/components/map/MapPanel.vue'
import MapPolygon from '@/components/map/MapPolygon.vue'
import MapDevice from '@/components/map/MapDevice.vue'

const areaList = [
  {
    name: '基坑区域',
    path: [
      [116.3969, 39.9083],
      [116.3977, 39.9089],
      [116.3970, 39.9094]
    ],
    status: 'normal'
  }
]

const deviceList = [
  {
    name: '塔吊1号',
    position: [116.3974, 39.9092]
  }
]

const handleAreaClick = (area) => {
  console.log('点击区域:', area.name)
}
</script>
功能模块技术栈说明
地图底图vue-amap基于 Vue3 组件化封装高德地图
区域热区AMap.Polygon区域状态动态绑定颜色
定位设备AMap.Marker支持实时坐标更新
容器封装MapPanel.vue自适应布局,统一中心与缩放
插件注册ToolBar, InfoWindow, Scale辅助工具组件

二、工地区域划分与热区绘制

我们将通过高德地图 Polygon 实现区域绘制,结合后台接口获取数据,并支持热区颜色状态动态变化、点击联动信息弹窗等功能。


1. 区域数据结构设计(后端接口返回)

从后端获取区域数据,每个区域包含名称、坐标点位、状态等信息:

[
  {
    "id": 1,
    "name": "基坑作业区",
    "points": [
      [116.3969, 39.9083],
      [116.3977, 39.9089],
      [116.3970, 39.9094]
    ],
    "status": "normal", 
    "info": "施工中,安全正常"
  },
  {
    "id": 2,
    "name": "材料堆放区",
    "points": [
      [116.3980, 39.9080],
      [116.3985, 39.9088],
      [116.3979, 39.9091]
    ],
    "status": "warning",
    "info": "临时堆放材料,请注意安全"
  }
]

2. 热区绘制组件封装(MapPolygon.vue)

<!-- src/components/map/MapPolygon.vue -->
<template>
  <el-amap-polygon
    v-for="area in areas"
    :key="area.id"
    :path="area.points"
    :stroke-color="getStrokeColor(area.status)"
    :fill-color="getFillColor(area.status)"
    :fill-opacity="0.5"
    :stroke-weight="2"
    :ext-data="area"
    @click="handleClick(area)"
  />
</template>

<script setup>
const props = defineProps({
  areas: Array
})

const emit = defineEmits(['areaClick'])

const getStrokeColor = (status) => {
  switch (status) {
    case 'normal': return '#67C23A'
    case 'warning': return '#E6A23C'
    case 'danger': return '#F56C6C'
    default: return '#409EFF'
  }
}

const getFillColor = (status) => {
  switch (status) {
    case 'normal': return '#DFF0D8'
    case 'warning': return '#FCF8E3'
    case 'danger': return '#F2DEDE'
    default: return '#E6F7FF'
  }
}

const handleClick = (area) => {
  emit('areaClick', area)
}
</script>

3. 页面中使用区域组件 + 获取数据

<template>
  <MapPanel :center="[116.3974, 39.9093]" :zoom="17">
    <MapPolygon :areas="areaList" @areaClick="onAreaClick" />
  </MapPanel>

  <!-- 信息弹窗 -->
  <el-dialog v-model="dialogVisible" title="区域信息" width="30%">
    <div>
      <p><strong>名称:</strong>{{ currentArea.name }}</p>
      <p><strong>状态:</strong>{{ currentArea.status }}</p>
      <p><strong>详情:</strong>{{ currentArea.info }}</p>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import MapPanel from '@/components/map/MapPanel.vue'
import MapPolygon from '@/components/map/MapPolygon.vue'
import { getRequest } from '@/utils/request'

const areaList = ref([])
const currentArea = ref({})
const dialogVisible = ref(false)

const fetchAreaData = async () => {
  try {
    const res = await getRequest('/api/map/areas')  // 替换为真实接口
    areaList.value = res.data
  } catch (err) {
    console.error('区域数据加载失败', err)
  }
}

const onAreaClick = (area) => {
  currentArea.value = area
  dialogVisible.value = true
}

onMounted(fetchAreaData)
</script>

4. 拓展功能建议(可选)

功能模块说明
实时状态更新每 5 秒轮询后端,动态更新热区颜色
多状态图例在地图侧边增加状态图例,辅助理解
动态添加与删除区域通过管理后台动态编辑工地区域
鼠标悬停显示 Tooltip使用 AMap.InfoWindow 或浮层展示简要信息
要点技术
区域绘制el-amap-polygon
状态动态渲染颜色映射函数 getFillColor()
区域点击联动事件回调 @click="handleClick"
数据来源后端接口 /api/map/areas
UI 展示Element Plus 弹窗

三、设备/人员定位模块设计与实现

我们通过高德地图的 Marker 组件实现定位展示,结合 WebSocket 或定时轮询方式进行动态刷新。


1. 后端返回数据结构示例

[
  {
    "id": "dev001",
    "type": "设备",
    "name": "塔吊-01",
    "position": [116.3963, 39.9086],
    "status": "online",
    "extraInfo": "运行正常"
  },
  {
    "id": "person101",
    "type": "人员",
    "name": "张三",
    "position": [116.3971, 39.9091],
    "status": "working",
    "extraInfo": "施工中"
  }
]

2. Marker 组件封装(MapMarkers.vue)

<!-- src/components/map/MapMarkers.vue -->
<template>
  <el-amap-marker
    v-for="item in list"
    :key="item.id"
    :position="item.position"
    :icon="getIcon(item)"
    :ext-data="item"
    :offset="[-10, -30]"
    @click="handleClick(item)"
  />
</template>

<script setup>
const props = defineProps({
  list: Array
})
const emit = defineEmits(['markerClick'])

const getIcon = (item) => {
  if (item.type === '设备') {
    return item.status === 'online'
      ? '/icons/device-online.png'
      : '/icons/device-offline.png'
  } else if (item.type === '人员') {
    return '/icons/worker.png'
  }
  return '/icons/default.png'
}

const handleClick = (item) => {
  emit('markerClick', item)
}
</script>

💡 建议图标使用 32x32 或 48x48 的 PNG 图,支持状态区分。


3. 页面集成组件 + 弹窗展示

<template>
  <MapPanel :center="[116.3974, 39.9093]" :zoom="17">
    <MapPolygon :areas="areaList" />
    <MapMarkers :list="markerList" @markerClick="onMarkerClick" />
  </MapPanel>

  <el-dialog v-model="dialogVisible" title="位置详情" width="30%">
    <div>
      <p><strong>名称:</strong>{{ currentItem.name }}</p>
      <p><strong>类型:</strong>{{ currentItem.type }}</p>
      <p><strong>状态:</strong>{{ currentItem.status }}</p>
      <p><strong>详情:</strong>{{ currentItem.extraInfo }}</p>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import MapPanel from '@/components/map/MapPanel.vue'
import MapPolygon from '@/components/map/MapPolygon.vue'
import MapMarkers from '@/components/map/MapMarkers.vue'
import { getRequest } from '@/utils/request'

const markerList = ref([])
const currentItem = ref({})
const dialogVisible = ref(false)

const fetchMarkers = async () => {
  try {
    const res = await getRequest('/api/map/positions') // 替换接口
    markerList.value = res.data
  } catch (err) {
    console.error('位置数据加载失败', err)
  }
}

const onMarkerClick = (item) => {
  currentItem.value = item
  dialogVisible.value = true
}

onMounted(() => {
  fetchMarkers()
  // 每30秒自动刷新
  setInterval(fetchMarkers, 30000)
})
</script>

4. 可选拓展功能建议

功能说明
状态动画效果使用带动画的 icon 或 CSS 动效提升可视化体验
Marker 聚合对密集人员或设备使用点聚合(AMap.MarkerCluster)
WebSocket 推送使用 Socket 替代轮询,实现秒级刷新
点击后地图定位点击人员定位可自动居中放大(map.setCenter()

模块技术实现
实时定位高德地图 el-amap-marker
图标切换根据类型/状态自定义 icon
数据更新轮询接口 / WebSocket
联动弹窗Element Plus 弹窗
UI 提示悬停 Tooltip、状态图例

四、热区自适应展示与高亮反馈

为了在不同分辨率、不同缩放层级下保持良好的视觉效果和交互体验,我们实现了 热区的自适应绘制与状态高亮反馈功能


1. 热区数据结构与后端接口

[
  {
    "id": "zone001",
    "name": "塔吊作业区",
    "points": [
      [116.3962, 39.9083],
      [116.3965, 39.9085],
      [116.3964, 39.9088],
      [116.3961, 39.9086]
    ],
    "status": "active"
  },
  {
    "id": "zone002",
    "name": "材料堆放区",
    "points": [
      [116.3970, 39.9082],
      [116.3974, 39.9084],
      [116.3972, 39.9087]
    ],
    "status": "warning"
  }
]

2. 热区绘制组件封装(MapHeatZones.vue

<template>
  <el-amap-polygon
    v-for="zone in zones"
    :key="zone.id"
    :path="zone.points"
    :stroke-color="getStroke(zone.status)"
    :fill-color="getFill(zone.status)"
    :stroke-opacity="1"
    :fill-opacity="0.4"
    :stroke-weight="2"
    @click="handleClick(zone)"
  />
</template>

<script setup>
const props = defineProps({
  zones: Array
})
const emit = defineEmits(['zoneClick'])

const getStroke = (status) => {
  return status === 'active' ? '#00bfa5' : status === 'warning' ? '#ff9800' : '#ccc'
}
const getFill = (status) => {
  return status === 'active' ? '#b2dfdb' : status === 'warning' ? '#ffe082' : '#e0e0e0'
}

const handleClick = (zone) => {
  emit('zoneClick', zone)
}
</script>

📝 可拓展状态色:normal(灰)、active(绿)、warning(黄)、danger(红)


3. 页面集成与状态反馈联动

<MapPanel :center="center" :zoom="17">
  <MapHeatZones :zones="zoneList" @zoneClick="onZoneClick" />
</MapPanel>

<el-drawer v-model="drawerVisible" title="区域详情" direction="rtl" size="300px">
  <p><strong>区域名称:</strong>{{ currentZone.name }}</p>
  <p><strong>状态:</strong>{{ currentZone.status }}</p>
  <!-- 可显示相关人员、事件、设备等 -->
</el-drawer>
const zoneList = ref([])
const currentZone = ref({})
const drawerVisible = ref(false)

const onZoneClick = (zone) => {
  currentZone.value = zone
  drawerVisible.value = true
}

// 获取热区数据
const fetchZones = async () => {
  const res = await getRequest('/api/map/zones')
  zoneList.value = res.data
}

onMounted(() => {
  fetchZones()
})

4. 自适应与高亮优化技巧

优化项实现方式
响应式缩放多边形 path 使用经纬度适配,无需额外缩放处理
高亮边框点击或 hover 时动态改变 strokeWidth / strokeColor
状态动效可为 warning/danger 区域添加动画边框或闪烁 CSS 效果
图层控制设置 zIndex 与图层顺序,避免与人员/设备标记冲突
提示气泡el-amap-info-window 支持点击或悬停时弹出说明

模块技术实现
热区绘制el-amap-polygon 多边形组件
自适应展示根据经纬度坐标动态绘制
状态反馈不同状态填充色与边框样式区分
信息联动点击弹窗展示区域详情
动态刷新定时更新热区数据或接入推送机制

五、地图图层控制与交互联动

实现图层的灵活控制与与其他模块(如图表、面板、列表等)的联动,以增强数据可视性与操作便捷性。


1. 图层模块划分

为了提升地图的模块化管理能力,我们将地图中的展示内容划分为多个“图层”,包括但不限于:

图层名称显示内容
热区图层工地区域划分热区
人员定位图层在线施工人员位置
设备图层塔吊、升降机、摄像头等设备
事件图层告警/巡检/签到记录点

2. 图层开关组件(MapLayerToggle.vue

<template>
  <el-card shadow="hover" class="layer-panel">
    <el-checkbox-group v-model="visibleLayers" @change="emitChange">
      <el-checkbox label="heatzone">热区</el-checkbox>
      <el-checkbox label="person">人员</el-checkbox>
      <el-checkbox label="device">设备</el-checkbox>
      <el-checkbox label="event">事件</el-checkbox>
    </el-checkbox-group>
  </el-card>
</template>

<script setup>
const visibleLayers = ref(['heatzone', 'person', 'device'])
const emit = defineEmits(['update'])

const emitChange = () => {
  emit('update', visibleLayers.value)
}
</script>

<style scoped>
.layer-panel {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 999;
  width: 180px;
  background: white;
}
</style>

3. 地图层级切换逻辑(父组件整合)

<MapLayerToggle @update="handleLayerUpdate" />
<el-amap>
  <MapHeatZones v-if="layers.includes('heatzone')" :zones="zoneList" />
  <MapPersons v-if="layers.includes('person')" :persons="personList" />
  <MapDevices v-if="layers.includes('device')" :devices="deviceList" />
  <MapEvents v-if="layers.includes('event')" :events="eventList" />
</el-amap>
const layers = ref(['heatzone', 'person', 'device'])

const handleLayerUpdate = (newLayers) => {
  layers.value = newLayers
}

4. 地图与图表/面板联动展示

实现点击地图上的热区、设备或人员后,联动右侧或下方的信息面板或统计图表进行更新:

const currentPerson = ref({})
const currentDevice = ref({})

const handlePersonClick = (person) => {
  currentPerson.value = person
  showPersonDrawer.value = true
}

const handleDeviceClick = (device) => {
  currentDevice.value = device
  showDeviceDrawer.value = true
}

你可以通过组件间事件通信或使用 pinia 共享状态来完成跨模块联动,确保“图-表-列表”三位一体的统一交互体验。


5. 实战技巧总结

功能项技术点实现建议
图层切换checkbox 控制显示使用 v-if 控制图层组件
交互反馈点击 marker 显示弹窗或面板使用 @click + Drawer
联动同步地图和图表共享状态使用 Pinia 管理全局 state
数据更新定时轮询 / WebSocket 实时刷新配合 loading 动画防抖处理
UI 整洁性悬浮图层控制面板 + 动画显示保持右上角常驻入口

六、扩展建议与优化方向

功能方向建议
热区状态刷新使用定时轮询或 WebSocket 推送
多图层叠加支持环境监测/视频图层
设备状态动画提示使用 Marker 动画属性或闪烁
多区域切换支持工区选择与过滤视图
主题与夜间模式支持配合 UI 主题同步切换

当然可以,以下是本篇《可视化大屏系列(4):高德地图集成与动态热区渲染》的结尾部分:


结语:构建真正“可感知”的智慧工地地图大屏

在本篇文章中,我们围绕“地图可视化”这一核心展开,从高德地图组件的集成、热区绘制、动态渲染,到地图图层控制与业务模块的交互联动,全方位搭建起一个真正“能看、能控、能联动”的智慧工地地图系统。

通过模块化封装、响应式布局、数据驱动渲染与高可扩展的事件系统,我们不仅实现了地图的展示,更打造了一个可随业务需求扩展的“空间信息平台”。

地图不仅仅是展示地理位置的载体,更是打通设备、人员、任务等数据的重要桥梁。在智慧工地建设过程中,未来我们还可以结合 GIS、BIM、IoT 等技术,进一步升级地图系统的智能化水平,实现从“看得见”到“看得懂”的跃迁。


下一篇,我们将进入《可视化大屏系列(5)》:设备状态面板与数据分析仪表盘,聚焦设备运行、工况统计、预警机制等方面的数据可视化实现。如果你对后续内容感兴趣,欢迎持续关注!

如需源码封装细节或实战项目部署,也欢迎留言或私信交流。🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈探索者chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值