需求
用户点击地图上的点位,在点击位置弹出弹框,展示点位的更多信息。
实现过程
通过new ol.Overlay({})
实现,地图初始化后,创建Overlay
,并将其添加到地图上。在用户点击到对应的点位后,设置Overlay
指定的DOM
元素的内容,然后显示Overlay
。
知识点
Overlay
用于在地图上叠加额外的 HTML 元素,如弹出框、信息窗口等,Overlay
的优势是可以自定义各种css
样式,所以也有人使用Overlay
来渲染点位。new ol.Overlay({autoPan})
的参数autoPan
设置为true
时,如果弹框不在视口范围内,自动移动地图,让其可见。有时候点击不生效。new ol.Overlay({positioning})
的参数positioning
用于设置弹框相对于传入坐标点的位置,可设置为bottom-left
,bottom-center
,bottom-right
,center-left
,center-center
,center-right
,top-left
,top-center
,top-right
。new ol.Overlay({element})
的参数element
用于指定Overlay
使用的DOM
元素。Overlay
只是将我们指定的DOM
元素在我们调用Overlay.setPosition(coordinates)
的时候,将其放到地图的指定位置上,DOM
元素的样式和内容需我们自己实现。- 隐藏
Overlay
可使用Overlay.getElement().style.display = 'none'
。 - 彻底删除
Overlay
使用Overlay.setMap(null)
。需要注意的是删除Overlay
时,Overlay
创建时指定的DOM
元素也一并被删除。
代码HTML+CSS+JS
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css" integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>Overlay显示</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--top-height: 50px;
}
html,
body,
#app,
.app-map {
height: 100%;
height: 100%;
}
.app-btns {
position: fixed;
right: 10px;
top: 10px;
background-color: #fff;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .5);
width: 210px;
padding: 25px;
text-align: center;
border-radius: 5px;
display: flex;
flex-direction: column;
z-index: 2;
}
.app-btns button {
font-size: 18px;
border: none;
padding: 12px 20px;
border-radius: 4px;
color: #fff;
background-color: #409eff;
border-color: #409eff;
cursor: pointer;
border: 1px solid #dcdfe6;
margin-bottom: 5px;
}
.app-btns button:hover {
background: #66b1ff;
border-color: #66b1ff;
}
.app-btns button.active {
background-color: #07c160;
}
#popup {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
background-color: #fff;
width: 350px;
z-index: 13;
border-radius: 4px;
border: 1px solid #ebeef5;
font-size: 16px;
}
#popup .top {
height: var(--top-height);
display: flex;
border-bottom: 1px solid #ebeef5;
padding-left: 20px;
}
#popup .top .name {
width: calc(100% - var(--top-height));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: var(--top-height);
}
#popup .top .close {
width: var(--top-height);
color: #606266;
cursor: pointer;
background-color: #fff;
border: none;
border-left: 1px solid #ebeef5;
font-size: 16px;
}
#popup .top .close:hover {
background-color: #eee;
color: #66b1ff;
}
#popup .bot {
padding: 0 20px;
}
#popup .bot li {
height: var(--top-height);
display: flex;
justify-content: space-between;
}
#popup .bot li span {
display: inline-block;
line-height: var(--top-height);
width: calc(100% - 80px);
}
#popup .bot li span.label {
width: 80px;
}
</style>
</head>
<body>
<div id="app">
<div class="app-map" id="app-map"></div>
<div class="app-btns">
<button @click='handleClickRemoveOverlay'>删除弹框</button>
</div>
<div id="popup">
<div class="top">
<div class="name">{{clickPointInfo.name}}</div>
<button class="close" @click='handleCloseOverlay'>
关闭
</button>
</div>
<div class="bot">
<ul>
<li>
<span class="label">点位ID:</span>
<span>{{clickPointInfo.random}}</span>
</li>
<li>
<span class="label">经纬度:</span>
<span>{{clickPointInfo.center.join(' , ')}}</span>
</li>
</ul>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js" integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js" integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
const { createApp } = Vue;
// feature图片
const base64Img = 'data:image/png;base64,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';
// 基础样式style
const baseStyle = new ol.style.Style({
image: new ol.style.Icon({
src: base64Img,
scale: 1.5,
anchor: [0.5, 0.5],
rotateWithView: true,
rotation: 0,
opacity: 1
}),
});
// 点位信息
const pointData = [{
name: '北京市',
center: [116.405285, 39.904989]
},
{
name: '天津市',
center: [117.190182, 39.125596]
},
{
name: '河北省',
center: [114.502461, 38.045474]
},
{
name: '山西省',
center: [112.549248, 37.857014]
},
{
name: '内蒙古自治区',
center: [111.670801, 40.818311]
},
{
name: '辽宁省',
center: [123.429096, 41.796767]
},
{
name: '吉林省',
center: [125.3245, 43.886841]
},
{
name: '黑龙江省',
center: [126.642464, 45.756967]
},
{
name: '上海市',
center: [121.472644, 31.231706]
},
{
name: '江苏省',
center: [118.767413, 32.041544]
},
{
name: '浙江省',
center: [120.153576, 30.287459]
},
{
name: '安徽省',
center: [117.283042, 31.86119]
},
{
name: '福建省',
center: [119.306239, 26.075302]
},
{
name: '江西省',
center: [115.892151, 28.676493]
},
{
name: '山东省',
center: [117.000923, 36.675807]
},
{
name: '河南省',
center: [113.665412, 34.757975]
},
{
name: '湖北省',
center: [114.298572, 30.584355]
},
{
name: '湖南省',
center: [112.982279, 28.19409]
},
{
name: '广东省',
center: [113.280637, 23.125178]
},
{
name: '广西壮族自治区',
center: [108.320004, 22.82402]
},
{
name: '海南省',
center: [110.33119, 20.031971]
},
{
name: '重庆市',
center: [106.504962, 29.533155]
},
{
name: '四川省',
center: [104.065735, 30.659462]
},
{
name: '贵州省',
center: [106.713478, 26.578343]
},
{
name: '云南省',
center: [102.712251, 25.040609]
},
{
name: '西藏自治区',
center: [91.132212, 29.660361]
},
{
name: '陕西省',
center: [108.948024, 34.263161]
},
{
name: '甘肃省',
center: [103.823557, 36.058039]
},
{
name: '青海省',
center: [101.778916, 36.623178]
},
{
name: '宁夏回族自治区',
center: [106.278179, 38.46637]
},
{
name: '新疆维吾尔自治区',
center: [87.617733, 43.792818]
},
{
name: '台湾省',
center: [121.509062, 25.044332]
},
{
name: '香港特别行政区',
center: [114.173355, 22.320048]
},
{
name: '澳门特别行政区',
center: [113.54909, 22.198951]
},
]
const vm = createApp({
data() {
return {
map: {},
overlay: null,
clickPointInfo: {
name: '',
center: []
}
}
},
methods: {
// 初始化地图
initMap() {
// 高德地图瓦片地址
const vectorLayer = new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
}),
name: '初始化地图图层'
});
// 初始化地图
this.map = new ol.Map({
target: 'app-map',
layers: [vectorLayer],
view: new ol.View({
projection: 'EPSG:3857',
//设定中心点,因为默认坐标系为 3587,所以要将我们常用的经纬度坐标系4326 转换为 3587坐标系
center: ol.proj.transform([111.8453154, 32.7383500], 'EPSG:4326', 'EPSG:3857'),
zoom: 5,
})
});
// 绑定地图事件
this.bindMapEvt();
// 渲染点位到地图上
this.renderPoint();
// 创建一个 Overlay
this.overlay = new ol.Overlay({
element: document.getElementById('popup'), // 弹出框的 HTML 元素
positioning: 'bottom-center', // 设置定位方式,这里是底部中心
autoPan: true, // 当overlay不在视口内的时候,移动地图,让其可见
stopEvent: false, // 是否停止事件传播
});
// 将Overlay添加到地图上
this.map.addOverlay(this.overlay);
},
// 绑定地图事件
bindMapEvt() {
// 监听鼠标点击
this.map.on('click', (evt) => {
// 根据点位像素位置,获取此位置的要素feature
const feature = this.map.forEachFeatureAtPixel(evt.pixel, function(feature) {
return feature;
});
if (feature) {
// 获取要素的坐标
const coordinates = feature.getGeometry().getCoordinates();
// 获取要素的信息
this.clickPointInfo = feature.get('info');
if (this.overlay) {
// 设置弹出框的位置为点击的位置
console.log(coordinates);
this.overlay.setPosition(coordinates);
// 显示弹出框
this.overlay.getElement().style.display = 'block';
} else {
alert(`点击的是${this.clickPointInfo.name},经纬度:${this.clickPointInfo.center.join(',')}`)
}
} else {
// 如果没有点击到要素,隐藏弹出框
this.overlay && (this.overlay.getElement().style.display = 'none');
}
});
// 监听鼠标移动,移动到feature上时,鼠标变为可点击的状态
this.map.on('pointermove', (e) => {
// 获取像素位置
let pixel = this.map.getEventPixel(e.originalEvent);
// 根据点位像素位置,获取此位置的要素feature
let feature = this.map.forEachFeatureAtPixel(pixel, (feature) => {
return feature;
});
// 要素存在,并且是需要改变鼠标样式为pointer的feature,鼠标样式变为pointer,否则auto
if (feature && feature.get('pointer')) {
this.map.getTargetElement().style.cursor = 'pointer';
} else {
this.map.getTargetElement().style.cursor = 'auto';
}
});
},
// 删除Overlay
handleClickRemoveOverlay() {
if (this.overlay != null) {
this.overlay.setMap(null);
this.overlay = null;
}
alert('overlay已删除');
},
// 点击上的关闭
handleCloseOverlay() {
this.overlay.getElement().style.display = 'none';
},
// 在地图上添加点位
renderPoint(numPoints) {
// 根据positions创建一个新的数据源和要素数组,
const vectorSource = new ol.source.Vector({
features: pointData.map(e => {
// ol.proj.fromLonLat用于将经纬度坐标从 WGS84 坐标系转换为地图投影坐标系
const feature = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat(e.center)),
info: {
random: Math.ceil(Math.random() * 100000),
...e
},
pointer: true
});
feature.setStyle(baseStyle);
return feature;
})
});
// 创建带有数据源的矢量图层
this.pointLayer = new ol.layer.Vector({
source: vectorSource,
layerID: 'addpointLayer',
name: '点位图层1'
});
// 将矢量图层添加到地图上
this.map.addLayer(this.pointLayer);
}
},
mounted() {
this.initMap();
}
}).mount('#app')
</script>
</body>
</html>