前言
在前端 GIS 开发中,测量距离和面积是一个非常常见的功能,尤其是在地图系统的构建中。本文将基于 Vue 3 Composition API 和 OpenLayers,通过引用封装好的测量工具类,实现地图距离和面积的测量功能。代码模块化,适用于项目中快速集成,同时也方便二次扩展。
项目准备
在开始之前,请确保你已经具备以下开发环境:
Vue 3 项目:已创建基于 Vue CLI 或 Vite 的 Vue 3 工程。
OpenLayers:安装最新的 OpenLayers 版本。使用以下命令安装:
npm install ol
Element-Plus:本文使用 Element-Plus 的按钮组件作为操作按钮。安装命令如下:
npm install element-plus
实现功能
我们需要实现以下功能:
- 初始化地图,加载 OpenLayers 的基础瓦片图层(OSM)。
- 点击按钮时可以测量 长度 或 面积。
- 提供 清除功能,清空测量的图层。
- 提供模块化封装,方便代码复用。
效果预览
运行项目后,可以看到以下效果:
- 点击“测量长度”按钮,鼠标在地图上画线段,实时显示距离。
- 点击“测量面积”按钮,鼠标画多边形,实时显示面积。
- 点击“清除”按钮,移除所有测量结果。
封装测量工具类
我们先封装一个测量工具类,命名为 measure.js
。代码如下:
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); // 移除测量图层
}
});
}
},
};
Vue 3 页面代码
接下来,我们在 src/views/MeasureMap.vue
文件中实现地图页面:
<template>
<div class="container">
<div class="w-full flex justify-center flex-wrap">
<div class="font-bold text-[24px]">在Vue3中使用OpenLayers测量距离和面积( 引用封装代码版 )</div>
</div>
<h4>
<el-button type="success" size="small" @click="getLength('length')">测量长度</el-button>
<el-button type="success" size="small" @click="getArea('area')">测量面积</el-button>
<el-button type="warning" size="small" @click="clear">清除</el-button>
</h4>
<p class="message">{
{ message }}</p>
<div id="vue-openlayers"></div>
</div>
</template>
<script lang="ts" setup>
import 'ol/ol.css';
import { ref, onMounted } from 'vue';
import { Map, View } from 'ol';
import Tile from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
import MeasureTool from '@/utils/OpenLayersMeasure';
const map = ref<Map | null>(null);
const message = ref<string>('');
const initMap = () => {
const raster = new Tile({
source: new OSM(),
name: 'OSM',
});
map.value = new Map({
target: 'vue-openlayers',
layers: [raster],
view: new View({
center: [-12000000, 4700000],
zoom: 10,
}),
});
};
const getLength = (type: 'length' | 'area') => {
clear();
message.value = '请双击以测量长度';
if (map.value) {
MeasureTool.measure(map.value, type, true);
}
};
const getArea = (type: 'length' | 'area') => {
clear();
message.value = '请单击以测量面积';
if (map.value) {
MeasureTool.measure(map.value, type, true);
}
};
const clear = () => {
message.value = '';
if (map.value) {
MeasureTool.measure(map.value, '', false);
}
};
onMounted(() => {
initMap();
});
</script>
<style scoped>
.container {
width: 840px;
height: 590px;
margin: 50px auto;
border: 1px solid #42B983;
}
#vue-openlayers {
width: 800px;
height: 400px;
margin: 0 auto;
border: 1px solid #42B983;
position: relative;
}
.message{
color: red;
font-size: 18px;
font-weight: bold;
}
</style>
总结
通过本文的示例代码,我们实现了基于 Vue 3 和 OpenLayers 的测量距离和面积功能。代码封装后更加简洁,也更方便在项目中快速复用。如果你对 GIS 开发感兴趣,可以尝试扩展此功能,比如支持更多的测量样式、坐标显示等。
喜欢本篇文章,记得点赞支持!如有问题,欢迎留言交流!