一、快速入门
官方文档:https://openlayers.org/doc/

点击API docs

左侧的array、Collection等都是类,其实openlayers是以面向对象的形式进行API设计的。后面在地图中出现点和线都是具体类的实例。
开发环境使用 Node.js (14 或更高版本),并且需要您已安装 git 。
使用 OpenLayers 构建项目的最简单方法是运行 npm create ol-app :
npm create ol-app my-app cd my-app npm start
第一条命令将创建一个名为 my-app 目录,安装 OpenLayers 和开发服务器,并设置一个包含 index.html 、 main.js 和 style.css 文件的基本应用程序。
第三条命令( npm start )会启动一个开发服务器,这样你就可以在浏览器中查看你的应用程序了。运行 npm start 后,你会看到输出信息,告诉你要打开的 URL。打开 http://localhost:5173/ (或显示的任何 URL)即可查看你的新应用程序。
创建的项目目录如下:

启动成功后,浏览器访问效果如下:

OpenLayers 应用程序由三个基本部分组成:
- The HTML markup with an element to contain the map (
index.html)
包含地图元素的 HTML 标记(index.html) - The JavaScript that initializes the map (
main.js)
用于初始化地图的 JavaScript 代码(main.js) - The CSS styles that determine the map size and any other customizations (
style.css)
决定地图大小和其他自定义设置的 CSS 样式(style.css)
用文本编辑器打开 index.html 文件。它应该看起来像这样:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/x-icon" href="https://openlayers.org/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Using OpenLayers with Vite</title> </head> <body> <div id="map"></div> <script type="module" src="./main.js"></script> </body> </html>
标记中两个重要的部分是用于容纳地图的 <div> 元素和用于引入 JavaScript 的 <script> 标签。地图容器或目标元素应该是块级元素(例如 <div> ),并且必须出现在初始化地图的 <script> 标签之前。
用文本编辑器打开 main.js 文件。它应该看起来像这样:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new OSM() }) ], view: new View({ center: [0, 0], zoom: 2 }) });
import './style.css'; 这行代码可能有点出乎意料。在这个例子中,我们使用 Vite 作为开发服务器。Vite 允许从 JavaScript 模块导入 CSS。如果您使用的是其他开发服务器,则可能需要将 style.css 文件放在 index.html 中的 <link> 标签内。
main.js 模块是应用程序的入口点。它会初始化一张新地图,为其添加一个图层,该图层包含一个 OSM 数据源和一个描述中心点和缩放级别的视图。请阅读 “基本概念”教程 ,了解有关 Map 、 View 、 Layer 和 Source 组件的更多信息。
代码解析
1、map地图容器
OpenLayers 的核心组件是地图(来自 ol/Map 模块)。它会被渲染到 target 容器中(例如网页上包含地图的 div 元素)。所有地图属性都可以在构建时配置,也可以使用 setter 方法进行配置,例如 setTarget() 。
可以使用以下标记创建一个包含地图的 <div>
<div id="map" style="width: 100%; height: 400px"></div>
以下脚本使用元素的 map ID 作为选择器,构建一个在上方 <div> 中渲染的地图。
注意:target用于绑定容器。target表示a target container,即视图容器
import Map from 'ol/Map.js'; const map = new Map({target: 'map'});
如果是vue中使用openlayers框架的话,需要等待DOM渲染完成才能绘制地图,需要增加下面这段代码:
async loadMap() { // 等待DOM完全渲染 await this.$nextTick(); // 保证当前组件模板中的 DOM 结构已经完成更新 // 确保容器存在 const container = document.getElementById('cesiumContainer1'); if (!container) { console.error('地图容器未找到'); return; } // 强制设置容器尺寸 container.style.width = '100%'; container.style.height = '600px'; // 再次等待DOM更新 await this.$nextTick(); // 验证容器尺寸 const rect = container.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { console.error('容器宽高为0,延迟重试'); setTimeout(() => this.loadMap(), 200); return; } console.log('地图容器尺寸:', rect.width, rect.height); const imgLayer = new TileLayer({ source: new XYZ({ // 影像底图 URL 模板 (球面墨卡托投影) url: `http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${this.tiandituKey}`, crossOrigin: 'anonymous' // 解决跨域问题 }) }); const vectorLayer = new VectorLayer({ source: new VectorSource({ url: this.url, format: new GeoJSON() }), style: new Style({ stroke: new Stroke({ color: '#FFD700', // 黄色 width: 3 }) }) }); let layersArr = []; layersArr.push(imgLayer); layersArr.push(vectorLayer); // 创建地图 this.map = new Map({ target: "cesiumContainer1", layers: layersArr, view: new View({ projection: 'EPSG:4326', // 使用经纬度投影 center: [110.799339, 32.663649], zoom: 8.5, }), // 2. 配置控件:保留其他默认控件,但禁用 zoom (放大缩小按钮) controls: defaultControls({ zoom: false, // 不显示 +/- 按钮 attribution: false, // 保留右下角的版权信息 rotate: false // 可选:也不显示指北针 }) }); // 监听地图加载完成 this.map.once('postrender', () => { console.log('地图渲染完成'); }); console.log('OpenLayers地图初始化成功'); },
2、view 视图
地图本身不负责地图中心、缩放级别和投影等属性。这些属性属于 ol/View 实例。
注意:center表示中心点的位置,。zoom表示缩放级别,projection表示投影,
import View from 'ol/View.js'; map.setView(new View({ center: [0, 0], zoom: 2, }));
以武汉[114.3165,30.5264]为中心,缩放级别为10,投影为EPSG:4326
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new OSM() }) ], view: new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10 }) });
效果如下:

View 也具有 projection 。投影决定了 center 坐标系以及地图分辨率计算的单位。如果未指定(如上面的代码片段所示),则默认投影为球面墨卡托投影 (EPSG:3857),地图单位为米。
zoom 选项是指定地图分辨率的便捷方式。可用的缩放级别由 maxZoom (默认值:28)、 zoomFactor (默认值:2)和 maxResolution (默认值根据投影有效范围是否适合 256x256 像素图块计算得出)决定。从缩放级别 0 开始,分辨率为每像素 maxResolution 单位,后续缩放级别通过将前一个缩放级别的分辨率除以 zoomFactor 来计算,直到达到缩放级别 maxZoom 。
3、数据源
为了获取图层的远程数据,OpenLayers 使用 ol/source/Source 子类。这些子类适用于免费和商业地图瓦片服务(例如 OpenStreetMap 或 Bing)、OGC 数据源(例如 WMS 或 WMTS)以及 GeoJSON 或 KML 等格式的矢量数据。
在 OpenLayers 里,数据源(source)是地图数据的真正提供者。简单说,source 负责“获取数据”,而图层(layer)负责“如何呈现”。将两者分离,你就能在不改动图层配置的情况下,轻松替换地图的数据来源。
import OSM from 'ol/source/OSM.js'; const source = new OSM();
openlayers是外国框架,本身内置了一些国外的地图服务,必须FQ。不过还可以通过加载url的方式访问国内的地图服务。
常用 Tile 数据源
(1)、ol.source.XYZ: 最通用的瓦片加载方式,使用 {x}/{y}/{z} 模板请求瓦片,支持 OSM、谷歌等大量XYZ服务。
(2)、ol.source.OSM: 专门为加载 OpenStreetMap 设计的封装,本质是 XYZ 子类
(3)、ol.source.TileWMS: 遵循 WMS 标准的瓦片服务。OpenLayers 负责发送请求获取瓦片
(4)、ol.source.WMTS: 遵循 WMTS 标准的服务,是 WMS 的缓存版,性能通常更好(5)、ol.source.TileArcGISRest: 用于加载 ArcGIS Server 发布的切片地图服务如何选择?
-
选
OSM:当且仅当你明确需要使用 OpenStreetMap 的官方标准瓦片,并且希望代码尽量简洁。适合快速原型、演示或个人项目。 -
选
XYZ(大多数情况):你需要使用 OSM 以外的地图服务(高德、谷歌、Esri、Stamen、天地图等),或者需要对 OSM 瓦片做特殊定制(例如更换 OSM 的第三方样式,如CartoDB或Stamen的瓦片地址)
XYZ中,XY表示瓦片的坐标,Z表示缩放级别,通过这三个参数就可以确定瓦片的位置和大小。
国内常用XYZ瓦片服务
| 服务名称 | URL 模板 | 说明 |
|---|---|---|
| 天地图 (Tianditu) | 矢量底图:http://t{0-7}.tianditu.gov.cn/DataServer?T=vec_c&x={x}&y={y}&l={z}卫星影像: http://t{0-7}.tianditu.gov.cn/DataServer?T=img_c&x={x}&y={y}&l={z}注记图层: http://t{0-7}.tianditu.gov.cn/DataServer?T=cva_c&x={x}&y={y}&l={z} |
注意:官方服务需要申请Key,生产环境中务必申请。同时,天地图使用TMS坐标(Y轴方向与XYZ相反),通常需要配置tileLoadFunction进行转换。 |
| 高德地图 (AutoNavi) | 矢量图:http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7影像图: http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=2&style=6 |
参数丰富,可通过scl和style控制图层(如style=6影像、7路网)。 |
| 谷歌地图 (Google) | 带标签的卫星图:https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}仅路网: https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z} |
通过lyrs参数切换图层类型(m: 路线图, s: 纯卫星图, y: 带标签卫星图, p: 带标签地形图, t: 纯地形图)。 |
使用高德地图的矢量图层
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) }) ], view: new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10 }) });
效果如下:

4、layer
地图是一层一层绘制的,
Tile瓦片图层用来加载地图,只会在初始化的时候做一次;Image静态图片图层用来加载静态图片,用得比较少;vector矢量图层通常用来添加矢量数据,如标注,这是最常用的,
图层是对来自数据源的数据进行可视化表示。OpenLayers 包含四种基本类型的图层:
ol/layer/Tile- Renders sources that provide tiled images in grids that are organized by zoom levels for specific resolutions.ol/layer/Tile- 渲染源提供按缩放级别组织的网格分块图像,以适应特定分辨率。ol/layer/Image- Renders sources that provide map images at arbitrary extents and resolutions.ol/layer/Image- 渲染以任意范围和分辨率提供地图图像的源。ol/layer/Vector- Renders vector data client-side.ol/layer/Vector- 在客户端渲染矢量数据。ol/layer/VectorTile- Renders data that is provided as vector tiles.ol/layer/VectorTile- 渲染以矢量瓦片形式提供的数据。
import TileLayer from 'ol/layer/Tile.js'; // ... const layer = new TileLayer({source: source}); map.addLayer(layer);
用文本编辑器打开 style.css 文件。它应该看起来像这样:
@import "node_modules/ol/ol.css"; html, body { margin: 0; height: 100%; } #map { position: absolute; top: 0; bottom: 0; width: 100%; }
第一行代码导入了 ol 包自带的 ol.css 文件(OpenLayers 在 npm 注册表中以 ol 包的形式发布)。ol 包已在上面的 ol npm create ol-app 步骤中安装。如果您是从现有应用程序开始,而不是使用 npm create ol-app ,则需要使用 npm install ol 安装该包。默认安装最新的版本
npm install ol
ol.css表包含 OpenLayers 创建的元素的样式,例如缩放按钮。
style.css 文件中的其余规则使得包含地图的 <div id="map"> 元素填充整个页面
OpenLayers 是一个模块化、高性能、功能丰富的库,用于显示地图和地理空间数据以及与之交互。
该库内置支持各种商业和免费的图像及矢量瓦片源,以及最流行的开源和专有矢量数据格式。借助 OpenLayers 的地图投影支持,数据可以采用任何投影方式。
OpenLayers 以 ol npm 包的形式提供,它提供了官方支持的 API 的所有模块。
OpenLayers 可在所有主流浏览器(全球使用率超过 1%)上运行,包括 Chrome、Firefox、Safari 和 Edge。对于较旧的浏览器,可能需要添加 polyfill( 例如 Fastly 或 Cloudflare )。
该库旨在用于台式机/笔记本电脑和移动设备,并支持指针和触摸交互。
模块和命名规则
使用驼峰命名法的 OpenLayers 模块默认导出类,并且可以包含其他常量或函数作为命名导出:
import Map from 'ol/Map.js'; import View from 'ol/View.js';
类层次结构按其父级分组,并放在包的子文件夹中,例如 layer/
为方便起见,这些也可以作为命名导出文件提供,例如
import {Map, View} from 'ol';
import {Tile, Vector} from 'ol/layer.js';
除了这些重新导出的类之外,名称为小写的模块还提供常量或函数作为命名导出:
import {getUid} from 'ol';
import {fromLonLat} from 'ol/proj.js';
二、格栅重投影
OpenLayers 可以显示来自 WMS、WMTS、静态图像以及许多其他来源的栅格数据,并且可以使用与服务器提供的坐标系不同的坐标系。如果源投影与地图视图投影不同,则可以在客户端(浏览器中)对源数据进行重新投影。
OpenLayers 内置支持在几种投影或坐标参考系统之间转换坐标(和重新投影栅格)。
内置的重投影支持适用于以下投影:
- WGS 84 / Geographic (
EPSG:4326)
WGS 84 / 地理坐标系 (EPSG:4326) - WGS 84 / Web or Spherical Mercator (
EPSG:3857)
WGS 84 / 球面墨卡托投影 (EPSG:3857) - WGS 84 / Universal Transverse Mercator (
EPSG:32601throughEPSG:32660andEPSG:32701throughEPSG:32760)
WGS 84 / 通用横轴墨卡托投影(EPSG:32601至EPSG:32660和EPSG:32701至EPSG:32760)
默认情况下,openlayers使用的是球面墨卡托投影,如果想平铺到浏览器上,则需要使用地理坐标系EPSG:4326。
对于其他非内置投影之间的转换,可以使用 Proj4js 库。
图像的地图投影转换直接在网页浏览器中完成。用户可以在任何 Proj4js 支持的坐标参考系中查看图像,并且现在可以合并和叠加以前不兼容的图层。
用法:
内置投影支持的 API 使用方法涉及在源和视图上指定投影标识符。可以使用 EPSG 字符串代码来标识投影:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; import TileWMS from 'ol/source/TileWMS'; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new TileWMS({ projection: 'EPSG:4326', // here is the source projection url: 'https://ahocevar.com/geoserver/wms', params: { 'LAYERS': 'ne:NE1_HR_LC_SR_W_DR', }, }), }) ], view: new View({ projection: 'EPSG:3857', // here is the view projection center: [0, 0], zoom: 2 }) });
三、视图view功能演示

extent
view 的 extent 属性是限制地图可见区域的核心工具,它定义了一个矩形范围,用户无法通过平移或缩放操作超出此范围。其本质是一个长度为4的数组,用于描述一个矩形边界框,[minX, minY, maxX, maxY]
[minX, minY]:矩形左下角的坐标,[maxX, maxY]:矩形右上角的坐标
如果你的视图投影已经是 EPSG:4326(经纬度),extent 也应当使用经纬度坐标。
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) }) ], view: new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, // 添加 extent 属性,限制地图可见范围(经纬度坐标) extent: [113.5, 29.5, 115.5, 31.5] // [minX, minY, maxX, maxY] }) });
setCenter方法的使用
html
<body>
<div id="map"></div>
<div class="btns">
<button>去到北京</button>
</div>
<script type="module" src="./main.js"></script>
</body>
css
.btns{ position: fixed; left: 10px; top: 10px; }
js
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) }) ], view: view, }); const btn = document.querySelector('.btns button') btn.onclick = function(){ view.setCenter([116.46,39.92]); }
效果:

去到北京时添加动画效果animate(飞行到目标位置)

为视图添加动画效果。视图的中心、缩放(或分辨率)和旋转都可以添加动画效果,从而实现视图状态之间的平滑过渡。例如,要将视图动画切换到新的缩放级别:
view.animate({zoom: view.getZoom() + 1});
默认情况下,动画持续一秒,并使用缓入缓出效果。您可以通过设置 duration (以毫秒为单位)来自定义此行为。
btn1.onclick = function(){ // view.setCenter([116.46,39.92]); view.animate({ center: [116.46,39.92], duration: 2000, // 单位为毫秒 }) }
效果如下:

要将多个动画串联起来,请使用多个动画对象调用该方法。例如,要先缩放再平移:
view.animate({zoom: 10}, {center: [0, 0]});
练习:地图偏移按钮
html
<body>
<div id="map"></div>
<div class="btns">
<button>去到北京</button>
<button>向上移动</button>
<button>向下移动</button>
<button>向左移动</button>
<button>向右移动</button>
</div>
<script type="module" src="./main.js"></script>
</body>
js
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) }) ], view: view, }); const btn1 = document.querySelectorAll('.btns button')[0] const btn2 = document.querySelectorAll('.btns button')[1] const btn3 = document.querySelectorAll('.btns button')[2] const btn4 = document.querySelectorAll('.btns button')[3] const btn5 = document.querySelectorAll('.btns button')[4] btn1.onclick = function(){ view.setCenter([116.46,39.92]); } btn2.onclick = function(){ // 获取当前的中心点 const center = view.getCenter(); // [经度,纬度] center[1] += 0.1; view.setCenter(center); map.render(); // 刷新视图 } btn3.onclick = function(){ // 获取当前的中心点 const center = view.getCenter(); // [经度,纬度] center[1] -= 0.1; view.setCenter(center); map.render(); // 刷新视图 } btn4.onclick = function(){ // 获取当前的中心点 const center = view.getCenter(); // [经度,纬度] center[0] -= 0.1; view.setCenter(center); map.render(); // 刷新视图 } btn5.onclick = function(){ // 获取当前的中心点 const center = view.getCenter(); // [经度,纬度] center[0] += 0.1; view.setCenter(center); map.render(); // 刷新视图 }
效果如下:

四、切换底图
下面的代码中同时绘制城市矢量图、卫星底图、标记地图
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) // 卫星底图 const sateliteSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6' }) // 标记地图 const markerSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=8' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const sateliteLayer = new TileLayer({ source: sateliteSource }) const markerLayer = new TileLayer({ source: markerSource }) const map = new Map({ target: 'map', layers: [gaodeLayer,sateliteLayer,markerLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, });
效果如下:

图层会按照先后顺序一次性绘制到map,如果希望展示其中某一个,将其层级关系置顶即可。
html
<body>
<div id="map"></div>
<div class="btns">
<button>切换到城市地图</button>
<button>切换到卫星地图</button>
</div>
<script type="module" src="./main.js"></script>
</body>
js
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) // 卫星底图 const sateliteSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6' }) // 标记地图 const markerSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=8' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const sateliteLayer = new TileLayer({ source: sateliteSource }) const markerLayer = new TileLayer({ source: markerSource }) const map = new Map({ target: 'map', layers: [gaodeLayer,sateliteLayer,markerLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const btn1 = document.querySelectorAll('.btns button')[0] const btn2 = document.querySelectorAll('.btns button')[1] btn1.onclick = function(){ map.setLayers([sateliteLayer,markerLayer,gaodeLayer]) } btn2.onclick = function(){ map.setLayers([gaodeLayer,sateliteLayer,markerLayer]) }
效果如下:

setZIndex方法设置图层顺序

const btn1 = document.querySelectorAll('.btns button')[0] const btn2 = document.querySelectorAll('.btns button')[1] btn1.onclick = function(){ // map.setLayers([sateliteLayer,markerLayer,gaodeLayer]) gaodeLayer.setZIndex(100); sateliteLayer.setZIndex(50); // 当daodeLayer最大时,下面两个图层的顺序无所谓 markerLayer.setZIndex(60); } btn2.onclick = function(){ // map.setLayers([gaodeLayer,sateliteLayer,markerLayer]) gaodeLayer.setZIndex(10); sateliteLayer.setZIndex(50); markerLayer.setZIndex(60); }
使用removeLayer和addLayer方法:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) // 卫星底图 const sateliteSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6' }) // 标记地图 const markerSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=8' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const sateliteLayer = new TileLayer({ source: sateliteSource }) const markerLayer = new TileLayer({ source: markerSource }) const map = new Map({ target: 'map', layers: [sateliteLayer,markerLayer,gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const btn1 = document.querySelectorAll('.btns button')[0] const btn2 = document.querySelectorAll('.btns button')[1] btn1.onclick = function(){ map.addLayer(gaodeLayer) } btn2.onclick = function(){ map.removeLayer(gaodeLayer) }
setVisible方法控制图层显示与隐藏
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) // 卫星底图 const sateliteSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=6' }) // 标记地图 const markerSource = new XYZ({ url: 'http://webst0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=8' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const sateliteLayer = new TileLayer({ source: sateliteSource }) const markerLayer = new TileLayer({ source: markerSource }) const map = new Map({ target: 'map', layers: [sateliteLayer,markerLayer,gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const btn1 = document.querySelectorAll('.btns button')[0] const btn2 = document.querySelectorAll('.btns button')[1] btn1.onclick = function(){ gaodeLayer.setVisible(true) } btn2.onclick = function(){ gaodeLayer.setVisible(false) }
五、加载天地图
天地图官网:https://www.tianditu.gov.cn/,先注册再登录

点击矢量底图

import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const tinadituSource = new XYZ({ url: 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=您的密钥' }) const tiandituLayer = new TileLayer({ source: tinadituSource }) const map = new Map({ target: 'map', layers: [tiandituLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, });
效果如下:

这里加载出来的是影像图层,如果想加载成矢量底图,则需要把url中的img改为vec即可
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const tinadituSource = new XYZ({ url: 'http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=您的密钥' }) const tiandituLayer = new TileLayer({ source: tinadituSource }) const map = new Map({ target: 'map', layers: [tiandituLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, });
效果如下:

六、加载静态图片
瓦片图层通常作为底图存在,现在在瓦片图层的基础上再加载静态图片图层。

引入ImageLayer
import ImageLayer from 'ol/layer/Image.js';

引入Static
import Static from 'ol/source/ImageStatic.js';
完整代码:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import ImageLayer from 'ol/layer/Image.js'; import Static from 'ol/source/ImageStatic.js'; const center = [114.3165, 30.5264] const view = new View({ projection: 'EPSG:4326', // here is the view projection center: center, zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const staticImageLayer = new ImageLayer({ source: new Static({ url: "/1.png", imageExtent: [center[0] - 0.1,center[1] - 0.1, center[0] + 0.1,center[1] + 0.1] }) }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); map.addLayer(staticImageLayer) // 后面的图层会将前面的图层挡住
效果如下:

六、加载矢量图层
加载矢量数据(很多格式,如GeoJSON)到底图上

引入VectorLayer
import VectorLayer from 'ol/layer/Vector.js';

引入VectorSource
import VectorSource from 'ol/source/Vector.js';
现在我们通过发送请求的方式获取geojson数据,返回来的数据需要通过format进行格式化,
我们现在来加载中国的数据

接口地址如下:https://geo.datav.aliyun.com/areas_v3/bound/100000.json

引入GeoJSON
import GeoJSON from 'ol/format/GeoJSON.js';
完整代码:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorLayer = new VectorLayer({ source: new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) }) map.addLayer(vectorLayer)
效果:

地图中中国地图的轮廓就显示出来了。
矢量图层可以进行样式的定义

引入style
import Style from 'ol/style/Style.js';
完整代码:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorLayer = new VectorLayer({ source: new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000.json', // 处理对应的矢量数据格式 format: new GeoJSON() }), style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }) }) }) map.addLayer(vectorLayer)
效果如下:

style中常见的样式如下:

现在来加载所有省份的数据,json api为:https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json
现在来演示stroke描边属性
首先引入Stroke
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 10, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorLayer = new VectorLayer({ source: new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }), style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) map.addLayer(vectorLayer)
效果如下:

回调函数
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorSource = new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) const vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) // 加载数据需要发请求,--> 异步,在回调函数中处理数据 vectorSource.on('change',function(){ console.log('数据加载完成') console.log(this) // 打印数据源 console.log(this.getFeatures()) // 打印数据源 }) map.addLayer(vectorLayer)
打印结果:

鼠标移动到某一个省份(要素),对应的省份高亮
getFeaturesAtCoordinate(coordinate)方法:根据坐标获取要素

完整代码:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorSource = new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) const vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) // 加载数据需要发请求,--> 异步,在回调函数中处理数据 vectorSource.on('change',function(){ console.log('数据加载完成') console.log(this) // 打印数据源 console.log(this.getFeatures()) // 打印数据源 }) map.addLayer(vectorLayer) // 鼠标移动到某一个省份(要素),对应的省份高亮 map.on('pointermove',function(e){ // console.log('鼠标移动了!') // 获取当前鼠标的坐标 console.log(e) // 获取当前鼠标的经纬度 const coordinate = e.coordinate // 找当前鼠标位置是否具有要素信息 const features = vectorSource.getFeaturesAtCoordinate(coordinate) console.log(features) // 把之前的样式恢复到原来的状态 const allFeatures = vectorSource.getFeatures() allFeatures.forEach(feature =>{ feature.setStyle(new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) })) }) if(features[0]){ features[0].setStyle(new Style({ fill: new Fill({ color: 'rgba(0, 255, 170, 0.4)' }) })) } })
效果:

加载十堰市地图和河流
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import VectorLayer from 'ol/layer/Vector'; import {XYZ} from 'ol/source'; import VectorSource from 'ol/source/Vector.js'; import GeoJSON from 'ol/format/GeoJSON.js'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; // import { defaultControls } from 'ol/control'; // import Control from 'ol/control/Control.js'; import {defaults} from 'ol/control/defaults'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [110.799339, 32.663649], zoom: 10, }) // 城市矢量图 const tinadituSource = new XYZ({ url: 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=03e539a8a39145e80de85cdbc54ebf68', crossOrigin: 'anonymous' // 解决跨域问题 }) const tiandituLayer = new TileLayer({ source: tinadituSource }) const map = new Map({ target: 'map', layers: [tiandituLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, // 2. 配置控件:保留其他默认控件,但禁用 zoom (放大缩小按钮) controls: defaults({ zoom: false, // 不显示 +/- 按钮 attribution: false, // 保留右下角的版权信息 rotate: false // 可选:也不显示指北针 }) }); // 加载矢量图层(十堰地图) const vectorLayer = new VectorLayer({ source: new VectorSource({ // url: 'https://geo.datav.aliyun.com/areas_v3/bound/420300_full.json', url: '/shiyan.geojson', // 处理对应的矢量数据格式 format: new GeoJSON() }), style: new Style({ stroke: new Stroke({ color: '#FFD700', // 黄色 width: 3 }) }) }) map.addLayer(vectorLayer) // 加载东河 const dongheLayer = new VectorLayer({ source: new VectorSource({ url: '/donghe.geojson', format: new GeoJSON() }) }) map.addLayer(dongheLayer)
效果如下:

七、要素实现

1、绘制点
之前我们是通过发送请求的方式获取geojson数据,

Point

引入point
import Point from 'ol/geom/Point.js';
完整 代码:
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const iconFeature = new Feature({ geometry: new Point([114.3165, 30.5264]) }) iconFeature.setStyle(new Style({ image: new Icon({ src: '/坐标.svg', scale: 0.2 // 缩放到原来的 20% }) })) // 将要素放到矢量数据源中 const iconSource = new Vector({ features: [iconFeature] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer)
效果如下:

OpenLayers 的 Icon 样式会直接加载 src 指定的图片,并按照图片的原始宽高(像素)在地图上绘制。如果图片本身的尺寸很大(比如你的 坐标.svg 文件可能原始宽高是几百甚至上千像素),那么图标在地图上就会显得巨大。创建 Icon 时如果只提供了 src,没有指定 scale(缩放比例)或 size(强制设定显示尺寸)。因此 OpenLayers 不会对图片进行任何缩放,完全按原图大小渲染。
在绘制点时存入对象的值到点要素中,并监听点击事件获取对象
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const iconFeature = new Feature({ geometry: new Point([114.3165, 30.5264]) }) iconFeature.setProperties({"name": "周文豪","age": 38}) iconFeature.setStyle(new Style({ image: new Icon({ src: '/坐标.svg', scale: 0.2 // 缩放到原来的 20% }) })) // 将要素放到矢量数据源中 const iconSource = new Vector({ features: [iconFeature] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer) map.on('click', (event) => { // 1. 获取点击位置的像素坐标 const pixel = event.pixel; // 2. 获取点击位置的地理坐标(投影坐标系) const coordinate = event.coordinate; console.log('地图点击坐标:', coordinate); // 3. 获取点击位置的所有要素(从上到下遍历所有图层) const feature = map.forEachFeatureAtPixel(pixel, (feat, layer) => { console.log('要素所在图层:', layer); console.log('要素:', feat); return feat; // 返回第一个找到的要素 }); console.log(feature) // 4. 如果点击到了要素,获取其属性信息 if (feature) { const properties = feature.getProperties(); console.log('点击到的要素属性:', properties); } else { // 点击空白区域,可以关闭弹窗或执行其他逻辑 console.log('点击了空白区域'); } });
点击图标后,控制台打印

VectorLayer设置zIndex
绘制图片点时,为防止图层被其他图层覆盖
// ✅ 核心修复:添加 zIndex 确保图层在最上层,添加 declutter 优化渲染 this.videoMonitorPointsLayer = new VectorLayer({ source: iconSource, zIndex: 9999, // ✅ 设置很高的 zIndex,确保在最上层 declutter: false // ✅ 关闭 declutter,确保所有图标都可以点击 })
通过overlay添加标签
const labelElement = document.createElement('div'); labelElement.style.cssText = ` background-color: rgba(0, 51, 102, 0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 14px; font-weight: bold; white-space: nowrap; pointer-events: none; box-shadow: 0 2px 4px rgba(0,0,0,0.3); `; labelElement.textContent = name; // 计算多边形最北端(纬度最大)的坐标,用于标签定位在图形上方 const extent = polygonGeometry.getExtent(); // extent 格式: [minX, minY, maxX, maxY] // 获取多边形的顶部中心位置 const topCenterCoordinate = [ (extent[0] + extent[2]) / 2, // X 中心 extent[3] // Y 最大值(顶部) ]; const labelOverlay = new Overlay({ element: labelElement, position: topCenterCoordinate, // 使用顶部中心位置 positioning: 'bottom-center', // 标签底部对齐到坐标点,这样标签会显示在坐标点上方 offset: [0, -10], // 向上偏移 10px,避免紧贴多边形边缘 stopEvent: false }); this.map.addOverlay(labelOverlay);
完整代码如下:
showPolygonOnMap(polygonData,name) { console.log(polygonData,name) try { // 解析多边形数据 let data = typeof polygonData === 'string' ? JSON.parse(polygonData) : polygonData;// 转换坐标为 [[lng,lat]] 格式 const transformedCoords = data.coordinates.map(coord => [coord.longitude, coord.latitude] ); const polygonGeometry = new Polygon([transformedCoords]); const polygonFeature = new Feature({ geometry: polygonGeometry, }); const vectorSource = new VectorSource({ features: [polygonFeature] }); this.dataLayer = new VectorLayer({ source: vectorSource, style: new Style({ // 填充样式 fill: new Fill({ // 使用 rgba 设置颜色,第四个参数 (alpha) 控制透明度 (0-1) // 这里使用的是半透明蓝色 color: 'rgba(0, 0, 255, 0.5)', }), // 描边样式 stroke: new Stroke({ color: '#ffff00', // 黄色边框 (也可以用 'rgba(255, 255, 0, 1)') width: 2, // 边框宽度,单位像素 }), }) }); const labelElement = document.createElement('div'); labelElement.style.cssText = ` background-color: rgba(0, 51, 102, 0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 14px; font-weight: bold; white-space: nowrap; pointer-events: none; box-shadow: 0 2px 4px rgba(0,0,0,0.3); `; labelElement.textContent = name; // 计算多边形最北端(纬度最大)的坐标,用于标签定位在图形上方 const extent = polygonGeometry.getExtent(); // extent 格式: [minX, minY, maxX, maxY] // 获取多边形的顶部中心位置 const topCenterCoordinate = [ (extent[0] + extent[2]) / 2, // X 中心 extent[3] // Y 最大值(顶部) ]; const labelOverlay = new Overlay({ element: labelElement, position: topCenterCoordinate, // 使用顶部中心位置 positioning: 'bottom-center', // 标签底部对齐到坐标点,这样标签会显示在坐标点上方 offset: [0, -10], // 向上偏移 10px,避免紧贴多边形边缘 stopEvent: false }); this.map.addOverlay(labelOverlay); // 最后把图层加到地图实例中 this.map.addLayer(this.dataLayer); } catch (error) { console.error('显示多边形失败:', error); this.$message.error("显示多边形失败"); } },
通过点要素使用canvas绘制圆
点要素除了加载logo之外,还可以加载canvas绘制的东西
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const iconFeature = new Feature({ geometry: new Point([114.3165, 30.5264]) }) const canvas = document.createElement("canvas") canvas.width = 32 canvas.height = 32 const ctx = canvas.getContext("2d") ctx.fillStyle = 'red' ctx.beginPath() ctx.arc(16,16,8,0,2*Math.PI) ctx.fill() iconFeature.setStyle(new Style({ image: new Icon({ img: canvas, imgSize: [30,30] }) })) // 将要素放到矢量数据源中 const iconSource = new Vector({ features: [iconFeature] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer)
效果如下:

地图点击事件实现标注功能
即鼠标点击地图就给该处设置一个canvas图形
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const canvas = document.createElement("canvas") canvas.width = 32 canvas.height = 32 const ctx = canvas.getContext("2d") ctx.fillStyle = 'red' ctx.beginPath() ctx.arc(16,16,8,0,2*Math.PI) ctx.fill()// 将要素放到矢量数据源中 const iconSource = new Vector({ features: [] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer) map.on('click',function(e){ console.log(e) const coordinate = e.coordinate const iconFeature = new Feature({ geometry: new Point(coordinate) }) iconFeature.setStyle(new Style({ image: new Icon({ img: canvas, imgSize: [30,30] }) })) iconSource.addFeature(iconFeature) })
效果如下:

2、通过点要素使用内置Circle绘制圆

引入CircleStyle
import CircleStyle from 'ol/style/Circle.js';
完整代码:
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 将要素放到矢量数据源中 const iconSource = new Vector({ features: [] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer) map.on('click',function(e){ console.log(e) const coordinate = e.coordinate const iconFeature = new Feature({ geometry: new Point(coordinate) }) iconFeature.setStyle(new Style({ image: new CircleStyle({ fill: new Fill({ color: 'green' }), radius: 10, }) })) iconSource.addFeature(iconFeature) })
效果同上。
3、通过点要素添加文字标注
刚才通过在地图上点击添加了小圆点,现在来在小圆点附近添加文字
引入Text
import Text from 'ol/style/Text.js';
完整代码:
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 将要素放到矢量数据源中 const iconSource = new Vector({ features: [] }) // 矢量数据源加载到矢量图层中 const iconLayer = new VectorLayer({ source: iconSource }) // 将矢量图层添加到地图实例中去 map.addLayer(iconLayer) map.on('click',function(e){ console.log(e) const coordinate = e.coordinate const iconFeature = new Feature({ geometry: new Point(coordinate) }) iconFeature.setStyle(new Style({ image: new CircleStyle({ fill: new Fill({ color: 'green' }), radius: 10, }), text: new Text({ text: '圆', fill: new Fill({ color: 'red' }), offsetX: 0, offsetY: -20 }) })) iconSource.addFeature(iconFeature) })
效果:

4、添加线要素

引入LineString
import LineString from 'ol/geom/LineString.js';
完整代码
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { LineString, Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const lineFeature = new Feature({ geometry: new LineString([[114.3165, 30.5264],[116.46,39.92]]) // 二维数组 }) lineFeature.setStyle(new Style({ stroke: new Stroke({ color: 'red' }) })) // 将要素放到矢量数据源中 const lineSource = new VectorSource({ features: [lineFeature] }) // 矢量数据源加载到矢量图层中 const lineLayer = new VectorLayer({ source: lineSource }) // 将矢量图层添加到地图实例中去 map.addLayer(lineLayer)
效果:

点击画线功能
上面的线是指定好了坐标的位置,现在来实现通过鼠标点击来画线
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { LineString, Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 将要素放到矢量数据源中 const lineSource = new VectorSource({ features: [] }) // 矢量数据源加载到矢量图层中 const lineLayer = new VectorLayer({ source: lineSource }) // 将矢量图层添加到地图实例中去 map.addLayer(lineLayer) // 创建数组来存放坐标位置 let lineArr = [] map.on('click',function(e){ // 去掉之前的要素 lineSource.clear() let coordinate = e.coordinate lineArr.push(coordinate) if(lineArr.length == 2){ let lineFeature = new Feature({ geometry: new LineString(lineArr) // 二维数组 }) lineFeature.setStyle(new Style({ stroke: new Stroke({ color: 'red' }) })) lineSource.addFeature(lineFeature) lineArr = [] } })
效果如下:

5、添加面要素
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Circle, LineString, Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const circleFeature = new Feature({ geometry: new Circle([114.3165, 30.5264],1) }) circleFeature.setStyle(new Style({ fill: new Fill({ color: 'yellow' }), stroke: new Stroke({ color: 'white', width: 2 }) })) // 将要素放到矢量数据源中 const circleSource = new VectorSource({ features: [circleFeature] }) // 矢量数据源加载到矢量图层中 const circleLayer = new VectorLayer({ source: circleSource }) // 将矢量图层添加到地图实例中去 map.addLayer(circleLayer)
效果

此时面要素有点像静态图片,地图放大,面要素也会变大,面要素的位置是固定的
绘制多边形

代码
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Circle, LineString, Point, Polygon } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const polygonFeature = new Feature({ geometry: new Polygon([[ [114.0, 30.0], // 左下 [115.0, 30.0], // 右下 [115.0, 31.0], // 右上 [114.0, 31.0], // 左上 [114.0, 30.0] // 闭合回起点 ]]) }) polygonFeature.setStyle(new Style({ // fill: new Fill({ // color: 'yellow' // }), stroke: new Stroke({ color: 'red', width: 2 }) })) // 将要素放到矢量数据源中 const polygonSource = new VectorSource({ features: [polygonFeature] }) // 矢量数据源加载到矢量图层中 const polygonLayer = new VectorLayer({ source: polygonSource }) // 将矢量图层添加到地图实例中去 map.addLayer(polygonLayer)
效果:

通过点击地图上的点来画多边形,右键结束绘制
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Circle, LineString, Point, Polygon } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon, Stroke } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // ========= 2. 永久多边形图层(存储最终绘制的多边形)========= const finalSource = new VectorSource({ features: [] }); const finalLayer = new VectorLayer({ source: finalSource, style: new Style({ fill: new Fill({ color: 'rgba(255, 165, 0, 0.4)' }), // 橙色半透明填充 stroke: new Stroke({ color: 'white', width: 2 }) }) }); map.addLayer(finalLayer); // ========= 3. 临时图层(显示正在绘制的多边形预览)========= const tempSource = new VectorSource({ features: [] }); const tempLayer = new VectorLayer({ source: tempSource, style: new Style({ fill: new Fill({ color: 'rgba(255, 255, 0, 0.3)' }), // 半透明黄色填充 stroke: new Stroke({ color: '#ffcc00', width: 2, lineDash: [5, 5] }) }) }); map.addLayer(tempLayer); // ========= 4. 绘制状态管理 ========= let currentPoints = []; // 存储当前绘制的顶点坐标,格式 [[lng, lat], ...] // 更新临时预览图形(折线 + 多边形预览) function updateTemp() { tempSource.clear(); if (currentPoints.length < 2) return; // 4.1 绘制折线(临时边) let lineCoords = currentPoints.map(p => [p[0], p[1]]); if (currentPoints.length >= 3) { // 当点数 ≥3 时,将首点追加到末尾,形成闭合预览效果 lineCoords.push(currentPoints[0]); } const lineFeature = new Feature({ geometry: new LineString(lineCoords) }); tempSource.addFeature(lineFeature); // 4.2 绘制临时填充面(点数 ≥3 时) if (currentPoints.length >= 3) { let polyCoords = [currentPoints.map(p => [p[0], p[1]])]; // 确保多边形闭合(首尾坐标相同) const first = polyCoords[0][0]; const last = polyCoords[0][polyCoords[0].length - 1]; if (first[0] !== last[0] || first[1] !== last[1]) { polyCoords[0].push(first); } const polygonFeature = new Feature({ geometry: new Polygon(polyCoords) }); tempSource.addFeature(polygonFeature); } } // 完成当前多边形:固化到永久图层,并清空临时点集 function finishPolygon() { if (currentPoints.length >= 3) { // 构建闭合多边形坐标 let coords = currentPoints.map(p => [p[0], p[1]]); coords.push(coords[0]); // 手动闭合 const polygon = new Polygon([coords]); const feature = new Feature({ geometry: polygon }); finalSource.addFeature(feature); } // 无论是否成功,都清空当前绘制状态 currentPoints = []; tempSource.clear(); } // ========= 5. 地图事件绑定 ========= // 左键点击:添加顶点 map.on('click', function(e) { currentPoints.push(e.coordinate); // e.coordinate 是 [lng, lat] updateTemp(); }); // 右键点击:完成多边形(阻止浏览器默认右键菜单) map.getViewport().addEventListener('contextmenu', function(e) { e.preventDefault(); finishPolygon(); return false; }); // 可选:按 ESC 键取消当前绘制 window.addEventListener('keydown', function(e) { if (e.key === 'Escape') { if (currentPoints.length > 0) { currentPoints = []; tempSource.clear(); } } });
效果:

八、事件
map的事件

view的事件

change:resolution事件:当zoom缩放时触发的事件。
VectorSource的事件

addfeature事件
交互类的使用Draw和Select
Interaction 是什么?
-
作用:负责捕捉用户在设备上(鼠标、键盘、触摸屏等)的原生操作,并将其转化为地图行为(如平移、缩放、绘制等)-。
-
直观区分:地图上的按钮、滑块等比尺是
Control;拖曳、缩放、点击选中地图要素等操作,就是Interaction在处理
常见交互类型与应用场景
OpenLayers 开放了丰富的 interaction 类,这里列出最常用的几个:
-
地图操控与设备交互
-
DragPan:拖拽平移地图。 -
MouseWheelZoom:鼠标滚轮缩放地图。 -
KeyboardPan/KeyboardZoom:键盘方向键 / "+" "-" 键控制地图。 -
PinchRotate/PinchZoom:移动端双指旋转/缩放地图。 -
DragZoom:鼠标拖拽拉框缩放。
-
-
图形绘制与编辑
-
Draw:在地图上绘制点、线、面等几何图形。 -
Modify:修改已绘制图形的形状(如拖拽顶点)。 -
Select:选中地图上的特定矢量要素(常与Modify配合实现编辑)。 -
Snap:在绘制时,让鼠标自动吸附到附近要素的顶点或边缘。
-
-
高级操作
-
Translate:移动地图上的矢量要素。 -
Extent:限制地图可视范围。 -
DragRotate:旋转地图视角
-
结合 Select 和 Modify 交互,实现选中并编辑图形的功能
import Select from 'ol/interaction/Select'; import Modify from 'ol/interaction/Modify'; const selectInteraction = new Select(); const modifyInteraction = new Modify({ features: selectInteraction.getFeatures() // 修改当前被选中的要素 }); map.addInteraction(selectInteraction); map.addInteraction(modifyInteraction);

引入Select
import Select from 'ol/interaction/Select.js';
select的事件:

Select表示选择要素,默认通过鼠标点击来选中
之前有通过getFeatureAtCoordinate来选择要素,现在通过选择交互类来实现
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; import Select from 'ol/interaction/Select.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorSource = new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) const vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) // 加载数据需要发请求,--> 异步,在回调函数中处理数据 vectorSource.on('change',function(){ console.log('数据加载完成') console.log(this) // 打印数据源 console.log(this.getFeatures()) // 打印数据源 }) map.addLayer(vectorLayer) const select = new Select() // 将select添加到地图实例中 map.addInteraction(select) select.on('select',function(e){ console.log(e) const f = e.selected[0] f.setStyle(new Style({ fill: new Fill({ color: 'rgba(0,255,0,0.5)' }) })) })
效果:

上面是通过鼠标点击的方式选中,现在改成鼠标移入
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; import Select from 'ol/interaction/Select.js'; import { pointerMove } from 'ol/events/condition'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorSource = new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) const vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) // 加载数据需要发请求,--> 异步,在回调函数中处理数据 vectorSource.on('change',function(){ console.log('数据加载完成') console.log(this) // 打印数据源 console.log(this.getFeatures()) // 打印数据源 }) map.addLayer(vectorLayer) const select = new Select({ condition: pointerMove }) // 将select添加到地图实例中 map.addInteraction(select) select.on('select',function(e){ console.log(e) const f = e.selected[0] f.setStyle(new Style({ fill: new Fill({ color: 'rgba(0,255,0,0.5)' }) })) })
效果:

condition的行为如下所示:

下面通过select的过滤器filter来决定是哪个图层添加选中,默认情况下所有图层都可以选中,

代码:
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import VectorSource from 'ol/source/Vector'; import GeoJSON from 'ol/format/GeoJSON.js'; import Style from 'ol/style/Style.js'; import { Fill } from 'ol/style'; import Stroke from 'ol/style/Stroke.js'; import Select from 'ol/interaction/Select.js'; import { pointerMove } from 'ol/events/condition'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 加载矢量图层 const vectorSource = new VectorSource({ url: 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json', // 处理对应的矢量数据格式 format: new GeoJSON() }) const vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255,0,0,0.4)' }), stroke: new Stroke({ color: 'green' }) }) }) // 加载数据需要发请求,--> 异步,在回调函数中处理数据 vectorSource.on('change',function(){ console.log('数据加载完成') console.log(this) // 打印数据源 console.log(this.getFeatures()) // 打印数据源 }) map.addLayer(vectorLayer) const select = new Select({ condition: pointerMove, filter: function(feature,layer){ return layer == vectorLayer } }) // 将select添加到地图实例中 map.addInteraction(select) select.on('select',function(e){ console.log(e) const f = e.selected[0] f.setStyle(new Style({ fill: new Fill({ color: 'rgba(0,255,0,0.5)' }) })) })
Draw交互类

引入Draw
import Draw from 'ol/interaction/Draw.js';
之前绘制点、线、面看不到绘制的过程,现在使用Draw来看绘制的过程
Draw的type配置的选项有如下所示:The geometry type. One of 'Point', 'LineString', 'LinearRing', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon', 'GeometryCollection', or 'Circle'.
import './style.css'; import {Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {XYZ} from 'ol/source'; import Draw from 'ol/interaction/Draw.js'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import { Stroke, Style } from 'ol/style'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 4, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) // 将绘制的图形放到矢量图层中 const vectorLayer = new VectorLayer({ source: new VectorSource(), style: new Style({ stroke: new Stroke({ color: 'red', width: 4 }) }) }) const map = new Map({ target: 'map', layers: [gaodeLayer,vectorLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const draw = new Draw({ type: 'LineString', source: vectorLayer.getSource() }) map.addInteraction(draw) vectorLayer.getSource().on('addfeature',function(e){ console.log(e) })
效果如下:

画多边形时监听绘制结束事件
drawPolygon() { if (!this.map) { this.$message.error("地图未初始化完成,请稍后重试"); return; } // 隐藏跟随的小图标 if (this.isFollowing) { $(".mark-move").hide(); this.isFollowing = false; } // 如果已经在绘制状态,先移除 if (this.drawInteraction) { this.map.removeInteraction(this.drawInteraction); this.drawInteraction = null; } // 如果已经有图层,先移除 if (this.polygonLayer) { this.map.removeLayer(this.polygonLayer); this.polygonLayer = null; } this.$message.info("请在地图上点击绘制多边形,双击结束绘制"); // 创建矢量源 const vectorSource = new VectorSource(); // 创建矢量图层 this.polygonLayer = new VectorLayer({ source: vectorSource, style: new Style({ fill: new Fill({ color: 'rgba(255, 0, 0, 0.4)' // 半透明红色填充 }), stroke: new Stroke({ color: '#ff0000', width: 2 }) }) }); // 添加图层到地图 this.map.addLayer(this.polygonLayer); // 创建绘制交互 this.drawInteraction = new Draw({ source: vectorSource, type: 'Polygon', // 绘制多边形 freehand: false // 非手绘模式 }); // 添加到地图 this.map.addInteraction(this.drawInteraction); // 监听绘制结束事件 this.drawInteraction.on('drawend', (evt) => { const feature = evt.feature; const geometry = feature.getGeometry(); // 获取多边形坐标(EPSS:3857) const coordinates = geometry.getCoordinates(); // 转换为经纬度(EPSS:4326) const lonLatCoords = coordinates[0].map(coord => { const lonLat = coord; return { longitude: parseFloat(lonLat[0].toFixed(6)), latitude: parseFloat(lonLat[1].toFixed(6)) }; }); console.log('绘制完成,坐标:', lonLatCoords); // 保存多边形数据 this.form.riskAreaData = { name: `多边形_${new Date().getTime()}`, type: 'polygon', coordinates: lonLatCoords, properties: { pointCount: lonLatCoords.length, area: this.calculatePolygonArea(lonLatCoords), createTime: new Date().toISOString() } }; console.log(this.form) // 移除绘制交互 this.map.removeInteraction(this.drawInteraction); this.drawInteraction = null; this.$message.success("多边形绘制完成"); }); },
HTML DOM事件
| 事件 | 描述 | 属于 |
|---|---|---|
| abort | 媒体加载中止时发生该事件。 | |
| afterprint | 当页面开始打印时,或者关闭打印对话框时,发生此事件。 | Event |
| animationend | CSS 动画完成时发生此事件。 | AnimationEvent |
| animationiteration | 重复 CSS 动画时发生此事件。 | AnimationEvent |
| animationstart | CSS 动画开始时发生此事件。 | AnimationEvent |
| beforeprint | 即将打印页面时发生此事件。 | Event |
| beforeunload | 在文档即将被卸载之前发生此事件。 | |
| blur | 当元素失去焦点时发生此事件。 | FocusEvent |
| canplay | 当浏览器可以开始播放媒体时,发生此事件。 | Event |
| canplaythrough | 当浏览器可以在不停止缓冲的情况下播放媒体时发生此事件。 | Event |
| change | 当form元素的内容、选择的内容或选中的状态发生改变时,发生此事件 | Event |
| click | 当用户单击元素时发生此事件。 | MouseEvent |
| contextmenu | 当用户右键单击某个元素以打开上下文菜单时,发生此事件。 | MouseEvent |
| copy | 当用户复制元素的内容时发生此事件。 | ClipboardEvent |
| cut | 当用户剪切元素的内容时发生此事件。 | ClipboardEvent |
| dblclick | 当用户双击元素时发生此事件。 | MouseEvent |
| drag | 拖动元素时发生此事件。 | DragEvent |
| dragend | 当用户完成拖动元素后,发生此事件。 | DragEvent |
| dragenter | 当拖动的元素进入放置目标时,发生此事件。 | DragEvent |
| dragleave | 当拖动的元素离开放置目标时,发生此事件。 | DragEvent |
| dragover | 当拖动的元素位于放置目标之上时,发生此事件。 | DragEvent |
| dragstart | 当用户开始拖动元素时发生此事件。 | DragEvent |
| drop | 当将拖动的元素放置在放置目标上时,发生此事件。 | DragEvent |
| durationchange | 媒体时长改变时发生此事件。 | Event |
| ended | 在媒体播放到尽头时发生此事件。 | Event |
| error | 当加载外部文件时发生错误后,发生此事件。 | |
| focus | 在元素获得焦点时发生此事件。 | FocusEvent |
| focusin | 在元素即将获得焦点时发生此事件。 | FocusEvent |
| focusout | 在元素即将失去焦点时发生此事件。 | FocusEvent |
| fullscreenchange | 当元素以全屏模式显示时,发生此事件。 | Event |
| fullscreenerror | 当元素无法在全屏模式下显示时,发生此事件。 | Event |
| hashchange | 当 URL 的锚部分发生改变时,发生此事件。 | HashChangeEvent |
| input | 当元素获得用户输入时,发生此事件。 | |
| invalid | 当元素无效时,发生此事件。 | Event |
| keydown | 当用户正在按下键时,发生此事件。 | KeyboardEvent |
| keypress | 当用户按下键时,发生此事件。 | KeyboardEvent |
| keyup | 当用户松开键时,发生此事件。 | KeyboardEvent |
| load | 在对象已加载时,发生此事件。 | |
| loadeddata | 媒体数据加载后,发生此事件。 | Event |
| loadedmetadata | 加载元数据(比如尺寸和持续时间)时,发生此事件。 | Event |
| loadstart | 当浏览器开始查找指定的媒体时,发生此事件。 | ProgressEvent |
| message | 在通过此事件源接收消息时,发生此事件。 | Event |
| mousedown | 当用户在元素上按下鼠标按钮时,发生此事件。 | MouseEvent |
| mouseenter | 当指针移动到元素上时,发生此事件。 | MouseEvent |
| mouseleave | 当指针从元素上移出时,发生此事件。 | MouseEvent |
| mousemove | 当指针在元素上方移动时,发生此事件。 | MouseEvent |
| mouseout | 当用户将鼠标指针移出元素或其中的子元素时,发生此事件。 | MouseEvent |
| mouseover | 当指针移动到元素或其中的子元素上时,发生此事件。 | MouseEvent |
| mouseup | 当用户在元素上释放鼠标按钮时,发生此事件。 | MouseEvent |
| mousewheel | 已弃用。请改用 wheel 事件。 | WheelEvent |
| offline | 当浏览器开始脱机工作时,发生此事件。 | Event |
| online | 当浏览器开始在线工作时,发生此事件。 | Event |
| open | 当打开与事件源的连接时,发生此事件。 | Event |
| pagehide | 当用户离开某张网页进行导航时,发生此事件。 | PageTransitionEvent |
| pageshow | 在用户导航到某张网页时,发生此事件。 | PageTransitionEvent |
| paste | 当用户将一些内容粘贴到元素中时,发生此事件。 | ClipboardEvent |
| pause | 当媒体被用户暂停或以编程方式暂停时,发生此事件。 | Event |
| play | 当媒体已启动或不再暂停时,发生此事件。 | Event |
| playing | 在媒体被暂停或停止以缓冲后播放时,发生此事件。 | Event |
| popstate | 窗口的历史记录改变时,发生此事件。 | PopStateEvent |
| progress | 当浏览器正处于获得媒体数据的过程中时,发生此事件。 | Event |
| ratechange | 媒体播放速度改变时发生此事件。 | Event |
| reset | 重置表单时发生此事件。 | Event |
| resize | 调整文档视图的大小时发生此事件。 | |
| scroll | 滚动元素的滚动条时发生此事件。 | |
| search | 当用户在搜索字段中输入内容时,发生此事件。 | Event |
| seeked | 当用户完成移动/跳到媒体中的新位置时,发生该事件。 | Event |
| seeking | 当用户开始移动/跳到媒体中的新位置时,发生该事件。 | Event |
| select | 用户选择文本后(对于<input>和<textarea>)发生此事件 | |
| show | 当 <menu> 元素显示为上下文菜单时,发生此事件。 | Event |
| stalled | 当浏览器尝试获取媒体数据但数据不可用时,发生此事件。 | Event |
| storage | Web 存储区域更新时发生此事件。 | StorageEvent |
| submit | 在提交表单时发生此事件。 | Event |
| suspend | 当浏览器有意不获取媒体数据时,发生此事件。 | Event |
| timeupdate | 当播放位置更改时发生此事件。 | Event |
| toggle | 当用户打开或关闭 <details> 元素时,发生此事件。 | Event |
| touchcancel | 在触摸被中断时,发生此事件。 | TouchEvent |
| touchend | 当手指从触摸屏上移开时,发生此事件。 | TouchEvent |
| touchmove | 当手指在屏幕上拖动时,发生此事件。 | TouchEvent |
| touchstart | 当手指放在触摸屏上时,发生此事件。 | TouchEvent |
| transitionend | CSS 转换完成时,发生此事件。 | TransitionEvent |
| unload | 页面卸载后(对于 <body>),发生此事件。 | |
| volumechange | 当媒体的音量已更改时,发生此事件。 | Event |
| waiting | 当媒体已暂停但预期会恢复时,发生此事件。 | Event |
| wheel | 当鼠标滚轮在元素向上或向下滚动时,发生此事件。 | WheelEvent |
1、监听鼠标移入地图和鼠标移出地图事件
async loadMap() { // 等待DOM完全渲染 await this.$nextTick(); // 保证当前组件模板中的 DOM 结构已经完成更新 // 确保容器存在 const container = document.getElementById('cesiumContainer1'); if (!container) { console.error('地图容器未找到'); return; } // 强制设置容器尺寸 container.style.width = '100%'; container.style.height = '600px'; // 添加鼠标进入和离开地图容器的事件监听 container.addEventListener('mouseenter', this.handleMouseEnterMap); container.addEventListener('mouseleave', this.handleMouseLeaveMap); // 再次等待DOM更新 await this.$nextTick(); // 验证容器尺寸 const rect = container.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { console.error('容器宽高为0,延迟重试'); setTimeout(() => this.loadMap(), 200); return; } console.log('地图容器尺寸:', rect.width, rect.height); const imgLayer = new TileLayer({ source: new XYZ({ // 影像底图 URL 模板 (球面墨卡托投影) url: `http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${this.tiandituKey}`, crossOrigin: 'anonymous' // 解决跨域问题 }) }); const vectorLayer = new VectorLayer({ source: new VectorSource({ url: this.shiyanUrl, format: new GeoJSON() }), style: new Style({ stroke: new Stroke({ color: '#FFD700', // 黄色 width: 3 }) }) }); let layersArr = []; layersArr.push(imgLayer); layersArr.push(vectorLayer); // 创建地图 this.map = new Map({ target: "cesiumContainer1", layers: layersArr, view: new View({ projection: 'EPSG:4326', // 使用经纬度投影 center: [110.799339, 32.663649], zoom: 8.5, }), // 2. 配置控件:保留其他默认控件,但禁用 zoom (放大缩小按钮) controls: defaultControls({ zoom: false, // 不显示 +/- 按钮 attribution: false, // 保留右下角的版权信息 rotate: false // 可选:也不显示指北针 }) }); // 监听地图加载完成 this.map.once('postrender', () => { console.log('地图渲染完成'); }); console.log('OpenLayers地图初始化成功'); },
鼠标移入地图和鼠标移出地图时的方法
// 处理鼠标进入地图区域 handleMouseEnterMap() { debugger this.isMouseInMap = true; if (this.isFollowing) { $(".mark-move").show(); } }, // 处理鼠标离开地图区域 handleMouseLeaveMap() { this.isMouseInMap = false; if (this.isFollowing) { $(".mark-move").hide(); } },
2、监听右键点击(contextmenu)事件和监听鼠标移动事件
OpenLayers 地图本身(通过 ol/Map 对象)并不直接提供 contextmenu 事件监听(它主要提供的是 click、singleclick、dblclick 等与地图交互更相关的事件)。常见的做法是直接监听承载地图的 DOM 元素上的 contextmenu 事件。
下面的代码时点击“定位”按钮时获取右键时的经纬度并画图片点
catchLngLat(){ // 如果已经在捕捉状态,则关闭 if (this.catchLngLatFlag) { this.catchLngLatFlag = false; this.isFollowing = false; $(".mark-move").hide(); // 移除OpenLayers地图事件监听 if (this.map && this.mapClickHandler) { this.map.un('singleclick', this.mapClickHandler); // 移除单击事件监听器 this.mapClickHandler = null; } if (this.map && this.mapContextMenu) { this.map.un('contextmenu', this.mapContextMenu); // 移除右键菜单事件监听器 this.mapContextMenu = null; } // 恢复默认右键菜单 document.oncontextmenu = null; return; } // 开启捕捉模式 this.catchLngLatFlag = true; this.isFollowing = true; // 绑定鼠标移动事件(显示跟随标记) document.addEventListener('mousemove', this.handleMouseMove); // 为OpenLayers地图绑定右键点击事件 if (!this.map) { this.$message.warning("地图未初始化完成"); return; } // 监听右键点击(contextmenu)事件 this.mapContextMenu = (evt) => { evt.preventDefault(); // 阻止默认右键菜单 if (!this.catchLngLatFlag) return; // 获取点击位置的像素坐标 const pixel = evt.pixel; // 将像素坐标转换为地理坐标(EPSG:4326) const coordinate = this.map.getCoordinateFromPixel(pixel); if (coordinate && coordinate.length === 2) { const lng = coordinate[0].toFixed(6); const lat = coordinate[1].toFixed(6); // 更新表单数据 this.$set(this.form, 'longitude', lng); this.$set(this.form, 'latitude', lat); console.log('右键定位 - 经度:', lng, '纬度:', lat); // 隐藏跟随标记 $(".mark-move").hide(); this.isFollowing = false; this.catchLngLatFlag = false; // 移除事件监听 document.removeEventListener('mousemove', this.handleMouseMove); if (this.mapContextMenu) { this.map.un('contextmenu', this.mapContextMenu); // 移除右键菜单事件监听器 this.mapContextMenu = null; } document.oncontextmenu = null; // 显示提示 this.$message.success(`定位成功: ${lng}, ${lat}`); // 在地图上添加标记点 this.addPointMarker(parseFloat(lng), parseFloat(lat)); } }; // 绑定右键事件到OpenLayers地图 this.map.on('contextmenu', this.mapContextMenu);// 绑定右键事件到OpenLayers地图 // 阻止浏览器默认右键菜单 document.oncontextmenu = (e) => { if (this.catchLngLatFlag) { e.preventDefault(); return false; } }; this.$message.info("请在地图上右键点击进行定位"); },
鼠标移动事件执行的方法:
handleMouseMove(e) { if (this.isFollowing && this.isMouseInMap) { $(".mark-move").css({ left: (e.clientX -20 ) + 'px', top: (e.clientY - 30) + 'px', display: "block" }); } else if (this.isFollowing && !this.isMouseInMap) { // 当鼠标不在地图范围内时隐藏标记 $(".mark-move").hide(); } },
九、Overlay覆盖物
Overlay(覆盖物) 是 OpenLayers 中用于在地图上显示 DOM 元素 的机制。它可以将任意的 HTML 元素(如弹窗、标签、信息面板等)绑定到地图的特定地理坐标上,并随着地图的平移和缩放自动移动。
1. Overlay 的作用
- 将 DOM 元素与地图坐标绑定
- 自动处理坐标转换和屏幕定位
- 支持动画效果和交互事件
- 随地图视图同步移动
Overlay vs Feature
| 对比项 | Overlay | Feature |
|---|---|---|
| 渲染方式 | DOM 元素 | Canvas/SVG 矢量渲染 |
| 性能 | 适合少量元素(< 100) | 适合大量要素(> 1000) |
| 样式 | 使用 CSS 样式 | 使用 OpenLayers Style |
| 交互 | 原生 DOM 事件 | OpenLayers 事件系统 |
| 适用场景 | 弹窗、信息面板、标签 | 点线面要素、轨迹、区域 |

引入Overlay
import Overlay from 'ol/Overlay.js';
html
<div id="pointDetailDialog" class="pointDetailDialog"> <div> <div>时间</div> <div>20260424</div> </div> <div> <div>地址</div> <div>武汉</div> </div> <div> <div>描述</div> <div>一切安好</div> </div> </div>
css
.pointDetailDialog { position: absolute; width: 24rem; height: auto; z-index: 199; background-size: 100% 100%; background-repeat: no-repeat; background-color: rgba(0, 10, 30, 0.3); border-radius: 0.8rem; backdrop-filter: blur(1rem); box-shadow: 0 0 2rem rgba(0, 255, 255, 0.3); }
js
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; import Overlay from 'ol/Overlay.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); // 创建 Overlay 实例 const overlay = new Overlay({ element: document.getElementById('pointDetailDialog'), // DOM 元素 positioning: 'center-center', // 定位方式 offset: [0, -10], // 偏移量 stopEvent: true, // 是否阻止事件传播 positioning: 'bottom-center' // 底部中心对齐 }); // 将 Overlay 添加到地图 map.addOverlay(overlay); map.on('click',function(e){ console.log(e) const coordinate = e.coordinate overlay.setPosition(coordinate); })
效果:

关闭overlay
// 方式2:从地图中移除(完全删除) map.removeOverlay(overlay);
positioning 定位方式
// 格式:'垂直-水平' positioning: 'bottom-center' // 底部中心(最常用) positioning: 'center-center' // 中心对齐 positioning: 'top-left' // 左上角 positioning: 'top-right' // 右上角 positioning: 'bottom-left' // 左下角 positioning: 'bottom-right' // 右下角
图示说明
坐标点 ● ┌─────────────┐ │ │ positioning: 'top-left' │ 弹窗内容 │ └─────────────┘ ┌─────────────┐ │ │ positioning: 'top-center' │ 弹窗内容 │ └─────────────┘ 坐标点 ● ┌─────────────┐ │ │ positioning: 'center-center' │ 弹窗内容 ● │ └─────────────┘ ┌─────────────┐ │ 弹窗内容 │ positioning: 'bottom-center' └─────────────┘ 坐标点 ●
offset 偏移量
offset 属性在定位后进一步微调位置:
// 格式:[x偏移, y偏移] offset: [0, -10] // 向上偏移 10 像素 offset: [10, 0] // 向右偏移 10 像素 offset: [-145, -30] // 向左 145px,向上 30px(你的项目) offset: [0, 0] // 无偏移(默认)
常用配置选项
const overlay = new Overlay({ // 【必需】DOM 元素 element: document.getElementById('popup'), // 【可选】定位方式(默认:'top-left') positioning: 'bottom-center', // 【可选】偏移量(默认:[0, 0]) offset: [0, -10], // 【可选】是否阻止事件传播到地图(默认:false) stopEvent: true, // 【可选】是否插入到地图容器(默认:true) insertFirst: true, // 【可选】是否自动平移地图使 Overlay 可见(默认:false) autoPan: false, // 【可选】自动平动的动画配置 autoPanAnimation: { duration: 250 // 动画时长(毫秒) }, // 【可选】自动平动时的边距 autoPanMargin: 20 });
实际应用场景
场景 1:点击地图显示信息弹窗
// 1. 监听地图点击事件 map.on('click', function(event) { const feature = map.forEachFeatureAtPixel(event.pixel, f => f); if (feature) { // 2. 获取要素属性 const properties = feature.getProperties(); // 3. 更新弹窗内容 document.getElementById('popup-content').innerHTML = ` <h3>${properties.name}</h3> <p>类型:${properties.type}</p> <p>坐标:${properties.longitude}, ${properties.latitude}</p> `; // 4. 显示弹窗 overlay.setPosition(event.coordinate); $('#popup').fadeIn(300); } else { // 点击空白区域,关闭弹窗 overlay.setPosition(undefined); $('#popup').fadeOut(200); } });
场景 2:动态创建多个 Overlay
// 为每个点位创建独立的 Overlay const overlays = []; points.forEach(point => { // 创建 DOM 元素 const popupElement = document.createElement('div'); popupElement.className = 'popup'; popupElement.innerHTML = `<div>${point.name}</div>`; document.body.appendChild(popupElement); // 创建 Overlay const overlay = new Overlay({ element: popupElement, positioning: 'bottom-center', offset: [0, -10] }); overlay.setPosition(fromLonLat([point.lng, point.lat])); map.addOverlay(overlay); overlays.push(overlay); });
场景 3:带动画效果的自动平移
const overlay = new Overlay({ element: document.getElementById('popup'), positioning: 'center-center', autoPan: true, // 启用自动平移 autoPanAnimation: { duration: 500 // 平移动画 500ms }, autoPanMargin: 50 // 距离边缘 50px 时触发平移 });
使用Overlay实现图标跳动的效果:
import './style.css'; import {Feature, Map, View} from 'ol'; import TileLayer from 'ol/layer/Tile'; import {Vector, XYZ} from 'ol/source'; import VectorLayer from 'ol/layer/Vector.js'; import { Point } from 'ol/geom'; import VectorSource from 'ol/source/Vector'; import { Fill, Icon } from 'ol/style'; import Style from 'ol/style/Style.js'; import CircleStyle from 'ol/style/Circle'; import Text from 'ol/style/Text.js'; import Overlay from 'ol/Overlay.js'; const view = new View({ projection: 'EPSG:4326', // here is the view projection center: [114.3165, 30.5264], zoom: 6, }) // 城市矢量图 const gaodeSource = new XYZ({ url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7' }) const gaodeLayer = new TileLayer({ source: gaodeSource }) const map = new Map({ target: 'map', layers: [gaodeLayer], // 先画城市矢量图,再画卫星底图,最后绘制标记地图 view: view, }); const overlays = []; const container = document.createElement('div'); container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.alignItems = 'center'; container.style.justifyContent = 'center'; container.style.cursor = 'pointer'; container.style.pointerEvents = 'auto'; container.style.filter = 'drop-shadow(0 2px 4px rgba(0,0,0,0.3))'; const label = document.createElement('span'); label.textContent = "测试点"; label.style.color = 'white'; label.style.font = "bold 14px 'Microsoft YaHei', 'PingFang SC', Arial, sans-serif"; label.style.textShadow = '1px 1px 0 black'; label.style.marginBottom = '4px'; const img = document.createElement('img'); img.src = '/1.png'; img.style.width = `50px`; img.style.height = `50px`; img.style.display = 'block'; container.appendChild(label); container.appendChild(img); const overlay = new Overlay({ position: [114.3165, 30.5264], element: container, offset: [0, 0], // 初始偏移为 0 positioning: 'bottom-center' }); map.addOverlay(overlay); overlays.push(overlay); // 跳动参数:振幅(像素)和周期(毫秒,完成一次完整上下浮动的时间) const amplitude = 8; // 振幅 8px,上下浮动范围 -8 ~ +8 const period = 2000; // 周期 2 秒,平滑缓慢 // 平滑动画:使用正弦波计算偏移量 const startTime = performance.now(); function animate(currentTime) { const elapsed = currentTime - startTime; // 正弦波角度,每 period 毫秒完成 2*PI 弧度(一个完整来回) const angle = (elapsed / period) * Math.PI * 2; // 偏移量 = 振幅 * sin(角度),范围为 -amplitude ~ +amplitude const offsetY = Math.sin(angle) * amplitude; // 应用到所有 overlay overlays.forEach(overlay => { overlay.setOffset([0, offsetY]); }); // 继续动画循环 requestAnimationFrame(animate); // 请求下一帧,形成循环 } requestAnimationFrame(animate); // 启动动画
效果如下:

浙公网安备 33010602011771号