背景介绍
这次使用openlayers实现的地图锚点和点击弹窗的功能,参考了网上别人的一些经验,简化了代码,后续实现点击弹窗插入数据的能力。使用的技术栈是vue3 + vite。
地图初始化以及属性介绍
由于OSM国内访问不到,这次使用的天地图的数据,token自己去天地图上注册获取就可以了
const map = new Map({
layers: [
new TileLayer({
source: new XYZ({
url: 'http://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=你的token'
})
}),
],
target: 'map',
view: new View({
projection: "EPSG:4326",
center: [128, 31.5],
zoom: 5,
})
})
layers
图层,是整个地图功能的基础,value为一个数组
TileLayer
平铺图层,展示的是大而全的地图数据,后续的锚点和弹窗视图都是落在平铺图层上的
target
容器,值为一个字符串,目标容器的id
view
视图,包含中心点和放大层级等属性
锚点功能
画点
想要在图层上画点,需要使用一个新的图层 VectorImageLayer
,包含source
属性默认为 VectorSource
,VectorSource
里面有features
就是我们想要添加的锚点的列表
const feature = new Feature({
geometry: new Point([119,36]) // 注册在什么坐标点添加
})
const vectorLayer = new VectorImageLayer({
source: new VectorSource({
features: [
feature
]
}),
})
feature.setStyle(new Style({
image: new Icon({
src: aicon
})
}));
这样我就在经度119,维度36的地方画了一个点,大概在宇宙中心曹县一带
点击
点击功能代码分为两步
-
监听地图点击事件
-
捕获点击的经纬度坐标,捕获附近的锚点
map.on('singleclick', (e) => { const coordinate = e.coordinate console.log(feature) const feature = map.forEachFeatureAtPixel(e.pixel, function (feature, layer) { return feature; }) // 弹出popup })
弹窗
定义
使用vue3的 teleport 标签,把目标弹窗插入到 body 里面
<template>
<teleport to="body">
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content">我是一个锚点</div>
</div>
</teleport>
</template>
注册
创建overLayer弹窗图层
// 创建弹窗图层
const popup = new Overlay({
element: document.getElementById('popup')
})
map.addOverlay(popup)
const closer = document.getElementById('popup-closer')
closer.onclick = function () {
popup.setPosition(undefined);
closer.blur();
return false;
};
使用
放到刚才的点击事件里面,就大功告成
map.on('singleclick', (e) => {
const coordinate = e.coordinate
console.log(feature)
const feature = map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
return feature;
})
if (feature) {
// 弹出popup
popup.setPosition(coordinate)
}
})
源码
<script setup>
import { ref, reactive, onMounted } from 'vue'
import Map from 'ol/Map'
import View from 'ol/View'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import Feature from 'ol/Feature'
import { Point } from 'ol/geom'
import VectorSource from 'ol/source/Vector'
import Overlay from 'ol/Overlay'
import Interaction from 'ol/interaction/Interaction'
import VectorImageLayer from 'ol/layer/VectorImage'
import Style from 'ol/style/Style'
import Icon from 'ol/style/Icon'
import aicon from '../assets/aicon.svg'
import MapDialog from './MapDialog.vue'
import 'ol/ol.css'
function generateLayer () {
const vectorSource = new VectorSource({
features: []
})
const vectorLayer = new VectorImageLayer({
source: vectorSource,
})
return vectorLayer
}
function dataHandler (msg) {
const data = msg.slice(15, -3)
return data.split(',')
}
onMounted(async () => {
const coors = ['119.36 35.07', '119.03 34', '119.36 33', '119.36 36']
const layer = generateLayer() // 创建锚点图层
const map = new Map({
layers: [
new TileLayer({
source: new XYZ({
url: 'http://t0.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk='
})
}),
layer
],
target: 'map',
view: new View({
projection: "EPSG:4326",
center: [128, 31.5],
zoom: 5,
})
})
const anchors = coors.map(item => {
const coor = item.split(' ')
const anchor = new Feature({
geometry: new Point(coor)
})
anchor.setStyle(new Style({
image: new Icon({
src: aicon
})
}));
layer.getSource().addFeature(anchor)
return anchor
})
map.getView().on('change:resolution', function () {
anchors.forEach(anchor => {
const style = anchor.getStyle();
// 重新设置图标的缩放率,基于层级20来做缩放
style.getImage().setScale(this.getZoom() / 5);
anchor.setStyle(style);
})
})
// 创建弹窗图层
const popup = new Overlay({
element: document.getElementById('popup')
})
map.addOverlay(popup)
const closer = document.getElementById('popup-closer')
closer.onclick = function () {
popup.setPosition(undefined);
closer.blur();
return false;
};
map.on('singleclick', (e) => {
const coordinate = e.coordinate
const feature = map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
return feature;
})
if (feature) {
// 弹出popup
popup.setPosition(coordinate)
}
})
})
</script>
<template>
<div id="map"></div>
<map-dialog></map-dialog>
</template>
<style lang="less">
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
}
</style>