Openlayers6之地图覆盖物Overlay详解及使用,地图标注及弹窗查看详情(结合React)

文章正文
发布时间:2024-12-13 08:56

demo案例:用户实现地图加载人员位置定位,并设置人员图片文字等标注,点击定位点查看人员详情。

在这里插入图片描述


主要通过ol/geom Point设置Style和ol/Overlay实现。主要实现步骤:

实现图文标注的实质是添加点时设置Ponit的样式,图片标注就是在Style中添加Image,文字标注就是在Style中添加Text;

实现详情弹窗实质就是是通过创建Overlay,并将其添加到地图上。在用户点击到对应的点位后,设置Overlay指定的DOM元素的内容,设置position显示Overlay。

overlay简述

什么是地图覆盖物Overlay?地图覆盖物主要是放置一些和地图位置相关的元素,常见的地图覆盖物有三种类型,如:popup 弹窗、label标注信息、text文本信息等,而这些覆盖物都是和html中的element等价的,通过Overlay的属性element和html元素绑定的同时设定坐标参数来达到将html元素放到地图上的位置,在平移缩放的时候html元素也会随着地图的移动而移动。Overlay的优势是可以自定义各种css样式,所以也有人使用Overlay来渲染定位

overlay API说明

Overlay初始化时可以接受很多配置参数,常用的属性有:

id:为对应的Overlay设置一个id,便于使用ol.Map的getOverlayById方法获取相应的overlay。

element:Overlay包含的DOM element,及指定Overlay使用的DOM元素。

offset:偏移量,像素为单位,Overlay相对于放置位置position的偏移量,默认值是 [0, 0],正值分别向右和向下偏移。

position:在地图所在的坐标系框架下,Overlay放置的位置。

positioning:根据position的位置来进行相对定位,可能的值包括bottom-left、bottom-center、bottom-right、center-left、center-center、center-right、top-left、top-center、top-right,默认是top-left,也就是element左上角与position重合。

stopEvent:地图的事件传播是否停止,默认是 true,即阻止传播,可能不太好理解,举个例子,当鼠标滚轮在地图上滚动时,会触发地图缩放事件,如果在Overlay之上滚动滚轮,并不会触发缩放事件,如果想鼠标在Overlay 之上也支持缩放,那么将该属性设置为false即可。

insertFirst:是否应该先添加到其所在的容器,当stopEvent设置为true时,Overlay和Openlayers的控件(controls)是放于一个容器的,此时将insertFirst设置为 true ,Overlay会首先添加到容器,这样,Overlay默认在控件的下一层(CSS z-index),所以,当stopEvent和insertFirst都采用默认值时,Overlay默认在控件的下一层。

autoPan:当触发Overlay setPosition方法时触发,当Overlay超出地图边界时,地图自动移动,以保证 Overlay全部可见。

autoPanAnimation:设置autoPan的效果动画。

autoPanMargin:地图自动平移时,地图边缘与Overlay的留白(空隙),单位是像素,默认是 20像素。

overlay特有方法,主要有:

getElement:取得包含overlay的DOM元素。隐藏Overlay时可使用Overlay.getElement.style.display = 'null'.

getId:取得overlay的id。

getMap:获取与overlay关联的map对象。

getOffset:获取offset属性。

getPosition:获取position属性.

getPositioning:获取positioning属性。

setElement:设置overlay的element。

setMap:设置与overlay的map对象。彻底删除Overlay使用Overlay.setMap(null)。需要注意的是删除Overlay时,Overlay创建时指定的DOM元素也一并被删除。

setOffset:设置offset。

setPosition:设置position属性。但只是将指定的DOM元素放在地图指定的位置上,DOM元素的样式和内容需要我们自己实现。

setPositioning:设置positioning属性。

Point图文标注实现 1. 添加点要素。 let personData = [ { id: 1, name: '张三', position: [104.0641, 30.5973], color: 1, sex: 1 } ]; // 根据positions创建一个新的数据源和要素数组, let source = new VectorSource(); let feature = new Feature({ geometry: new Point([104.0641, 30.5973]) }); source.addFeature(feature); // 创建带有数据源的矢量图层 let layer = new VectorLayer({ source: source, name:'pointLayer' }); // 将矢量图层添加到Map上 map.addLayer(layer); 2. 设置feature样式。主要采用Icon和Text new Style({ image: new Icon({ scale: 0.5, src: '图片地址', anchor: [0.5, 1] }), text: new Text({ text: '需要展示的文字', // 只能传入字符串 fill: new Fill({ color: '#FFFFFF' }), backgroundFill: new Fill({ color: '#555555' }), padding: [2, 2, 0, 4], offsetY: -48, scale: 1.4 }) }); 图文标注完整代码 // 引入模块 import { Feature } from 'ol'; // 地图Collection import { Vector as VectorLayer } from 'ol/layer'; import { Vector as VectorSource } from 'ol/source'; import { Point } from 'ol/geom'; import { Style, Icon, Fill, Text } from 'ol/style'; // 模拟数据 let personData = [ { id: 1, name: '张三', position: [104.0641, 30.5973], color: 1, sex: 1 } ]; // 设置feature样式 function setFeatureStyle(feature) { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: '图片路径', anchor: [0.5, 1] }), new Text({ text: '展示文字', // 只能传入字符串 fill: new Fill({ color: '#FFFFFF' }), backgroundFill: new Fill({ color: '#555555' }), padding: [2, 2, 0, 4], offsetY: -48, scale: 1.4 }) }); ); } useEffect(() => { if (map) { // 如果layer已经存在,就先删除,再绘制 if (layer) { layer.getSource().clear(); map.removeLayer(layer); layer = null; } // 根据position创建一个新的数据源和要素数组 let source = new VectorSource(); for (let i = 0; i < personData.length; i++) { const item = personData[i]; let feature = new Feature({ // ol.proj.fromLonLat用于将经纬度坐标从 WGS84 坐标系转换为地图投影坐标系 geometry: new Point(Proj.fromLonLat(item.position)), type: 'point' }); // 设置feature样式 setFeatureStyle(feature); source.addFeature(feature); } // 创建带有数据源的矢量图层 let layer = new VectorLayer({ source: source, name: 'pointLayer' }); // 将矢量图层添加到Map上 map.addLayer(layer); } }, [map]); Overlay详情弹窗实现

上面已经讲述过详情弹窗的实质,创建的具体步骤:

1. 创建一个DOM元素,通常为div及包含的子元素。 <div className='overlayBox' id='overlayBox'> {/* 关闭按钮 */} <CloseOutlined className="close" onClick={closeOverlay} /> </div> 2. 创建一个Overlay实例并将DOM元素挂载。 // 创建一个弹窗 Overlay对象 let overlayDom = document.getElementById('overlayBox'); overlay = new Overlay({ element: overlayDom, autoPan: true, positioning: 'center-center', offset: [0, -120], stopEvent: true }); // 将弹窗添加到Map中 map.addOverlay(overlay); 3. 监听地图点击事件。 map.on('click', (evt) => { // 根据点位像素位置,获取此位置的要素feature const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) {} }); 4. 点击地图中的要素时读取信息并向地图Map中添加Overlay。 // 设置弹出框的位置为点击的位置 overlay.setPosition(coordinates); 弹出窗完整代码 // 引入模块 import Overlay from 'ol/Overlay'; // 弹框 let overlay; // 关闭弹窗 function closeOverlay() { overlay.setPosition(undefined); } useEffect(() => { if (map) { // 创建一个弹窗 Overlay对象 let overlayDom = document.getElementById('overlayBox'); overlay = new Overlay({ element: overlayDom, autoPan: true, positioning: 'center-center', offset: [0, -120], stopEvent: true }); // 将弹窗添加到Map中 map.addOverlay(overlay); // 监听鼠标点击 map.on('click', (evt) => { // 根据点位像素位置,获取此位置的要素feature const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { // 获取要素的坐标 const coordinates = feature.getGeometry().getCoordinates(); if (overlay) { // 设置弹出框的位置为点击的位置 overlay.setPosition(coordinates); } } else { // 如果没有点击到要素,隐藏弹出框 overlay.setPosition(undefined); } }); } }, [map]); return <div className='map_container'> <div id='map' style={{ width: '100%', height: '100%' }}></div> <div className='overlayBox' id='overlayBox'> <CloseOutlined className="close" onClick={closeOverlay} /> </div> </div>; demo实例完整代码

地图点击事件我采用了Openlayersol/interaction/Select选中实现,也可是使用监听map click实现。

import React, { useState, useEffect } from 'react'; import { CloseOutlined } from '@ant-design/icons'; import { Map, View, Feature } from 'ol'; // 地图Collection import * as Proj from 'ol/proj'; // 转化 import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'; // 图层 import { XYZ, Vector as VectorSource } from 'ol/source'; // 资源 import { Point } from 'ol/geom'; import { Style, Icon, Fill, Text } from 'ol/style'; // 样式 import Overlay from 'ol/Overlay'; // 弹框 import { Select } from 'ol/interaction'; import { click } from 'ol/events/condition'; import redImg from '@/static/image/red.png'; import blueImg from '@/static/image/blue.png'; import whiteImg from '@/static/image/white.png'; import orangeImg from '@/static/image/orange.png'; import grayImg from '@/static/image/gray.png'; let source; let layer; let overlay; const OpenlayerOverlay = () => { const [map, setMap] = useState(null); // 地图 const [view, setView] = useState(null); // 地图视图 const [curInfo, setCurInfo] = useState(null); // 当前查看信息 let personData = [ { id: 1, name: '张三', position: [104.0641, 30.5973], color: 1, sex: 1 }, { id: 2, name: '李四', position: [104.0622, 30.5954], color: 2, sex: 2 }, { id: 3, name: '王五', position: [104.0722, 30.5960], color: 3, sex: 1 }, { id: 4, name: '张麻子', position: [104.0634, 30.5958], color: 4, sex: 1 }, ]; // 设置feature样式 function setFeatureStyle(feature, data) { if (data.color === 1) { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: redImg, anchor: [0.5, 1] }) }) ); } else if (data.color === 2) { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: blueImg, anchor: [0.5, 1] }) }) ); } else if (data.color === 3) { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: whiteImg, anchor: [0.5, 1] }) }) ); } else if (data.color === 4) { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: orangeImg, anchor: [0.5, 1] }) }) ); } else { feature.setStyle( new Style({ image: new Icon({ scale: 0.5, src: grayImg, anchor: [0.5, 1] }) }) ); } feature.getStyle().setText( new Text({ text: data.name, // 只能传入字符串 fill: new Fill({ color: '#FFFFFF' }), backgroundFill: new Fill({ color: '#555555' }), padding: [2, 2, 0, 4], offsetY: -48, scale: 1.4 }) ); } // 关闭弹窗 function closeOverlay() { overlay.setPosition(undefined); } useEffect(() => { if (map) { // 创建一个弹窗 Overlay对象 let overlayDom = document.getElementById('overlayBox'); overlay = new Overlay({ element: overlayDom, autoPan: true, // 弹出窗口在边缘点击时候显示不完整,设置自动平移效果 positioning: 'center-center', // 根据position属性的位置来进行相对定位, 默认为top-left offset: [0, -120], // 偏移量,像素为单位,Overlay 相对于放置位置(position)的偏移量,默认值是 [0, 0],正值分别向右和向下偏移; stopEvent: true // 地图的事件传播是否停止,默认是 true,即阻止传播,可能不太好理解,举个例子,当鼠标滚轮在地图上滚动时,会触发地图缩放事件,如果在 overlay 之上滚动滚轮,并不会触发缩放事件,如果想鼠标在 overlay 之上也支持缩放,那么将该属性设置为 false 即可; }); // 将弹窗添加到Map中 map.addOverlay(overlay); // 添加交互行为 let selectClick = new Select({ condition: click, // 事件类型 style: false, // 被选中后的样式 如果不写style,将为默认样式(不是自己设置的样式,而是opelayers自带的样式),设置为false或者null将保持自己设置的样式 // 指定图层 filter: (feature, layer) => { console.log(layer); return layer; } }); map.addInteraction(selectClick); // 获取当前点击元素信息,并设置弹窗显示位置 selectClick.on("select", (e) => { if (e.selected.length > 0) { // 获取当前点击要素信息 let clickPointInfo = e.selected[0].getProperties(); // 设置当前数据 setCurInfo(clickPointInfo); // 设置弹出窗的位置为当前数据定位位置 overlay.setPosition(Proj.fromLonLat(clickPointInfo.position)); } else { setCurInfo(null); overlay.setPosition(undefined); } }) } }, [map]); useEffect(() => { if (map) { // 如果layer已经存在,就先删除,再绘制 if (layer) { layer.getSource().clear(); map.removeLayer(layer); layer = null; } // 根据position创建一个新的数据源和要素数组 source = new VectorSource(); for (let i = 0; i < personData.length; i++) { const item = personData[i]; let feature = new Feature({ // ol.proj.fromLonLat用于将经纬度坐标从 WGS84 坐标系转换为地图投影坐标系 geometry: new Point(Proj.fromLonLat(item.position)), id: item.id, color: item.color, sex: item.sex, position: item.position, name: item.name, type: 'point' }); // 设置feature样式 setFeatureStyle(feature, item); source.addFeature(feature); } // 创建带有数据源的矢量图层 layer = new VectorLayer({ source: source, name: 'pointLayer' }); // 将矢量图层添加到Map上 map.addLayer(layer); } }, [map, personData]); // 地图鼠标样式调整 useEffect(() => { if (map) { // 给地图绑定鼠标移动事件,检测鼠标位置所在是否存在feature来改变鼠标输入样式 map.on('pointermove', (e) => { // 获取像素位置 let pixel = map.getEventPixel(e.originalEvent); // 根据点位像素位置,获取此位置的要素feature let feature = map.forEachFeatureAtPixel(pixel, (featureOther) => { return featureOther; }); // 要素存在,并且是需要改变鼠标样式为pointer的feature,鼠标样式变为pointer,否则auto if (feature === undefined) { map.getTargetElement().style.cursor = "auto"; } else { map.getTargetElement().style.cursor = "pointer"; } }); } }, [map]); useEffect(() => { // 监听地图视图,创建地图 if (view) { // 使用高德图层 const tileLayer = new TileLayer({ source: new XYZ({ url: '{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&scl=1&size=1&style=7&x={x}&y={y}&z={z}' }), name: 'mapLayer' }); // 创建实例 const _map = new Map({ target: 'map', layers: [tileLayer], // 使用高德图层 view: view }); setMap(_map); } }, [view]); useEffect(() => { // View用于创建2D视图 const viewObj = new View({ // 设定中心点,因为默认坐标系为 3587,所以要将我们常用的经纬度坐标系4326 转换为 3587坐标系 center: Proj.transform([104.06403453968424, 30.597419070782898], 'EPSG:4326', 'EPSG:3857'), zoom: 16 }); setView(viewObj); }, []); useEffect(() => { return () => { layer = null; source = null; // 删除Overlay // overlay.setMap(null); overlay = null; setMap(null); setView(null); setCurInfo(null); } }, []); return <div className='map_container'> <div id='map' style={{ width: '100%', height: '100%' }}></div> <div className='overlayBox' id='overlayBox'> {/* 关闭按钮 */} <CloseOutlined className="close" onClick={closeOverlay} /> <div>姓名:{curInfo && curInfo.name}</div> <div>性别:{curInfo && curInfo.sex === 1 ? '男' : '女'}</div> <div>位置:{curInfo && curInfo.position[0]}, {curInfo && curInfo.position[1]}</div> </div> </div>; } export default OpenlayerOverlay;

采用监听Map鼠标点击事件实现demo,

// 监听鼠标点击 map.on('click', (evt) => { // 根据点位像素位置,获取此位置的要素feature const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { // 获取要素的坐标 const coordinates = feature.getGeometry().getCoordinates(); // 获取当前点击要素的信息 let clickPointInfo = feature.getProperties(); // 设置当前点击点位数据 setCurInfo(clickPointInfo); if (overlay) { // 设置弹出框的位置为点击的位置 overlay.setPosition(coordinates); } } else { // 如果没有点击到要素,隐藏弹出框 overlay.setPosition(undefined); setCurInfo(null); } });