在前两期实现离线地图初始化以及规划某一特定区域的基础上,本文中主要阐述如何实现在所规划的区域地图上打点,同时在打的点上点击该点出现弹窗的效果。
提示:因本文实现的具体效果部分涉及到前面文章的知识点,如有不明白的小伙伴可进行查看前两篇文章,文章的的实现流程也是按照步骤来的。
目录
一、初始化地图、规划某一特定区域
对于初始化地图以及规划某一特定区域的详细代码在这里就不在重新复述了,有看不懂的小伙伴查看我前面的两篇文章即可,里面详细讲解了关于这部分的内容,详细代码也有。这里因为打点和和实现弹窗需要,所以再重新将代码附上。
<template>
<div id="container" class="map"></div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { CloseIcon } from 'tdesign-icons-vue-next'
import 'ol/ol.css' // 地图样式
import TileLayer from 'ol/layer/Tile' // 瓦片渲染方法
import XYZ from 'ol/source/XYZ'
import { Map, View, Feature } from 'ol' // 地图实例方法、视图方法
import { fromLonLat } from 'ol/proj'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import { Style, Icon, Stroke, Fill, Text, Circle } from 'ol/style'
import { Point, LineString, Polygon } from 'ol/geom'
import GeoJSON from 'ol/format/GeoJSON'
let map = ref(null)
//地图图层参数
const mapView = reactive({
center: fromLonLat([120.299, 31.568]), // 地图中心点
zoom: 10, // 初始缩放级别
minZoom: 10, // 最小缩放级别
maxZoom: 15 // 最大缩放级别
// extent: [116, 29, 125, 31] // 设置地图中心范围
})
const mapUrl = ref(
'这里填写离线地图的瓦片地址'
)
//规划区域
const addGeoJson = async () => {
// 加载数据
const json = await fetch('定区域的json API地址').then(response =>
response.json()
)
const features = new GeoJSON({ featureProjection: 'EPSG:3857' }).readFeatures(json)
const vectorSource = new VectorSource({ features: features })
// 规划区域样式
const areaStyle = feature => {
return new Style({
fill: new Fill({
//矢量图层填充颜色,以及透明度
// color: 'rgba(5,50,86,0.6)'
color: 'rgba(0, 153, 148, 0.5)'
}),
stroke: new Stroke({
//边界样式
color: 'rgba(73,242,242,0.8)',
width: 3
}),
text: new Text({
//文本样式
text: feature.get('name'),
// 设置文本样式
textAlign: 'center',
textBaseline: 'bottom',
padding: [5, 10, 5, 10],
font: '14px Calibri,sans-serif',
fill: new Fill({
color: '#000'
}),
stroke: new Stroke({
color: '#fff',
width: 3
})
})
})
}
const lineLayer = new VectorLayer({
zIndex: 99,
source: vectorSource,
style: areaStyle
})
map.value.addLayer(lineLayer) // 把图层添加到地图
}
// 初始化地图
const init = () => {
// 使用瓦片渲染方法
const tileLayer = new TileLayer({
source: new XYZ({
url: mapUrl.value
})
})
map.value = new Map({
layers: [tileLayer],
view: new View(mapView),
target: 'container'
})
addGeoJson()
}
onMounted(() => {
init()
})
</script>
<style lang="less" scoped>
#container {
width: 100%;
height: 100%;
position: absolute;
}
</style>
二、模拟传入的打点数据
因为考虑到做项目可能地图的打点数据来于后端,这里就简单模拟一下具体打点的后端数据格式,方便前端能取,正常显示地图上的标注点。强调一下这个数据只是为了便于前端正常查看,若正常做项目,数据应该是后端传给前端,points中的数据为一个空数组,前端直接取后端给的数据即可。数据中的type:便于后续打点时区分标注点的样式,description:为该点所对应的弹窗内容。
// 标注点数据
const points = ref([
{
lon: 120.303543,
lat: 31.681019,
type: '220',
description: [
{
name: '220kv实例变',
loadRate: 1,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.296595,
lat: 31.575706,
type: '500',
description: [
{
name: '500kv实例',
loadRate: 2,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.296595,
lat: 31.550228,
type: '110',
description: [
{
name: '110kv实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.275891,
lat: 31.910984,
type: '35',
description: [
{
name: '35kv实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 119.820538,
lat: 31.364384,
type: 'user',
description: [
{
name: '用户实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.266053,
lat: 31.550228,
type: 'station',
description: [
{
name: '电厂实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
}
])
三、实现打点方法
实现地图上的具体点需要写一个方法,将这个方法在初始化地图方法中进行调用即可,以下是实现的具体详细步骤:
1.设置点样式
设置点的具体样式,因为可能点的样式会不一样,所以这里将点图标作为变量。
//点样式
const getPointStyle = icon => {
var pointStyle = new Style({
image: new Icon({
anchor: [0.6, 0.6],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
// src: 'https://i.hd-r.cn/f7be245b35c840be205a6f54e1f9f5c0.png'//r如果样式都一样可采用此方法
src: icon
})
})
return pointStyle
}
2.遍历标注点数据
var pointFeature = new Array()
let pointFeature2 = null
// 遍历标注点数据
points.value.forEach(point => {
const coordinates = fromLonLat([point.lon, point.lat])
// 添加点标记
pointFeature2 = new Feature({
geometry: new Point(coordinates)
})
if (point.type == '500') {
//根据类型设置不同的图标
const src = 'https://s21.ax1x.com/2024/11/25/pAhYPot.png'
pointFeature2.setStyle(getPointStyle(src))
} else if (point.type == '220') {
const src = 'https://s21.ax1x.com/2024/11/25/pAhYVSS.png'
pointFeature2.setStyle(getPointStyle(src))
}
// 设置点标记的属性--这一步一定要设置,否则无法显示属性,即弹窗中的信息
pointFeature2.set('description', point.description)
pointFeature.push(pointFeature2)
})
3.设置点数据源和点图层
// 点数据源
var pointSource = new VectorSource({
features: pointFeature
})
// 点图层
var pointLayer = new VectorLayer({
zIndex: 666,
source: pointSource,
style: function (feature, resolution) {
return feature.getStyle()
}
})
4.将点图层添加到地图
map.value.addLayer(pointLayer)
5.整合代码
// 标注点
const drawPoint = () => {
//点样式
const getPointStyle = icon => {
var pointStyle = new Style({
image: new Icon({
anchor: [0.6, 0.6],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
// src: 'https://i.hd-r.cn/f7be245b35c840be205a6f54e1f9f5c0.png'
src: icon
})
})
return pointStyle
}
var pointFeature = new Array()
let pointFeature2 = null
// 遍历标注点数据
points.value.forEach(point => {
const coordinates = fromLonLat([point.lon, point.lat])
// 添加点标记
pointFeature2 = new Feature({
geometry: new Point(coordinates)
})
if (point.type == '500') {
//根据类型设置不同的图标
const src = 'https://s21.ax1x.com/2024/11/25/pAhYPot.png'
pointFeature2.setStyle(getPointStyle(src))
} else if (point.type == '220') {
const src = 'https://s21.ax1x.com/2024/11/25/pAhYVSS.png'
pointFeature2.setStyle(getPointStyle(src))
}
// 设置点标记的属性--这一步一定要设置,否则无法显示属性,即弹窗中的信息
pointFeature2.set('description', point.description)
pointFeature.push(pointFeature2)
})
// 点数据源
var pointSource = new VectorSource({
features: pointFeature
})
// 点图层
var pointLayer = new VectorLayer({
zIndex: 666,
source: pointSource,
style: function (feature, resolution) {
return feature.getStyle()
}
})
// 点图层添加到地图
map.value.addLayer(pointLayer)
}
四、弹窗实现
1.设计弹窗样式
这里只是做了简单的设计,仅供参考
<template>
<div id="container" class="map"></div>
<t-message
v-if="popupVisible"
class="popup"
:style="{ left: popupPosition.x + 'px', top: popupPosition.y + 'px' }"
theme="success"
>
<div class="t-message-close" @click="closePopup">
<b><CloseIcon /></b>
</div>
<div class="content">
<h3>{{ popupName }}</h3>
<span class="txt"
>实时负载率:<span class="text">{{ loadRate }}%</span></span
>
<span class="txt"
>当月最高负载率:<span class="text">{{ maxLoadRate }}%</span></span
>
<span class="txt"
>月均负载率:<span class="text">{{ avgLoadRate }}%</span></span
>
<span class="txt"
>当月重载时间:<span class="text">{{ overloadTime }}</span></span
>
<span class="txt"
>当月过载时间:<span class="text">{{ overTime }}</span></span
>
</div>
</t-message>
</template>
<style lang="less" scoped>
#container {
width: 100%;
height: 100%;
position: absolute;
}
.popup {
position: absolute;
background: white;
border: 1px solid #009994;
// padding: 10px;
border-radius: 4px;
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 2px 8px rgba(0, 111, 107, 0.5);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: normal;
cursor: pointer;
.t-message-close {
position: absolute;
top: 10px;
right: 10px;
font-size: 1vw;
cursor: pointer;
}
.content {
display: flex;
flex-direction: column;
margin-top: 10px;
h3 {
color: #000000e6;
// font-weight: 600;
font-size: 1.1vw;
padding: 5px 0;
}
.txt {
font-size: 1vw;
color: #009994;
padding: 5px;
.text {
color: #000000e6;
}
}
}
}
</style>
2.定义弹窗中的变量
const popupVisible = ref(false)
//弹窗名称
const popupName = ref('')
//实时负载率
const loadRate = ref()
//当月最高负载率
const maxLoadRate = ref()
//月均负载率
const avgLoadRate = ref()
//当月重载时间
const overloadTime = ref('')
//当月过载时间
const overTime = ref('')
const popupPosition = ref({ x: 0, y: 0 })
3.实现弹窗方法
// 点击事件-出现弹窗
const clickHandler = e => {
// 添加鼠标事件
map.value.on('singleclick', event => {
const feature = map.value.forEachFeatureAtPixel(event.pixel, feature => feature)
if (feature) {
// console.log('这是:', feature.get('description'))
const coordinates = feature.getGeometry().getCoordinates()
// popupContent.value = feature.get('name')
//获取弹窗中的信息数据
feature.get('description').forEach(item => {
popupName.value = item.name
loadRate.value = item.loadRate
maxLoadRate.value = item.maxLoadRate
avgLoadRate.value = item.avgLoadRate
overloadTime.value = item.overloadTime
overTime.value = item.overTime
})
const popupPixel = map.value.getPixelFromCoordinate(coordinates)
popupPosition.value = { x: popupPixel[0], y: popupPixel[1] }
popupVisible.value = true
} else {
closePopup()
}
})
}
4.关闭弹窗
const closePopup = () => {
popupVisible.value = false
}
六、初始化地图中引用打点、弹窗方法
// 初始化地图
const init = () => {
drawPoint()
clickHandler()
}
七、整合所有代码
<template>
<div id="container" class="map"></div>
<t-message
v-if="popupVisible"
class="popup"
:style="{ left: popupPosition.x + 'px', top: popupPosition.y + 'px' }"
theme="success"
>
<div class="t-message-close" @click="closePopup">
<b><CloseIcon /></b>
</div>
<div class="content">
<h3>{{ popupName }}</h3>
<span class="txt"
>实时负载率:<span class="text">{{ loadRate }}%</span></span
>
<span class="txt"
>当月最高负载率:<span class="text">{{ maxLoadRate }}%</span></span
>
<span class="txt"
>月均负载率:<span class="text">{{ avgLoadRate }}%</span></span
>
<span class="txt"
>当月重载时间:<span class="text">{{ overloadTime }}</span></span
>
<span class="txt"
>当月过载时间:<span class="text">{{ overTime }}</span></span
>
</div>
</t-message>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { CloseIcon } from 'tdesign-icons-vue-next'
import 'ol/ol.css' // 地图样式
import TileLayer from 'ol/layer/Tile' // 瓦片渲染方法
import XYZ from 'ol/source/XYZ'
import { Map, View, Feature } from 'ol' // 地图实例方法、视图方法
import { fromLonLat } from 'ol/proj'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import { Style, Icon, Stroke, Fill, Text, Circle } from 'ol/style'
import { Point, LineString, Polygon } from 'ol/geom'
import GeoJSON from 'ol/format/GeoJSON'
let map = ref(null)
const popupVisible = ref(false)
//弹窗名称
const popupName = ref('')
//实时负载率
const loadRate = ref()
//当月最高负载率
const maxLoadRate = ref()
//月均负载率
const avgLoadRate = ref()
//当月重载时间
const overloadTime = ref('')
//当月过载时间
const overTime = ref('')
const popupPosition = ref({ x: 0, y: 0 })
//地图图层参数
const mapView = reactive({
center: fromLonLat([120.299, 31.568]), // 地图中心点
zoom: 10, // 初始缩放级别
minZoom: 10, // 最小缩放级别
maxZoom: 15 // 最大缩放级别
// extent: [116, 29, 125, 31] // 设置地图中心范围
})
const mapUrl = ref(
'这里填写离线地图的瓦片地址'
)
// 标注点数据
const points = ref([
{
lon: 120.303543,
lat: 31.681019,
type: '220',
description: [
{
name: '220kv实例变',
loadRate: 1,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.296595,
lat: 31.575706,
type: '500',
description: [
{
name: '500kv实例',
loadRate: 2,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.296595,
lat: 31.550228,
type: '110',
description: [
{
name: '110kv实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.275891,
lat: 31.910984,
type: '35',
description: [
{
name: '35kv实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 119.820538,
lat: 31.364384,
type: 'user',
description: [
{
name: '用户实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
},
{
lon: 120.266053,
lat: 31.550228,
type: 'station',
description: [
{
name: '电厂实例',
loadRate: 3,
maxLoadRate: 1,
avgLoadRate: 1,
overloadTime: '2024-8-29',
overTime: '2024-8-29'
}
]
}
])
//规划区域-无锡
const addGeoJson = async () => {
// 加载数据
const json = await fetch('规定区域的json API地址').then(response =>
response.json()
)
const features = new GeoJSON({ featureProjection: 'EPSG:3857' }).readFeatures(json)
const vectorSource = new VectorSource({ features: features })
// 规划区域样式
const areaStyle = feature => {
return new Style({
fill: new Fill({
//矢量图层填充颜色,以及透明度
// color: 'rgba(5,50,86,0.6)'
color: 'rgba(0, 153, 148, 0.5)'
}),
stroke: new Stroke({
//边界样式
color: 'rgba(73,242,242,0.8)',
width: 3
}),
text: new Text({
//文本样式
text: feature.get('name'),
// 设置文本样式
textAlign: 'center',
textBaseline: 'bottom',
padding: [5, 10, 5, 10],
font: '14px Calibri,sans-serif',
fill: new Fill({
color: '#000'
}),
stroke: new Stroke({
color: '#fff',
width: 3
})
})
})
}
const lineLayer = new VectorLayer({
zIndex: 99,
source: vectorSource,
style: areaStyle
})
map.value.addLayer(lineLayer) // 把图层添加到地图
}
// 标注点
const drawPoint = () => {
//点样式
const getPointStyle = icon => {
var pointStyle = new Style({
image: new Icon({
anchor: [0.6, 0.6],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
// src: 'https://i.hd-r.cn/f7be245b35c840be205a6f54e1f9f5c0.png'
src: icon
})
})
return pointStyle
}
var pointFeature = new Array()
let pointFeature2 = null
// 遍历标注点数据
points.value.forEach(point => {
const coordinates = fromLonLat([point.lon, point.lat])
// 添加点标记
pointFeature2 = new Feature({
geometry: new Point(coordinates)
})
if (point.type == '500') {
//根据类型设置不同的图标
const src = 'https://s21.ax1x.com/2024/11/25/pAhYPot.png'
pointFeature2.setStyle(getPointStyle(src))
} else if (point.type == '220') {
const src = 'https://s21.ax1x.com/2024/11/25/pAhYVSS.png'
pointFeature2.setStyle(getPointStyle(src))
}
// 设置点标记的属性--这一步一定要设置,否则无法显示属性,即弹窗中的信息
pointFeature2.set('description', point.description)
pointFeature.push(pointFeature2)
})
// 点数据源
var pointSource = new VectorSource({
features: pointFeature
})
// 点图层
var pointLayer = new VectorLayer({
zIndex: 666,
source: pointSource,
style: function (feature, resolution) {
return feature.getStyle()
}
})
// 点图层添加到地图
map.value.addLayer(pointLayer)
}
// 点击事件-出现弹窗
const clickHandler = e => {
// 添加鼠标事件
map.value.on('singleclick', event => {
const feature = map.value.forEachFeatureAtPixel(event.pixel, feature => feature)
if (feature) {
// console.log('这是:', feature.get('description'))
const coordinates = feature.getGeometry().getCoordinates()
// popupContent.value = feature.get('name')
//获取弹窗中的信息数据
feature.get('description').forEach(item => {
popupName.value = item.name
loadRate.value = item.loadRate
maxLoadRate.value = item.maxLoadRate
avgLoadRate.value = item.avgLoadRate
overloadTime.value = item.overloadTime
overTime.value = item.overTime
})
const popupPixel = map.value.getPixelFromCoordinate(coordinates)
popupPosition.value = { x: popupPixel[0], y: popupPixel[1] }
popupVisible.value = true
} else {
closePopup()
}
})
}
const closePopup = () => {
popupVisible.value = false
}
// 初始化地图
const init = () => {
// 使用瓦片渲染方法
const tileLayer = new TileLayer({
source: new XYZ({
url: mapUrl.value
})
})
map.value = new Map({
layers: [tileLayer],
view: new View(mapView),
target: 'container'
})
addGeoJson()
drawPoint()
clickHandler()
}
onMounted(() => {
init()
})
</script>
<style lang="less" scoped>
#container {
width: 100%;
height: 100%;
position: absolute;
}
.popup {
position: absolute;
background: white;
border: 1px solid #009994;
// padding: 10px;
border-radius: 4px;
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
box-shadow: 0 2px 8px rgba(0, 111, 107, 0.5);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: normal;
cursor: pointer;
.t-message-close {
position: absolute;
top: 10px;
right: 10px;
font-size: 1vw;
cursor: pointer;
}
.content {
display: flex;
flex-direction: column;
margin-top: 10px;
h3 {
color: #000000e6;
// font-weight: 600;
font-size: 1.1vw;
padding: 5px 0;
}
.txt {
font-size: 1vw;
color: #009994;
padding: 5px;
.text {
color: #000000e6;
}
}
}
}
</style>