1 OpenLayers学习笔记-地图框选

359 阅读4分钟

需求

用户点击按钮,可以在地图上进行框选,框选结束后,返回框选区域内的数据。前端在用户框选结束后,获取到框选的范围:左上角经纬度和右下角经纬度,将经纬度信息发送给后台,后台返回区域内数据。

实现过程

通过OpenLayers的ol.interaction.DragBox实现。在框选结束后,通过dragBox.getGeometry().getExtent()获取框选区域的范围坐标。返回值是一个数组[左上角经度,左上角纬度,右下角经度,右下角纬度]。但是经纬度值是投影坐标系(EPSG:3857),需要使用ol.proj.transform转换为我们一般常见的EPSG4326坐标系,然后发送给后台。

知识点

  • ol.source.OSM(),使用OpenStreetMap提供的切片数据,等同于https://tile.openstreetmap.org/{z}/{x}/{y}.png。国内访问可能有限制,可以使用高德地图或者天地图的瓦片服务,或者使用天地图的服务,天地图的需要注册并申请key,高德地图的无需,直接使用即可。

    const openStreetMap = new ol.layer.Tile({
      source: new ol.source.OSM()
    });
    // 等同于
    const osmLayer = new ol.layer.Tile({
      source: new ol.source.XYZ({
          url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
      })
    });
    // 高德矢量 http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}
    
  • 天地图的矢量底图和矢量注记是分开的,需要添加两个图层,高德地图的是在一起的。

  • ol.proj.transform()可实现不同的坐标的转换,([经度,纬度],坐标系1,坐标系2)

  • new ol.interaction.DragBox()condition参数可以控制框选的触发时机。ol.events.condition.platformModifierKeyOnly表示按住Ctrl+鼠标左键进行框选。ol.events.condition.noModifierKeys表示不用其他辅助按键,直接鼠标左键框选。

  • map.removeInteraction(dragBox)删除添加到地图上的交互。

  • 可通过ol.util.VERSION查看openlayers查看版本号。

代码HTML+CSS+JS

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>地图上框选范围</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css" integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <style type="text/css">
        * {
            padding: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body, html {
            width: 100%;
            height: 100%;
            margin: 0;
            font-family: "Microsoft YaHei"
        }
        #map, #app {
            width: 100vw;
            height: 100vh;
        }
        #info {
            position: fixed;
            right: 10px;
            top: 10px;
            background-color: #fff;
            box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
            width: 250px;
            padding: 10px;
            text-align: center;
            border-radius: 5px;
        }
        button {
            font-size: 18px;
            border: none;
            padding: 12px 20px;
            border-radius: 4px;
            color: #fff;
            background-color: #409eff;
            border-color: #409eff;
            cursor: pointer;
            border: 1px solid #dcdfe6;
        }
        p {
            position: fixed;
            left: 10px;
            top: 10px;
            z-index: 2;
            padding: 2px 10px;
            color: #fff;
            background-color: #07c170;
            font-size: 12px;
            border-radius: 3px;
        }
        button:disabled {
            color: rgba(16, 16, 16, 0.3);
            background-color: rgba(239, 239, 239, 0.3);
            cursor: not-allowed;
        }
        .lonlat > div {
            display: flex;
            padding: 15px;
            justify-content: space-between;
            border-bottom: 1px solid rgba(7, 193, 96, 0.4);
        }
        .dragBox {
            background-color: rgba(7, 193, 96, 0.4);
        }
        [v-cloak] {
          display: none;
        }
    </style>
</head>
<body>
<div id="app" v-cloak>
    <div id="map" @mousemove='handleMouseMoveOnMap' @mouseenter='handleMouseMoveOnMap' @mouseleave='handleMouseLeaveOnMap'></div>
    <p :style='{left: clientX, top: clientY}'>按住鼠标左键拖拽框选</p>
    <div id="info">
        <button @click='handleClickSelectMap' :disabled='isDisabled'>地图上框选范围</button>
        <div class="lonlat">
            <div>
                <span>左上角经度:</span>
                <span>{{lonlat1[0]}}</span>
            </div>
            <div>
                <span>左上角纬度:</span>
                <span>{{lonlat1[1]}}</span>
            </div>
            <div>
                <span>右下角经度:</span>
                <span>{{lonlat2[0]}}</span>
            </div>
            <div>
                <span>右下角纬度:</span>
                <span>{{lonlat2[1]}}</span>
            </div>
        </div> 
    </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js" integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js" integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
     const { createApp } = Vue;
     const vm = createApp({
        data() {
            return {
                map: {}, // 地图实例
                isDisabled: false, // 按钮状态 框选中
                dragBox: {}, // 拖拽框选实例
                lonlat1: [], // 左上角经纬度
                lonlat2: [], // 右下角经纬度
                clientX: '-100%', // 提示信息left值
                clientY: '-100%' // 提示信息top值
            }
        },
        methods: {
            // 初始化地图
            initMap() {
                // 高德地图瓦片地址
                const amapVectorLayer = new ol.layer.Tile({
                    source: new ol.source.XYZ({
                        url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
                    })
                });
                this.map = new ol.Map({
                    target: 'map',
                    layers: [amapVectorLayer],
                    view: new ol.View({
                        center: [12129346.151443446, 4217286.994156892],
                        zoom: 5
                    })
                });
                // 绑定地图点击事件,打印经纬度
                this.map.on('click', (e) => {
                    const point = ol.proj.transform(e.coordinate, 'EPSG:3857', 'EPSG:4326')
                    console.log(`经度:${point[0].toFixed(6)} 纬度:${point[1].toFixed(6)}`);
                });
            },
            // 鼠标点击按钮,创建拖拽实例,绑定拖拽结束事件
            handleClickSelectMap () {
                this.isDisabled = true;
                this.dragBox = new ol.interaction.DragBox({
                    className: 'dragBox', // 可自定义css样式
                    // condition: ol.events.condition.platformModifierKeyOnly, // ctrl
                    condition: ol.events.condition.noModifierKeys,
                });
                this.map.addInteraction(this.dragBox);
                this.dragBox.on('boxend', (e) => {
                    this.isDisabled = false;
                    const boxExtent = this.dragBox.getGeometry().getExtent();
                    this.lonlat1 = ol.proj.transform([boxExtent[0], boxExtent[1]], 'EPSG:3857', 'EPSG:4326').map(e => e.toFixed(6));
                    this.lonlat2 = ol.proj.transform([boxExtent[2], boxExtent[3]], 'EPSG:3857', 'EPSG:4326').map(e => e.toFixed(6));
                    this.map.removeInteraction(this.dragBox); // 框选解释,删除拖拽款选交互
                    this.handleMouseLeaveOnMap(); // 框选结束,隐藏提示信息
                });
                dragBox.on('boxstart', function () {
                  console.log('===========开始框选===========');
                });
            },
            // 地图绑定鼠标移动事件,提示拖拽框选方法
            handleMouseMoveOnMap (e) {
                if (!this.isDisabled) {
                    return;
                }
                this.clientX = e.clientX + 'px';
                this.clientY = e.clientY + 'px';
            },
            // 鼠标离开地图,隐藏提示信息
            handleMouseLeaveOnMap () {
                this.clientX = '-100%';
                this.clientY = '-100%';
            }
        },
        mounted() {
            this.initMap();
        }
    }).mount('#app');
</script>
</body>
</html>

参看文章

OpenLayers地图框选官网栗子

OpenLayers地图款选官网文档

高德谷歌腾讯天地图地图瓦片url

天地图各种瓦片服务地址

EPSG:3857坐标系知识

EPSG4326坐标系知识

OSM介绍