前言
地图测量功能是GIS应用中最实用的功能之一,它能够帮助用户精确测量地图上的距离和面积。在城市规划、土地管理、工程测绘等领域有着广泛的应用。本文将详细介绍如何使用OpenLayers实现一个功能完整的测量工具,包括距离测量和面积测量,并提供实时的测量结果显示和友好的用户交互体验。
项目结构分析
javascript
<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 阻止事件冒泡和默认行为
依赖引入详解
javascript
//引入依赖
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: 事件监听器管理工具
全局变量定义
javascript
/**
* 当前绘制的要素(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: 绘制线条时的提示文本
数据属性初始化
javascript
data() {
return {
map: null, // 地图实例
}
}
地图初始化
javascript
mounted() {
//初始化地图
this.map = new Map({
target: 'map',//指定挂载dom,注意必须是id
layers: [
new TileLayer({
source: new OSM()//加载OpenStreetMap
})
],
//配置视图
view: new View({
center: [113.24981689453125, 23.126468438108688], //视图中心位置
projection: "EPSG:4326", //指定投影
zoom: 12, //缩放到的级别
})
});
}
初始化说明:
- 使用OSM作为底图
- 设置广州为地图中心
- 使用WGS84坐标系
- 不添加默认控件,保持界面简洁
核心功能实现
测量控制方法 (measureCtl)
javascript
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. 清理前一次交互
javascript
if(draw){
//清除掉之前绘制的交互
this.map.removeInteraction(draw);
}
功能说明:
- 检查是否存在之前的绘制交互
- 清除旧的交互,避免冲突
- 确保每次只有一个测量工具激活
2. 创建矢量数据源和图层
javascript
//定义一个数据源
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. 绑定鼠标移动事件
javascript
//地图容器绑定鼠标移动事件,动态显示帮助提示框内容
this.map.on('pointermove', this.pointerMoveHandler);
鼠标移动处理方法 (pointerMoveHandler)
javascript
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)
javascript
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)
javascript
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)
javascript
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)
javascript
//绑定交互绘制工具开始绘制的事件
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)
javascript
//绑定交互绘制工具结束绘制的事件
draw.on('drawend', (event) => {
measureTooltipElement.className = 'tooltip tooltip-static'; //设置测量提示框的样式
measureTooltip.setOffset([0, -7]);
sketch = null; //置空当前绘制的要素对象
measureTooltipElement = null; //置空测量工具提示框对象
_this.createMeasureTooltip();//重新创建一个测试工具提示框显示结果
unByKey(listener);
});
结束处理步骤:
- 切换样式: 将提示框样式改为静态样式
- 调整位置: 微调提示框偏移
- 清理状态: 重置全局变量
- 创建新提示框: 为下次测量准备
- 清理监听器: 移除几何变化监听
测量算法实现
距离测量 (formatLength)
javascript
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)
javascript
formatArea(polygon) {
var area;
//使用测地学方法测量
var sourceProj = this.map.getView().getProjection();//地图数据源投影坐标系
var geom = (polygon.clone().transform(sourceProj, 'EPSG:4326')); //将多边形要素坐标系投影为EPSG:4326
area = 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格式: 使用上标显示平方符号
样式设计详解
工具栏样式
javascript
.MapTool {
position: absolute;
top: .5em;
right: .5em;
z-index: 9999;
}
提示框基础样式
javascript
#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;
}
样式特点:
- 半透明背景: 不遮挡地图内容
- 圆角边框: 现代化视觉效果
- 不换行: 保持提示信息简洁
测量提示框样式
javascript
#map >>> .tooltip-measure {
opacity: 1;
font-weight: bold;
}
静态提示框样式
javascript
#map >>> .tooltip-static {
background-color: #ffcc33;
color: black;
border: 1px solid white;
}
功能说明:
- 黄色背景: 突出显示最终测量结果
- 黑色文字: 提高可读性
- 白色边框: 增强视觉层次
提示框箭头样式
javascript
#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. 测量结果存储
javascript
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. 清除测量结果
javascript
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. 测量单位切换
javascript
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. 测量精度控制
javascript
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应用中不可或缺的重要功能模块。