当前位置: 首页 > 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;将在武汉・中国…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...