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

OpenLayers常用控件 -- 章节七:测量工具控件教程

前言

地图测量功能是GIS应用中最实用的功能之一,它能够帮助用户精确测量地图上的距离和面积。在城市规划、土地管理、工程测绘等领域有着广泛的应用。本文将详细介绍如何使用OpenLayers实现一个功能完整的测量工具,包括距离测量和面积测量,并提供实时的测量结果显示和友好的用户交互体验。

项目结构分析

<template><div id="map"><div class="MapTool"><el-row><el-button type="primary" @click.stop.prevent="measureCtl('Polygon')">面积</el-button><el-button type="success" @click.stop.prevent="measureCtl('LineString')">距离</el-button></el-row></div></div>
</template>

模板结构详解:

  • 地图容器: id="map" 作为地图挂载点
  • 工具栏: .MapTool 包含测量功能按钮
  • 测量按钮: 分别触发面积测量和距离测量功能
  • 事件修饰符: @click.stop.prevent 阻止事件冒泡和默认行为

依赖引入详解

//引入依赖
import {Map, View, Overlay} from 'ol'
import * as ol from 'ol'
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Style from 'ol/style/Style';
import {Fill, Stroke, Circle} from 'ol/style';
import {OSM} from 'ol/source'
import TileLayer from 'ol/layer/Tile'
import {defaults as defaultControls} from 'ol/control.js';
import Draw from 'ol/interaction/Draw';
import {Polygon,LineString} from 'ol/geom';
import * as sphere from 'ol/sphere';
import {unByKey} from 'ol/Observable';

核心依赖说明:

基础组件
  • Map, View: 地图核心组件
  • Overlay: 覆盖层,用于显示测量结果提示框
  • VectorSource, VectorLayer: 矢量数据源和图层,存储绘制的测量图形
样式组件
  • Style, Fill, Stroke, Circle: 图形样式配置组件
  • 用途: 定制测量图形的显示效果
交互组件
  • Draw: 绘制交互工具,实现用户绘制测量图形
  • Polygon, LineString: 几何图形类型,用于类型判断
测量工具
  • sphere: 球面测量模块,提供精确的地理测量算法
  • unByKey: 事件监听器管理工具

全局变量定义

/*** 当前绘制的要素(Currently drawn feature.)* @type {ol.Feature}*/
var sketch;
/*** 帮助提示框对象(The help tooltip element.)* @type {Element}*/
var helpTooltipElement;
/***帮助提示框显示的信息(Overlay to show the help messages.)* @type {ol.Overlay}*/
var helpTooltip;
/*** 测量工具提示框对象(The measure tooltip element. )* @type {Element}*/
var measureTooltipElement;
/***测量工具中显示的测量值(Overlay to show the measurement.)* @type {ol.Overlay}*/
var measureTooltip;
/***  当用户正在绘制多边形时的提示信息文本* @type {string}*/
var continuePolygonMsg = 'Click to continue drawing the polygon';
/*** 当用户正在绘制线时的提示信息文本* @type {string}*/
var continueLineMsg = 'Click to continue drawing the line';
/*** 事件监听*/
var listener;
/*** 绘制交互*/
let draw;

全局变量功能分析:

绘制相关变量
  • sketch: 当前正在绘制的要素对象
  • draw: 绘制交互实例
  • listener: 几何图形变化事件监听器
提示框相关变量
  • helpTooltipElement: 帮助提示框DOM元素
  • helpTooltip: 帮助提示框覆盖层对象
  • measureTooltipElement: 测量结果提示框DOM元素
  • measureTooltip: 测量结果覆盖层对象
提示信息
  • continuePolygonMsg: 绘制多边形时的提示文本
  • continueLineMsg: 绘制线条时的提示文本

数据属性初始化

data() {return {map: null,  // 地图实例}
}

地图初始化

mounted() {//初始化地图this.map = new Map({target: 'map',//指定挂载dom,注意必须是idlayers: [new TileLayer({source: new OSM()//加载OpenStreetMap})],//配置视图view: new View({center: [113.24981689453125, 23.126468438108688], //视图中心位置projection: "EPSG:4326", //指定投影zoom: 12,  //缩放到的级别})});
}

初始化说明:

  • 使用OSM作为底图
  • 设置广州为地图中心
  • 使用WGS84坐标系
  • 不添加默认控件,保持界面简洁

核心功能实现

测量控制方法 (measureCtl)

measureCtl(measureType){if(draw){//清除掉之前绘制的交互this.map.removeInteraction(draw);}//定义一个数据源this.source = new VectorSource();this.vectorLayer = new VectorLayer({source:this.source,style: new Style({fill: new Fill({color: 'rgba(255, 255, 255, 0.2)'}),stroke: new Stroke({color: '#ffcc33',width: 2}),image: new Circle({radius: 7,fill: new Fill({color: '#ffcc33'})})})});this.map.addLayer(this.vectorLayer);//地图容器绑定鼠标移动事件,动态显示帮助提示框内容this.map.on('pointermove', this.pointerMoveHandler);//根据不同类型添加绘制的交互this.addInteraction(measureType)
}

方法详细解析:

1. 清理前一次交互
if(draw){//清除掉之前绘制的交互this.map.removeInteraction(draw);
}

功能说明:

  • 检查是否存在之前的绘制交互
  • 清除旧的交互,避免冲突
  • 确保每次只有一个测量工具激活
2. 创建矢量数据源和图层
//定义一个数据源
this.source = new VectorSource();
this.vectorLayer = new VectorLayer({source:this.source,style: new Style({fill: new Fill({color: 'rgba(255, 255, 255, 0.2)'}),stroke: new Stroke({color: '#ffcc33',width: 2}),image: new Circle({radius: 7,fill: new Fill({color: '#ffcc33'})})})
});

样式配置详解:

  • fill: 填充样式,半透明白色
  • stroke: 边框样式,黄色2像素宽度
  • image: 点样式,黄色圆圈,半径7像素
3. 绑定鼠标移动事件
//地图容器绑定鼠标移动事件,动态显示帮助提示框内容
this.map.on('pointermove', this.pointerMoveHandler);

鼠标移动处理方法 (pointerMoveHandler)

pointerMoveHandler(event) {if (event.dragging) {return;}//当前默认提示信息var helpMsg = 'Click to start drawing';//判断绘制几何类型设置相应的帮助提示信息if (sketch) {var geom = (sketch.getGeometry());if (geom instanceof Polygon) {helpMsg = continuePolygonMsg; //绘制多边形时提示相应内容} else if (geom instanceof LineString) {helpMsg = continueLineMsg; //绘制线时提示相应内容}}helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示helpTooltip.setPosition(event.coordinate);//设置帮助提示框的位置
}

功能分析:

  • 拖拽检测: 拖拽时不显示提示
  • 动态提示: 根据绘制状态显示不同提示信息
  • 位置跟随: 提示框跟随鼠标位置移动

创建帮助提示框 (createHelpTooltip)

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'});//提示框的覆盖层this.map.addOverlay(helpTooltip);
}

实现步骤:

  1. 清理旧元素: 移除之前的帮助提示框
  2. 创建DOM元素: 创建新的div元素
  3. 设置样式类: 应用tooltip样式
  4. 创建覆盖层: 配置位置偏移和定位方式
  5. 添加到地图: 将覆盖层添加到地图

创建测量提示框 (createMeasureTooltip)

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'});this.map.addOverlay(measureTooltip);
}

配置说明:

  • offset: [0, -15]: 向上偏移15像素
  • positioning: 'bottom-center': 底部居中定位
  • tooltip-measure: 专用样式类

添加绘制交互 (addInteraction)

addInteraction(measureType) {//绘制交互draw = new Draw({source: this.source,//测量绘制层数据源type: measureType,  //几何图形类型style: new Style({//绘制几何图形的样式fill: new Fill({color: 'rgba(255, 255, 255, 0.8)'}),stroke: new Stroke({color: 'rgba(0, 0, 0, 0.5)',lineDash: [10, 10],width: 2}),image: new Circle({radius: 5,stroke: new Stroke({color: 'rgba(0, 0, 0, 0.7)'}),fill: new Fill({color: 'rgba(255, 255, 255, 0.5)'})})})});//将绘制交互事件添加到地图中this.map.addInteraction(draw);//创建测量工具提示框this.createMeasureTooltip();//创建帮助提示框this.createHelpTooltip();// ...事件绑定代码
}

绘制样式特点:

  • lineDash: [10, 10]: 虚线效果,绘制时的临时样式
  • 半透明填充: 便于查看底图内容
  • 对比色边框: 黑色边框便于识别

绘制事件处理

开始绘制事件 (drawstart)
//绑定交互绘制工具开始绘制的事件
draw.on('drawstart', (event) => {sketch = event.feature; //绘制的要素var tooltipCoord = event.coordinate;// 绘制的坐标//绑定change事件,根据绘制几何类型得到测量长度值或面积值listener = sketch.getGeometry().on('change', (event) => {var geom = event.target;//绘制几何要素var output;if (geom instanceof Polygon) {output = _this.formatArea(geom);//面积值//获取一个在几何体中内部的坐标点tooltipCoord = geom.getInteriorPoint().getCoordinates();//坐标} else if (geom instanceof LineString) {output = _this.formatLength(geom);//长度值//测量长度获取到线的最后一个坐标tooltipCoord = geom.getLastCoordinate();//坐标}measureTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置});
});

事件处理流程:

  1. 保存要素引用: 将当前绘制要素存储到sketch变量
  2. 绑定几何变化事件: 监听几何图形的实时变化
  3. 实时计算: 根据几何类型调用相应的测量方法
  4. 更新显示: 实时更新测量结果和提示框位置
结束绘制事件 (drawend)
//绑定交互绘制工具结束绘制的事件
draw.on('drawend', (event) => {measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式measureTooltip.setOffset([0, -7]);sketch = null; //置空当前绘制的要素对象measureTooltipElement = null; //置空测量工具提示框对象_this.createMeasureTooltip();//重新创建一个测试工具提示框显示结果unByKey(listener);
});

结束处理步骤:

  1. 切换样式: 将提示框样式改为静态样式
  2. 调整位置: 微调提示框偏移
  3. 清理状态: 重置全局变量
  4. 创建新提示框: 为下次测量准备
  5. 清理监听器: 移除几何变化监听

测量算法实现

距离测量 (formatLength)

formatLength(line) {var length;//使用测地学方法测量var sourceProj = this.map.getView().getProjection(); //地图数据源投影坐标系length = sphere.getLength(line, {"projection": sourceProj, "radius": 6378137});var output;if (length > 100) {output = (Math.round(length / 1000 * 100) / 100) + ' ' + 'km'; //换算成KM单位} else {output = (Math.round(length * 100) / 100) + ' ' + 'm'; //m为单位}return output;//返回线的长度
}

测量原理详解:

  • sphere.getLength(): 使用球面几何算法计算真实地理距离
  • projection参数: 指定当前地图的投影坐标系
  • radius: 地球半径,6378137米(WGS84椭球体长半轴)
  • 单位转换: 自动在米和千米之间切换

面积测量 (formatArea)

formatArea(polygon) {var area;//使用测地学方法测量var sourceProj = this.map.getView().getProjection();//地图数据源投影坐标系var geom = (polygon.clone().transform(sourceProj, 'EPSG:4326')); //将多边形要素坐标系投影为EPSG:4326area = Math.abs(sphere.getArea(geom, {"projection": sourceProj, "radius": 6378137})); //获取面积var output;if (area > 10000) {output = (Math.round(area / 1000000 * 100) / 100) + ' ' + 'km<sup>2</sup>'; //换算成KM单位} else {output = (Math.round(area * 100) / 100) + ' ' + 'm<sup>2</sup>';//m为单位}return output; //返回多边形的面积
}

面积计算步骤:

  1. 坐标转换: 将多边形转换为WGS84坐标系
  2. 球面面积: 使用sphere.getArea()计算球面面积
  3. 绝对值: 确保面积为正值
  4. 单位转换: 平方米和平方千米之间自动切换
  5. HTML格式: 使用上标显示平方符号

样式设计详解

工具栏样式

.MapTool {position: absolute;top: .5em;right: .5em;z-index: 9999;
}

提示框基础样式

#map >>> .tooltip {position: relative;background: rgba(0, 0, 0, 0.5);border-radius: 4px;color: white;padding: 4px 8px;opacity: 0.7;white-space: nowrap;
}

样式特点:

  • 半透明背景: 不遮挡地图内容
  • 圆角边框: 现代化视觉效果
  • 不换行: 保持提示信息简洁

测量提示框样式

#map >>> .tooltip-measure {opacity: 1;font-weight: bold;
}

静态提示框样式

#map >>> .tooltip-static {background-color: #ffcc33;color: black;border: 1px solid white;
}

功能说明:

  • 黄色背景: 突出显示最终测量结果
  • 黑色文字: 提高可读性
  • 白色边框: 增强视觉层次

提示框箭头样式

#map >>> .tooltip-measure:before, .tooltip-static:before {border-top: 6px solid rgba(0, 0, 0, 0.5);border-right: 6px solid transparent;border-left: 6px solid transparent;content: "";position: absolute;bottom: -6px;margin-left: -7px;left: 50%;
}#map >>> .tooltip-static:before {border-top-color: #ffcc33;
}

箭头实现原理:

  • CSS三角形: 使用border属性创建三角形箭头
  • 绝对定位: 精确定位箭头位置
  • 颜色匹配: 箭头颜色与提示框背景一致

实际应用扩展

1. 测量结果存储

data() {return {map: null,measureResults: []  // 存储测量结果}
},methods: {saveMeasureResult(type, value, geometry) {const result = {id: Date.now(),type: type,value: value,geometry: geometry.clone(),timestamp: new Date()};this.measureResults.push(result);}
}

2. 清除测量结果

methods: {clearMeasurements() {// 清除图层if (this.vectorLayer) {this.map.removeLayer(this.vectorLayer);}// 清除覆盖层this.map.getOverlays().clear();// 清除交互if (draw) {this.map.removeInteraction(draw);draw = null;}// 重置变量sketch = null;listener = null;measureTooltipElement = null;helpTooltipElement = null;}
}

3. 测量单位切换

data() {return {map: null,unit: 'metric'  // 'metric' 或 'imperial'}
},methods: {formatLength(line) {var length;var sourceProj = this.map.getView().getProjection();length = sphere.getLength(line, {"projection": sourceProj, "radius": 6378137});var output;if (this.unit === 'imperial') {// 英制单位var feet = length * 3.28084;if (feet > 5280) {output = (Math.round(feet / 5280 * 100) / 100) + ' miles';} else {output = (Math.round(feet * 100) / 100) + ' ft';}} else {// 公制单位if (length > 100) {output = (Math.round(length / 1000 * 100) / 100) + ' km';} else {output = (Math.round(length * 100) / 100) + ' m';}}return output;}
}

4. 测量精度控制

methods: {formatLength(line, precision = 2) {var length;var sourceProj = this.map.getView().getProjection();length = sphere.getLength(line, {"projection": sourceProj, "radius": 6378137});var output;if (length > 100) {output = (Math.round(length / 1000 * Math.pow(10, precision)) / Math.pow(10, precision)) + ' km';} else {output = (Math.round(length * Math.pow(10, precision)) / Math.pow(10, precision)) + ' m';}return output;}
}

核心API方法总结

Draw交互API:

方法/事件功能参数说明
new Draw(options)创建绘制交互source, type, style配置数据源、几何类型、样式
drawstart开始绘制事件event返回feature和coordinate
drawend结束绘制事件event绘制完成时触发

Sphere测量API:

方法功能参数返回值
getLength(geometry, options)计算线长度几何对象、选项长度(米)
getArea(geometry, options)计算多边形面积几何对象、选项面积(平方米)

Overlay覆盖层API:

方法功能参数说明
new Overlay(options)创建覆盖层element, offset, positioning配置DOM元素和位置
setPosition(coordinate)设置位置坐标更新覆盖层位置
setOffset(offset)设置偏移[x, y]调整显示偏移

总结

本文详细介绍了OpenLayers中测量工具的完整实现方案,主要知识点包括:

  1. 交互绘制: 使用Draw交互实现用户绘制功能
  2. 实时测量: 通过几何变化事件实现实时测量计算
  3. 球面算法: 使用sphere模块进行精确的地理测量
  4. 用户界面: 通过Overlay实现友好的提示信息显示
  5. 样式定制: 通过CSS实现美观的视觉效果

测量工具的核心价值在于:

  • 精确测量: 基于球面几何的精确算法
  • 实时反馈: 绘制过程中实时显示测量结果
  • 用户友好: 清晰的提示信息和视觉反馈
  • 功能完整: 支持距离和面积两种测量类型

掌握了这个测量工具的实现方法,就可以为Web地图应用添加专业级的测量功能,广泛应用于GIS系统、工程测绘、土地管理等领域。这是Web GIS应用中不可或缺的重要功能模块。

http://www.xdnf.cn/news/20224.html

相关文章:

  • 《sklearn机器学习——聚类性能指标》Fowlkes-Mallows 得分
  • Java学习笔记二(类)
  • 【3D图像算法技术】如何在Blender中对复杂物体进行有效减面?
  • 【EXPLAIN详解:MySQL查询优化师的显微镜】
  • MacOS 使用 luarocks+wrk+luajit
  • Docker 本地开发环境搭建(MySQL5.7 + Redis7 + Nginx + 达梦8)- Windows11 版 2.0
  • Mac Intel 芯片 Docker 一键部署 Neo4j 最新版本教程
  • 【Android 消息机制】Handler
  • PDF教程|如何把想要的网页保存下来?
  • docker 推送仓库(含搭建、代理等)
  • 服务器线程高占用定位方法
  • 使用 Shell 脚本监控服务器 IOWait 并发送邮件告警
  • Python带状态生成器完全指南:从基础到高并发系统设计
  • C#实现导入CSV数据到List<T>的完整教程
  • 【基础-单选】用哪一种装饰器修饰的struct表示该结构体具有组件化能力?
  • Playwright携手MCP:AI智能体实现自主化UI回归测试
  • 第26节:GPU加速计算与Compute Shader探索
  • Homebrew执行brew install出现错误(homebrew-bottles)
  • Go语言后端开发面试实战:谢飞机的“硬核”面试之旅
  • CodeBuddy 辅助重构:去掉 800 行 if-else 的状态机改造
  • Eclipse下的一些快捷键备忘录
  • LangChain实战(十九):集成OpenAI Functions打造强大Agent
  • Day37 MQTT协议 多客户端服务器模型
  • 手写MyBatis第53弹: @Intercepts与@Signature注解的工作原理
  • 工业洗地机和商用洗地机的区别是什么?
  • 【基础-单选】关于bundleName,下列说法正确的是?
  • 波特率vs比特率
  • rh134第三章复习总结
  • 贪心算法应用:保险理赔调度问题详解
  • Java中的死锁