一、快速入门

官方文档:https://openlayers.org/doc/

image

点击API docs

image

左侧的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)即可查看你的新应用程序。

创建的项目目录如下:

image

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

image

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
  })
});

效果如下:

image

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
参数丰富,可通过sclstyle控制图层(如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
  })
});

效果如下:

image

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:32601 through EPSG:32660 and EPSG:32701 through EPSG: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功能演示

image

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]);
}

效果:

screenshots

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

image

为视图添加动画效果。视图的中心、缩放(或分辨率)和旋转都可以添加动画效果,从而实现视图状态之间的平滑过渡。例如,要将视图动画切换到新的缩放级别:

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, // 单位为毫秒
  })
}

效果如下:

screenshots

要将多个动画串联起来,请使用多个动画对象调用该方法。例如,要先缩放再平移:

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(); // 刷新视图
}

效果如下:

screenshots

四、切换底图

下面的代码中同时绘制城市矢量图、卫星底图、标记地图

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,
});

效果如下:

image

图层会按照先后顺序一次性绘制到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])
}

效果如下:

screenshots

setZIndex方法设置图层顺序

image

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/,先注册再登录

image

点击矢量底图

image

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,
});

效果如下:

image

这里加载出来的是影像图层,如果想加载成矢量底图,则需要把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,
});

效果如下:

image

六、加载静态图片

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

image

引入ImageLayer

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

image

引入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) // 后面的图层会将前面的图层挡住

效果如下:

image

六、加载矢量图层

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

image

引入VectorLayer

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

image

引入VectorSource

import VectorSource from 'ol/source/Vector.js';

现在我们通过发送请求的方式获取geojson数据,返回来的数据需要通过format进行格式化,

我们现在来加载中国的数据

image

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

image

引入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)

效果:

image

地图中中国地图的轮廓就显示出来了。

矢量图层可以进行样式的定义

image

引入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)

效果如下:

image

style中常见的样式如下:

image

现在来加载所有省份的数据,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)

效果如下:

image

回调函数

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)

打印结果:

image

鼠标移动到某一个省份(要素),对应的省份高亮

getFeaturesAtCoordinate(coordinate)方法:根据坐标获取要素

image

完整代码:

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)'
    })
  }))
  }
})

效果:

screenshots

加载十堰市地图和河流

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)

效果如下:

image

七、要素实现

image

1、绘制点

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

image

Point

image

引入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)

效果如下:

image

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('点击了空白区域');
        }
      });

点击图标后,控制台打印

image

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)

效果如下:

image

地图点击事件实现标注功能

即鼠标点击地图就给该处设置一个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)
})

效果如下:

screenshots

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

image

引入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)
})

效果:

image

4、添加线要素

image

引入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)

效果:

image

点击画线功能

上面的线是指定好了坐标的位置,现在来实现通过鼠标点击来画线

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 = []
  }
})

效果如下:

screenshots

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)

效果

image

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

绘制多边形

image

代码

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)

效果:

image

通过点击地图上的点来画多边形,右键结束绘制

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();
            }
        }
    });

效果:

screenshots

八、事件

map的事件

image

 view的事件

image

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

VectorSource的事件

image

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);

image

引入Select

import Select from 'ol/interaction/Select.js';

select的事件:

image

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)'
    })
  }))
})

效果:

screenshots

上面是通过鼠标点击的方式选中,现在改成鼠标移入

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)'
    })
  }))
})

效果:

screenshots

condition的行为如下所示:

image

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

image

代码:

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交互类

image

引入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)
})

效果如下:

screenshots

画多边形时监听绘制结束事件

    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 事件监听(它主要提供的是 clicksingleclickdblclick 等与地图交互更相关的事件)。常见的做法是直接监听承载地图的 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

对比项OverlayFeature
渲染方式 DOM 元素 Canvas/SVG 矢量渲染
性能 适合少量元素(< 100) 适合大量要素(> 1000)
样式 使用 CSS 样式 使用 OpenLayers Style
交互 原生 DOM 事件 OpenLayers 事件系统
适用场景 弹窗、信息面板、标签 点线面要素、轨迹、区域

image

引入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);
})

效果:

image

关闭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);  // 启动动画

效果如下:

screenshots

 

posted on 2026-04-28 11:25  周文豪  阅读(204)  评论(0)    收藏  举报