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);
}
实现步骤:
- 清理旧元素: 移除之前的帮助提示框
- 创建DOM元素: 创建新的div元素
- 设置样式类: 应用tooltip样式
- 创建覆盖层: 配置位置偏移和定位方式
- 添加到地图: 将覆盖层添加到地图
创建测量提示框 (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);//设置测量工具提示框的显示位置});
});
事件处理流程:
- 保存要素引用: 将当前绘制要素存储到sketch变量
- 绑定几何变化事件: 监听几何图形的实时变化
- 实时计算: 根据几何类型调用相应的测量方法
- 更新显示: 实时更新测量结果和提示框位置
结束绘制事件 (drawend)
//绑定交互绘制工具结束绘制的事件
draw.on('drawend', (event) => {measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式measureTooltip.setOffset([0, -7]);sketch = null; //置空当前绘制的要素对象measureTooltipElement = null; //置空测量工具提示框对象_this.createMeasureTooltip();//重新创建一个测试工具提示框显示结果unByKey(listener);
});
结束处理步骤:
- 切换样式: 将提示框样式改为静态样式
- 调整位置: 微调提示框偏移
- 清理状态: 重置全局变量
- 创建新提示框: 为下次测量准备
- 清理监听器: 移除几何变化监听
测量算法实现
距离测量 (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; //返回多边形的面积
}
面积计算步骤:
- 坐标转换: 将多边形转换为WGS84坐标系
- 球面面积: 使用sphere.getArea()计算球面面积
- 绝对值: 确保面积为正值
- 单位转换: 平方米和平方千米之间自动切换
- 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中测量工具的完整实现方案,主要知识点包括:
- 交互绘制: 使用Draw交互实现用户绘制功能
- 实时测量: 通过几何变化事件实现实时测量计算
- 球面算法: 使用sphere模块进行精确的地理测量
- 用户界面: 通过Overlay实现友好的提示信息显示
- 样式定制: 通过CSS实现美观的视觉效果
测量工具的核心价值在于:
- 精确测量: 基于球面几何的精确算法
- 实时反馈: 绘制过程中实时显示测量结果
- 用户友好: 清晰的提示信息和视觉反馈
- 功能完整: 支持距离和面积两种测量类型
掌握了这个测量工具的实现方法,就可以为Web地图应用添加专业级的测量功能,广泛应用于GIS系统、工程测绘、土地管理等领域。这是Web GIS应用中不可或缺的重要功能模块。