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

前言

地图测量功能是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);
}

实现步骤:

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

创建测量提示框 (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);//设置测量工具提示框的显示位置
    });
});

事件处理流程:

  1. 保存要素引用: 将当前绘制要素存储到sketch变量
  2. 绑定几何变化事件: 监听几何图形的实时变化
  3. 实时计算: 根据几何类型调用相应的测量方法
  4. 更新显示: 实时更新测量结果和提示框位置
结束绘制事件 (drawend)
javascript 复制代码
//绑定交互绘制工具结束绘制的事件
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)

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; //返回多边形的面积
}

面积计算步骤:

  1. 坐标转换: 将多边形转换为WGS84坐标系
  2. 球面面积: 使用sphere.getArea()计算球面面积
  3. 绝对值: 确保面积为正值
  4. 单位转换: 平方米和平方千米之间自动切换
  5. 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中测量工具的完整实现方案,主要知识点包括:

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

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

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

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

相关推荐
Hashan4 小时前
深入理解:Webpack编译原理
前端·webpack
雲墨款哥4 小时前
一个前端开发者的救赎之路-JS基础回顾(五)-数组
前端·javascript·面试
朱程4 小时前
深入JS(一):手写 Promise
前端·javascript
Hierifer5 小时前
跨端技术:浅聊双线程原理和实现
前端
FreeBuf_5 小时前
加密货币武器化:恶意npm包利用以太坊智能合约实现隐蔽通信
前端·npm·智能合约
java水泥工5 小时前
基于Echarts+HTML5可视化数据大屏展示-图书馆大屏看板
前端·echarts·html5
EndingCoder5 小时前
Electron 性能优化:内存管理和渲染效率
javascript·性能优化·electron·前端框架
半夏陌离5 小时前
SQL 实战指南:电商订单数据分析(订单 / 用户 / 商品表关联 + 统计需求)
java·大数据·前端
子兮曰5 小时前
🚀Vue3异步组件:90%开发者不知道的性能陷阱与2025最佳实践
前端·vue.js·vite