当前位置: 首页 > news >正文

86.在 Vue 3 中使用 OpenLayers 自定义组件(放大、缩小、长度测量、面积测量)

摘要
在 WebGIS 开发中,OpenLayers 是一个非常强大的开源地图库,它可以在 Web 应用中渲染高效的地图。本篇文章将介绍如何在 Vue 3 中使用 OpenLayers,并封装一个自定义地图控件组件,实现地图的放大、缩小、长度测量和面积测量功能。


1. 项目介绍

在 WebGIS 相关的前端开发中,OpenLayers 是一个流行的选择。结合 Vue 3,我们可以利用 Composition API 更好地封装和管理地图逻辑,使代码更加清晰和可维护。

本篇文章将介绍如何在 Vue 3 项目中集成 OpenLayers,并基于此封装一个自定义地图组件,提供以下功能:

  1. 放大(Zoom In)
  2. 缩小(Zoom Out)
  3. 测量长度(Measure Length)
  4. 测量面积(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 开发中&#xff0c;OpenLayers 是一个非常强大的开源地图库&#xff0c;它可以在 Web 应用中渲染高效的地图。本篇文章将介绍如何在 Vue 3 中使用 OpenLayers&#xff0c;并封装一个自定义地图控件组件&#xff0c;实现地图的放大、缩小、长度测量和面积测量功能…...

http 与 https 的区别?

HTTP(超文本传输协议)和 HTTPS(安全超文本传输协议)是互联网通信的基础协议。随着网络技术的发展和安全需求的提升,HTTPS变得越来越重要。本文将深入探讨HTTP与HTTPS之间的区别,包括其工作原理、安全性、性能、应用场景及未来发展等。 1. HTTP与HTTPS的基本概念 1.1 HT…...

SAIL-RK3576单板运行7b的deepseek对话模型

大概流程&#xff1a; 使用ollama工具进行deepseek的模型部署和使用&#xff0c;先安装ollama工具&#xff0c;在使用ollama工具拉去deepseek模型&#xff0c;最后使用ollama工具加载deepseek模型进行对话...

独立C++ asio库实现的UDP Server

以下是一个使用独立的 C Asio 库实现的 UDP 服务器的示例代码。这个 UDP 服务器可以监听指定端口&#xff0c;接收客户端发送的数据&#xff0c;并将接收到的数据原样返回给客户端。 #include <iostream> #include <asio.hpp> #include <array>class UdpSer…...

SQL Server STUFF 函数的用法及应用场景

在 SQL Server 中&#xff0c;STUFF 函数是一种强大的字符串处理工具&#xff0c;常用于删除指定位置的字符并插入新的字符。通过这个函数&#xff0c;开发者能够灵活地修改字符串&#xff0c;从而在数据处理、字符串拼接和格式化等方面大显身手。本文将深入探讨 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 页&#xff0c;针对图 3-4-3&#xff0c;提出了一个问题&#xff1a;“点 A A A 到 W \mathbb{W} W 上的一个点的距离有无穷多个。现在&#xff0c;我们最关心的是其中最短的那个&#xff0c;怎么找&#xff1f;请参阅 3.6 节。”并且&#xff0c;在…...

VisionPro 划痕检测小练习

划痕检测,我这里用到的是Sobel算子和blob斑点匹配以及blob里面的形态学调整 Sobel 是一种在数字图像处理和计算机视觉领域广泛应用的算法&#xff0c;主要用于边缘检测 脚本展示 #region namespace imports using System; using System.Collections; using System.Drawing; …...

解析 2025 工业边缘计算:三大技术风向的影响力

工业数字化转型的加速&#xff0c;工业边缘计算市场正呈现出蓬勃发展的态势。展望 2025 年&#xff0c;以下三大技术将成为引领工业边缘计算发展的重要风向标。 其一&#xff0c;人工智能与边缘计算的深度融合。人工智能技术将更广泛地应用于工业边缘设备&#xff0c;实现更智…...

企语企业管理系iFair(F23.2_a0)在Debian操作系统中的安装

起因&#xff1a;在安装了F24.8版本后&#xff0c;发现生产用环境和测试、开发用环境还是分开的好。 旧版的用来实验、测试&#xff0c;新版的一步一步小心的配置、使用是比较稳妥的操作。因此&#xff0c;决定在KVM虚拟机上搭建一个F23.2版本的企语系统。 一、 存在的问题 而…...

如何在Flask中处理静态文件

哈喽,大家好,我是木头左! 本文将详细介绍如何在Flask中处理静态文件,包括如何配置静态文件夹、如何访问静态文件以及如何处理静态文件的缓存问题。 配置静态文件夹 在Flask中,你可以通过static_folder参数来指定静态文件夹。默认情况下,Flask会在项目的根目录下寻找名为…...

无人机飞行试验大纲

‌无人机飞行试验大纲‌ ‌编制日期‌&#xff1a;2025年02月11日 ‌一、试验目的与背景‌ 本次无人机飞行试验旨在验证无人机的飞行性能、控制系统稳定性、机体结构强度以及各项任务执行能力。随着无人机技术在各个领域的广泛应用&#xff0c;对其性能进行全面、系统的测试显得…...

C语言初阶牛客网刷题——JZ65 不用加减乘除做加法】【难度:简单】

1. 题目描述 牛客网OJ题链接 写一个函数&#xff0c;求两个整数之和&#xff0c;要求在函数体内不得使用、-、*、/四则运算符号。 2. 分析 十进制相加思想&#xff1a; 157 &#xff0c; 先计算不考虑进位的相加结果 12 &#xff08;因为 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 是框架的核心类&#xff0c;负责自动化训练流程、分布式训练、日志记录、模型保存等复杂操作。通过配置参数即可快速实现高效训练&#xff0c;无需手动编写循环代码。以下是详细介绍和使用示例&#xff1a; Trainer 的核心功能 自动化训练循环 自…...

mysql监控--慢查询

一、监控配置 二、慢查询文件 在 MySQL 中&#xff0c;慢查询日志记录了执行时间较长的查询&#xff0c;通常&#xff0c;慢查询日志可能会生成以下几种文件&#xff1a; 1. 慢查询日志文件 这是最主要的文件&#xff0c;记录了执行时间超过设置阈值的 SQL 查询。可以通过 …...

Conda 包管理:高效安装、更新和删除软件包

Conda 包管理&#xff1a;高效安装、更新和删除软件包 1. 引言 在使用 Anaconda 进行 Python 开发时&#xff0c;包管理是日常操作的核心内容。Conda 提供了一整套高效的工具来管理 Python 环境中的软件包&#xff0c;避免了版本冲突&#xff0c;并确保了环境的一致性。 本篇…...

AcWing 798. 差分矩阵

题目来源&#xff1a; 找不到页面 - AcWing 题目内容&#xff1a; 输入一个 n 行 m 列的整数矩阵&#xff0c;再输入 q 个操作&#xff0c;每个操作包含五个整数 x1,y1,x2,y2,c&#xff0c;其中 (x1,y1) 和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将…...

通用定时器学习记录

简介 通用定时器&#xff1a;TIM2/TIM3/TIM4/TIM5 主要特性&#xff1a;16位递增、递减、中心对齐计数器&#xff08;计数值0~65535&#xff09; 16位预分频器&#xff08;分频系数1~65536&#xff09; 可用于触发DAC、ADC 在更新事件、触发事件、输入捕获、输出比较时&am…...

科技之光闪耀江城:2025武汉国际半导体产业与电子技术博览会5月15日盛大开幕

在科技浪潮汹涌澎湃的当下&#xff0c;半导体产业作为现代信息技术的中流砥柱&#xff0c;正以令人惊叹的速度重塑着世界的面貌。2025年5月15-17日&#xff0c;一场聚焦半导体与电子技术前沿的行业盛会 ——2025 武汉国际半导体产业与电子技术博览会&#xff0c;将在武汉・中国…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅

目录 前言 操作系统与驱动程序 是什么&#xff0c;为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中&#xff0c;我们在使用电子设备时&#xff0c;我们所输入执行的每一条指令最终大多都会作用到硬件上&#xff0c;比如下载一款软件最终会下载到硬盘上&am…...