86.在 Vue 3 中使用 OpenLayers 自定义组件(放大、缩小、长度测量、面积测量)
摘要
在 WebGIS 开发中,OpenLayers 是一个非常强大的开源地图库,它可以在 Web 应用中渲染高效的地图。本篇文章将介绍如何在 Vue 3 中使用 OpenLayers,并封装一个自定义地图控件组件,实现地图的放大、缩小、长度测量和面积测量功能。
1. 项目介绍
在 WebGIS 相关的前端开发中,OpenLayers 是一个流行的选择。结合 Vue 3,我们可以利用 Composition API 更好地封装和管理地图逻辑,使代码更加清晰和可维护。
本篇文章将介绍如何在 Vue 3 项目中集成 OpenLayers,并基于此封装一个自定义地图组件,提供以下功能:
- 放大(Zoom In)
- 缩小(Zoom Out)
- 测量长度(Measure Length)
- 测量面积(Measure Area)
2. 安装 OpenLayers
首先,我们需要在 Vue 3 项目中安装 OpenLayers。
(1)创建 Vue 3 项目
如果你还没有 Vue 3 项目,可以使用以下命令创建一个新的 Vue 3 项目:
npm create vite@latest vue3-openlayers --template vue
cd vue3-openlayers
npm install
(2)安装 OpenLayers
运行以下命令安装 OpenLayers:
npm install ol
3. 编写 OpenLayers 自定义组件
在 components 目录下创建 OpenLayersMap.vue 组件,该组件负责加载地图并提供交互功能。
完整代码
<!--* @Author: 彭麒* @Date: 2025/2/14* @Email: 1062470959@qq.com* @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。-->
<template><div class="container"><div class="w-full flex justify-center flex-wrap"><div class="font-bold text-[24px]">在Vue3中使用OpenLayers自定义组件(放大、缩小、长度测量、面积测量)</div></div><div class="controlbox"><div class="getlength0" @click="getLength('length')"></div><div class="getarea0" @click="getArea('area')"></div><div class="zoomIn" @click="zoomIn"></div><div class="zoomOut" @click="zoomOut"></div></div><div id="vue-openlayers"></div></div>
</template><script setup>
import { onMounted, ref } from "vue";
import "ol/ol.css";
import { Map, View } from "ol";
import Tile from "ol/layer/Tile";
import OSM from "ol/source/OSM";
import MeasureTool from "@/utils/OpenLayersMeasure.ts";
import * as control from "ol/control";const map = ref(null);const zoomIn = () => {if (map.value) {let czoom = map.value.getView().getZoom();map.value.getView().setZoom(czoom + 1);}
};const zoomOut = () => {if (map.value) {let czoom = map.value.getView().getZoom();map.value.getView().setZoom(czoom - 1);}
};const getLength = (type) => {clearMeasure();MeasureTool.measure(map.value, type, true);
};const getArea = (type) => {clearMeasure();MeasureTool.measure(map.value, type, true);
};const clearMeasure = () => {MeasureTool.measure(map.value, "", false);
};const initMap = () => {let raster = new Tile({source: new OSM(),name: "OSM",});map.value = new Map({target: "vue-openlayers",layers: [raster],view: new View({center: [-12000000, 4700000],zoom: 2,}),controls: control.defaults({zoom: false,rotate: false,attribution: false,}).extend([]),});
};onMounted(() => {initMap();
});
</script><style scoped>
.container {width: 840px;height: 590px;margin: 50px auto;border: 1px solid #42B983;position: relative;
}#vue-openlayers {width: 800px;height: 470px;margin: 0 auto;border: 1px solid #42B983;position: relative;
}.controlbox {position: absolute;z-index: 200;bottom: 50px;width: 30px;padding: 5px 7px;height: 120px;right: 30px;border: 1px solid #ccc;border-radius: 4px;cursor: pointer;display: flex;align-content: space-between;flex-direction: column;background-color: #fff;
}.getlength0 {width: 18px;height: 30px;background: url(@/assets/OpenLayers/getlength.png) center center no-repeat;background-size: 16px 16px;
}.getarea0 {width: 18px;height: 30px;background: url(@/assets/OpenLayers/getarea.png) center center no-repeat;background-size: 16px 16px;
}.zoomIn {width: 18px;height: 30px;background: url(@/assets/OpenLayers/zoomin.png) center center no-repeat;background-size: 16px 16px;
}.zoomOut {width: 18px;height: 30px;background: url(@/assets/OpenLayers/zoomout.png) center center no-repeat;background-size: 16px 16px;
}
</style>
4. 测量工具 OpenLayersMeasure.ts
创建 utils/OpenLayersMeasure.ts 文件,封装 OpenLayers 的测量功能:
import Draw from 'ol/interaction/Draw';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Point from 'ol/geom/Point';
import { unByKey } from 'ol/Observable';
import Overlay from 'ol/Overlay';
import { Feature } from 'ol';
import { getArea, getLength } from 'ol/sphere';
import LineString from 'ol/geom/LineString';
import Polygon from 'ol/geom/Polygon';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';export default {measure(map, measureType, show) {let source = new VectorSource(); // 创建一个新的矢量源let sketch; // 当前绘制的要素let helpTooltipElement; // 帮助提示元素let helpTooltip; // 显示帮助消息的覆盖层let measureTooltipElement; // 测量提示元素let measureTooltip; // 显示测量结果的覆盖层const continuePolygonMsg = ''; // 绘制多边形时显示的消息const continueLineMsg = ''; // 绘制线条时显示的消息createMeasureTooltip(); // 创建测量提示createHelpTooltip(); // 创建帮助提示const pointerMoveHandler = function (evt) {if (evt.dragging) {return;}let helpMsg = '请点击开始绘制'; // 默认帮助消息if (sketch) {const geom = sketch.getGeometry();if (geom instanceof Polygon) {helpMsg = continuePolygonMsg; // 如果是多边形,显示相应消息} else if (geom instanceof LineString) {helpMsg = continueLineMsg; // 如果是线条,显示相应消息}}helpTooltipElement.innerHTML = helpMsg; // 更新帮助提示内容helpTooltip.setPosition(evt.coordinate); // 设置帮助提示位置helpTooltipElement.classList.remove('hidden'); // 显示帮助提示};map.on('pointermove', pointerMoveHandler); // 监听指针移动事件map.getViewport().addEventListener('mouseout', function () {helpTooltipElement.classList.add('hidden'); // 鼠标移出视口时隐藏帮助提示});let draw; // 绘制交互const formatLength = function (line) {const sourceProj = map.getView().getProjection(); // 获取投影坐标系const length = getLength(line, { projection: sourceProj }); // 计算长度let output;if (length > 100) {output = (Math.round(length / 1000 * 100) / 100) + ' km'; // 如果长度大于100米,显示为公里} else {output = (Math.round(length * 100) / 100) + ' m'; // 否则显示为米}return output;};const formatArea = function (polygon) {const sourceProj = map.getView().getProjection(); // 获取投影坐标系const area = getArea(polygon, { projection: sourceProj }); // 计算面积let output;if (area > 10000) {output = (Math.round(area / 1000000 * 100) / 100) + ' km<sup>2</sup>'; // 如果面积大于10000平方米,显示为平方公里} else {output = (Math.round(area * 100) / 100) + ' m<sup>2</sup>'; // 否则显示为平方米}return output;};for (const layerTmp of map.getLayers().getArray()) {if (layerTmp.get('name') == 'feature') {source = layerTmp.getSource(); // 获取存放要素的矢量层}}function addInteraction() {const type = (measureType == 'area' ? 'Polygon' : 'LineString'); // 根据测量类型设置绘制类型draw = new Draw({source: source,type: type,style: new Style({fill: new Fill({color: 'rgba(255, 255, 255, 0.2)', // 填充颜色}),stroke: new Stroke({color: 'rgba(255, 0, 0, 0.5)', // 线条颜色lineDash: [10, 10], // 虚线样式width: 2, // 线条宽度}),image: new CircleStyle({radius: 5, // 圆点半径stroke: new Stroke({color: 'rgba(0, 0, 0, 0.7)', // 圆点边框颜色}),fill: new Fill({color: 'rgba(255, 255, 255, 0.2)', // 圆点填充颜色}),}),}),});map.addInteraction(draw); // 添加绘制交互let listener;draw.on('drawstart', function (evt) {sketch = evt.feature; // 设置当前绘制的要素let tooltipCoord = evt.coordinate; // 提示坐标listener = sketch.getGeometry().on('change', function (evt) {const geom = evt.target;let output;if (geom instanceof Polygon) {output = formatArea(geom); // 格式化面积tooltipCoord = geom.getInteriorPoint().getCoordinates(); // 获取多边形内部点坐标} else if (geom instanceof LineString) {output = formatLength(geom); // 格式化长度tooltipCoord = geom.getLastCoordinate(); // 获取线条最后一个点的坐���}measureTooltipElement.innerHTML = output; // 更新测量提示内容measureTooltip.setPosition(tooltipCoord); // 设置测量提示位置});map.on('dblclick', function (evt) {const point = new Point(evt.coordinate);source.addFeature(new Feature(point)); // 添加双击点要素});});draw.on('drawend', function () {measureTooltipElement.className = 'tooltip tooltip-static'; // 设置测量提示样式measureTooltip.setOffset([0, -7]); // 设置测量提示偏移sketch = null; // 清空当前绘制的要素measureTooltipElement = null; // 清空测量提示元素createMeasureTooltip(); // 创建新的测量提示unByKey(listener); // 移除监听器map.un('pointermove', pointerMoveHandler); // 移除指针移动事件监听map.removeInteraction(draw); // 移除绘制交互helpTooltipElement.classList.add('hidden'); // 隐藏帮助提示});}function createHelpTooltip() {if (helpTooltipElement) {helpTooltipElement.parentNode.removeChild(helpTooltipElement); // 移除旧的帮助提示元素}helpTooltipElement = document.createElement('div');helpTooltipElement.className = 'tooltip hidden'; // 设置帮助提示样式helpTooltip = new Overlay({element: helpTooltipElement,offset: [15, 0], // 设置偏移positioning: 'center-left', // 设置定位方式});map.addOverlay(helpTooltip); // 添加帮助提示覆盖层}function createMeasureTooltip() {if (measureTooltipElement) {measureTooltipElement.parentNode.removeChild(measureTooltipElement); // 移除旧的测量提示元素}measureTooltipElement = document.createElement('div');measureTooltipElement.className = 'tooltip tooltip-measure'; // 设置测量提示样式measureTooltip = new Overlay({element: measureTooltipElement,offset: [0, -15], // 设置偏移positioning: 'bottom-center', // 设置定位方式});map.addOverlay(measureTooltip); // 添加测量提示覆盖层}addInteraction(); // 添加绘制交互const vector = new VectorLayer({name: 'lineAndArea',source: source,style: new Style({fill: new Fill({color: 'rgba(255, 255, 255, 0.2)', // 填充颜色}),stroke: new Stroke({color: 'red', // 线条颜色width: 2, // 线条宽度}),image: new CircleStyle({radius: 7, // 圆点半径fill: new Fill({color: '#ffcc33', // 圆点填充颜色}),}),}),zIndex: 16, // 设置图层顺序});if (show && measureType) {map.addLayer(vector); // 显示测量图层} else {map.getOverlays().clear(); // 清除所有覆盖层map.getLayers().getArray().forEach((layer, index, array) => {if (layer.get('name') == 'lineAndArea') {map.removeLayer(layer); // 移除测量图层}});}},
};
5. 在 Vue 项目中使用组件
在 App.vue 中引入 OpenLayersMap.vue 组件:
<template> <div> <OpenLayersMap /> </div>
</template>
<script setup>
import OpenLayersMap from "@/components/OpenLayersMap.vue";
</script>
6. 运行项目
在项目根目录运行以下命令,启动 Vue 开发服务器:
npm run dev
然后在浏览器中访问 http://localhost:5173/,你就可以看到 OpenLayers 地图,并且可以使用放大、缩小、长度测量和面积测量功能了。

7. 结语
本篇文章介绍了如何在 Vue 3 项目中集成 OpenLayers,并封装了一个自定义地图控件组件,实现了放大、缩小、长度测量和面积测量功能。
你可以根据自己的需求扩展更多功能,比如:
- 添加更多测量单位
- 显示测量结果的弹窗
- 增加图层切换等功能
希望这篇文章能帮助到你!如果你觉得有用,请点赞支持!🚀🚀🚀
💬 交流与讨论
如果你有任何问题或建议,欢迎在评论区留言交流!💡
相关文章:
86.在 Vue 3 中使用 OpenLayers 自定义组件(放大、缩小、长度测量、面积测量)
摘要 在 WebGIS 开发中,OpenLayers 是一个非常强大的开源地图库,它可以在 Web 应用中渲染高效的地图。本篇文章将介绍如何在 Vue 3 中使用 OpenLayers,并封装一个自定义地图控件组件,实现地图的放大、缩小、长度测量和面积测量功能…...
http 与 https 的区别?
HTTP(超文本传输协议)和 HTTPS(安全超文本传输协议)是互联网通信的基础协议。随着网络技术的发展和安全需求的提升,HTTPS变得越来越重要。本文将深入探讨HTTP与HTTPS之间的区别,包括其工作原理、安全性、性能、应用场景及未来发展等。 1. HTTP与HTTPS的基本概念 1.1 HT…...
SAIL-RK3576单板运行7b的deepseek对话模型
大概流程: 使用ollama工具进行deepseek的模型部署和使用,先安装ollama工具,在使用ollama工具拉去deepseek模型,最后使用ollama工具加载deepseek模型进行对话...
独立C++ asio库实现的UDP Server
以下是一个使用独立的 C Asio 库实现的 UDP 服务器的示例代码。这个 UDP 服务器可以监听指定端口,接收客户端发送的数据,并将接收到的数据原样返回给客户端。 #include <iostream> #include <asio.hpp> #include <array>class UdpSer…...
SQL Server STUFF 函数的用法及应用场景
在 SQL Server 中,STUFF 函数是一种强大的字符串处理工具,常用于删除指定位置的字符并插入新的字符。通过这个函数,开发者能够灵活地修改字符串,从而在数据处理、字符串拼接和格式化等方面大显身手。本文将深入探讨 STUFF 函数的语…...
MongoDB进阶篇-索引
文章目录 1. 索引概述 2. 索引的类型 2.1 单字段索引 2.2 复合索引 2.3 其他索引 2.3.1 地理空间索引(Geospatial Index) 2.3.2 文本索引(Text Indexes) 2.3.3 哈希索引(Hashed Indexes) 3. 索引相关操作 3.1 查看索引 3.2 创建索引 3.3.1 创建单字段索引 3.3.2 创建复合…...
《机器学习数学基础》补充资料:柯西—施瓦茨不等式以及相关证明
《机器学习数学基础》 153 页,针对图 3-4-3,提出了一个问题:“点 A A A 到 W \mathbb{W} W 上的一个点的距离有无穷多个。现在,我们最关心的是其中最短的那个,怎么找?请参阅 3.6 节。”并且,在…...
VisionPro 划痕检测小练习
划痕检测,我这里用到的是Sobel算子和blob斑点匹配以及blob里面的形态学调整 Sobel 是一种在数字图像处理和计算机视觉领域广泛应用的算法,主要用于边缘检测 脚本展示 #region namespace imports using System; using System.Collections; using System.Drawing; …...
解析 2025 工业边缘计算:三大技术风向的影响力
工业数字化转型的加速,工业边缘计算市场正呈现出蓬勃发展的态势。展望 2025 年,以下三大技术将成为引领工业边缘计算发展的重要风向标。 其一,人工智能与边缘计算的深度融合。人工智能技术将更广泛地应用于工业边缘设备,实现更智…...
企语企业管理系iFair(F23.2_a0)在Debian操作系统中的安装
起因:在安装了F24.8版本后,发现生产用环境和测试、开发用环境还是分开的好。 旧版的用来实验、测试,新版的一步一步小心的配置、使用是比较稳妥的操作。因此,决定在KVM虚拟机上搭建一个F23.2版本的企语系统。 一、 存在的问题 而…...
如何在Flask中处理静态文件
哈喽,大家好,我是木头左! 本文将详细介绍如何在Flask中处理静态文件,包括如何配置静态文件夹、如何访问静态文件以及如何处理静态文件的缓存问题。 配置静态文件夹 在Flask中,你可以通过static_folder参数来指定静态文件夹。默认情况下,Flask会在项目的根目录下寻找名为…...
无人机飞行试验大纲
无人机飞行试验大纲 编制日期:2025年02月11日 一、试验目的与背景 本次无人机飞行试验旨在验证无人机的飞行性能、控制系统稳定性、机体结构强度以及各项任务执行能力。随着无人机技术在各个领域的广泛应用,对其性能进行全面、系统的测试显得…...
C语言初阶牛客网刷题——JZ65 不用加减乘除做加法】【难度:简单】
1. 题目描述 牛客网OJ题链接 写一个函数,求两个整数之和,要求在函数体内不得使用、-、*、/四则运算符号。 2. 分析 十进制相加思想: 157 , 先计算不考虑进位的相加结果 12 (因为 57 的不考虑进位的结果是 2 &#x…...
git 记录
git 记录 报错warning: unknown value given to http.version: 2 报错 warning: unknown value given to http.version: ‘2’ 删除指定http版本 git config --global --unset http.version...
PyTorch Lightning Trainer介绍
PyTorch Lightning 的 Trainer 是框架的核心类,负责自动化训练流程、分布式训练、日志记录、模型保存等复杂操作。通过配置参数即可快速实现高效训练,无需手动编写循环代码。以下是详细介绍和使用示例: Trainer 的核心功能 自动化训练循环 自…...
mysql监控--慢查询
一、监控配置 二、慢查询文件 在 MySQL 中,慢查询日志记录了执行时间较长的查询,通常,慢查询日志可能会生成以下几种文件: 1. 慢查询日志文件 这是最主要的文件,记录了执行时间超过设置阈值的 SQL 查询。可以通过 …...
Conda 包管理:高效安装、更新和删除软件包
Conda 包管理:高效安装、更新和删除软件包 1. 引言 在使用 Anaconda 进行 Python 开发时,包管理是日常操作的核心内容。Conda 提供了一整套高效的工具来管理 Python 环境中的软件包,避免了版本冲突,并确保了环境的一致性。 本篇…...
AcWing 798. 差分矩阵
题目来源: 找不到页面 - AcWing 题目内容: 输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将…...
通用定时器学习记录
简介 通用定时器:TIM2/TIM3/TIM4/TIM5 主要特性:16位递增、递减、中心对齐计数器(计数值0~65535) 16位预分频器(分频系数1~65536) 可用于触发DAC、ADC 在更新事件、触发事件、输入捕获、输出比较时&am…...
科技之光闪耀江城:2025武汉国际半导体产业与电子技术博览会5月15日盛大开幕
在科技浪潮汹涌澎湃的当下,半导体产业作为现代信息技术的中流砥柱,正以令人惊叹的速度重塑着世界的面貌。2025年5月15-17日,一场聚焦半导体与电子技术前沿的行业盛会 ——2025 武汉国际半导体产业与电子技术博览会,将在武汉・中国…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
