5 OpenLayers学习笔记-Overlay显示

278 阅读8分钟

需求

用户点击地图上的点位,在点击位置弹出弹框,展示点位的更多信息。

实现过程

通过new ol.Overlay({})实现,地图初始化后,创建Overlay,并将其添加到地图上。在用户点击到对应的点位后,设置Overlay指定的DOM元素的内容,然后显示Overlay

知识点

  • Overlay用于在地图上叠加额外的 HTML 元素,如弹出框、信息窗口等,Overlay的优势是可以自定义各种css样式,所以也有人使用Overlay来渲染点位。
  • new ol.Overlay({autoPan})的参数autoPan设置为true时,如果弹框不在视口范围内,自动移动地图,让其可见。有时候点击不生效。
  • new ol.Overlay({positioning})的参数positioning用于设置弹框相对于传入坐标点的位置,可设置为bottom-leftbottom-centerbottom-rightcenter-leftcenter-centercenter-righttop-lefttop-centertop-right
  • new ol.Overlay({element})的参数element用于指定Overlay使用的DOM元素。
  • Overlay只是将我们指定的DOM元素在我们调用Overlay.setPosition(coordinates)的时候,将其放到地图的指定位置上,DOM元素的样式和内容需我们自己实现。
  • 隐藏Overlay可使用Overlay.getElement().style.display = 'none'
  • 彻底删除Overlay使用Overlay.setMap(null)需要注意的是删除Overlay时,Overlay创建时指定的DOM元素也一并被删除

代码HTML+CSS+JS

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <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" />
    <title>Overlay显示</title>
    <style>
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    :root {
        --top-height: 50px;
    }
    html,
    body,
    #app,
    .app-map {
        height: 100%;
        height: 100%;
    }
    .app-btns {
        position: fixed;
        right: 10px;
        top: 10px;
        background-color: #fff;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .5);
        width: 210px;
        padding: 25px;
        text-align: center;
        border-radius: 5px;
        display: flex;
        flex-direction: column;
        z-index: 2;
    }
    .app-btns 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;
        margin-bottom: 5px;
    }
    .app-btns button:hover {
        background: #66b1ff;
        border-color: #66b1ff;
    }
    .app-btns button.active {
        background-color: #07c160;
    }
    #popup {
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
        background-color: #fff;
        width: 350px;
        z-index: 13;
        border-radius: 4px;
        border: 1px solid #ebeef5;
        font-size: 16px;
    }
    #popup .top {
        height: var(--top-height);
        display: flex;
        border-bottom: 1px solid #ebeef5;
        padding-left: 20px;
    }

    #popup .top .name {
        width: calc(100% - var(--top-height));
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        line-height: var(--top-height);
    }
    #popup .top .close {
        width: var(--top-height);
        color: #606266;
        cursor: pointer;
        background-color: #fff;
        border: none;
        border-left: 1px solid #ebeef5;
        font-size: 16px;
    }
    #popup .top .close:hover {
        background-color: #eee;
        color: #66b1ff;
    }
    #popup .bot {
        padding: 0 20px;
    }
    #popup .bot li {
        height: var(--top-height);
        display: flex;
        justify-content: space-between;
    }
    #popup .bot li span {
        display: inline-block;
        line-height: var(--top-height);
        width: calc(100% - 80px);
    }
    #popup .bot li span.label {
        width: 80px;
    }
    </style>
</head>
<body>
    <div id="app">
        <div class="app-map" id="app-map"></div>
        <div class="app-btns">
            <button @click='handleClickRemoveOverlay'>删除弹框</button>
        </div>
        <div id="popup">
            <div class="top">
                <div class="name">{{clickPointInfo.name}}</div>
                <button class="close" @click='handleCloseOverlay'>
                    关闭
                </button>
            </div>
            <div class="bot">
                <ul>
                    <li>
                        <span class="label">点位ID:</span>
                        <span>{{clickPointInfo.random}}</span>
                    </li>
                    <li>
                        <span class="label">经纬度:</span>
                        <span>{{clickPointInfo.center.join(' , ')}}</span>
                    </li>
                </ul>
            </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;
    // feature图片
    const base64Img = 'data:image/png;base64,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';
    // 基础样式style
    const baseStyle = new ol.style.Style({
        image: new ol.style.Icon({
            src: base64Img,
            scale: 1.5,
            anchor: [0.5, 0.5],
            rotateWithView: true,
            rotation: 0,
            opacity: 1
        }),
    });
    // 点位信息
    const pointData = [{
            name: '北京市',
            center: [116.405285, 39.904989]
        },
        {
            name: '天津市',
            center: [117.190182, 39.125596]
        },
        {
            name: '河北省',
            center: [114.502461, 38.045474]
        },
        {
            name: '山西省',
            center: [112.549248, 37.857014]
        },
        {
            name: '内蒙古自治区',
            center: [111.670801, 40.818311]
        },
        {
            name: '辽宁省',
            center: [123.429096, 41.796767]
        },
        {
            name: '吉林省',
            center: [125.3245, 43.886841]
        },
        {
            name: '黑龙江省',
            center: [126.642464, 45.756967]
        },
        {
            name: '上海市',
            center: [121.472644, 31.231706]
        },
        {
            name: '江苏省',
            center: [118.767413, 32.041544]
        },
        {
            name: '浙江省',
            center: [120.153576, 30.287459]
        },
        {
            name: '安徽省',
            center: [117.283042, 31.86119]
        },
        {
            name: '福建省',
            center: [119.306239, 26.075302]
        },
        {
            name: '江西省',
            center: [115.892151, 28.676493]
        },
        {
            name: '山东省',
            center: [117.000923, 36.675807]
        },
        {
            name: '河南省',
            center: [113.665412, 34.757975]
        },
        {
            name: '湖北省',
            center: [114.298572, 30.584355]
        },
        {
            name: '湖南省',
            center: [112.982279, 28.19409]
        },
        {
            name: '广东省',
            center: [113.280637, 23.125178]
        },
        {
            name: '广西壮族自治区',
            center: [108.320004, 22.82402]
        },
        {
            name: '海南省',
            center: [110.33119, 20.031971]
        },
        {
            name: '重庆市',
            center: [106.504962, 29.533155]
        },
        {
            name: '四川省',
            center: [104.065735, 30.659462]
        },
        {
            name: '贵州省',
            center: [106.713478, 26.578343]
        },
        {
            name: '云南省',
            center: [102.712251, 25.040609]
        },
        {
            name: '西藏自治区',
            center: [91.132212, 29.660361]
        },
        {
            name: '陕西省',
            center: [108.948024, 34.263161]
        },
        {
            name: '甘肃省',
            center: [103.823557, 36.058039]
        },
        {
            name: '青海省',
            center: [101.778916, 36.623178]
        },
        {
            name: '宁夏回族自治区',
            center: [106.278179, 38.46637]
        },
        {
            name: '新疆维吾尔自治区',
            center: [87.617733, 43.792818]
        },
        {
            name: '台湾省',
            center: [121.509062, 25.044332]
        },
        {
            name: '香港特别行政区',
            center: [114.173355, 22.320048]
        },
        {
            name: '澳门特别行政区',
            center: [113.54909, 22.198951]
        },
    ]
    const vm = createApp({
        data() {
            return {
                map: {},
                overlay: null,
                clickPointInfo: {
                    name: '',
                    center: []
                }
            }
        },
        methods: {
            // 初始化地图
            initMap() {
                // 高德地图瓦片地址
                const vectorLayer = 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}'
                    }),
                    name: '初始化地图图层'
                });
                //  初始化地图
                this.map = new ol.Map({
                    target: 'app-map',
                    layers: [vectorLayer],
                    view: new ol.View({
                        projection: 'EPSG:3857',
                        //设定中心点,因为默认坐标系为 3587,所以要将我们常用的经纬度坐标系4326 转换为 3587坐标系
                        center: ol.proj.transform([111.8453154, 32.7383500], 'EPSG:4326', 'EPSG:3857'),
                        zoom: 5,
                    })
                });
                // 绑定地图事件
                this.bindMapEvt();
                // 渲染点位到地图上
                this.renderPoint();
                // 创建一个 Overlay
                this.overlay = new ol.Overlay({
                    element: document.getElementById('popup'), // 弹出框的 HTML 元素
                    positioning: 'bottom-center', // 设置定位方式,这里是底部中心
                    autoPan: true, // 当overlay不在视口内的时候,移动地图,让其可见
                    stopEvent: false, // 是否停止事件传播
                });
                // 将Overlay添加到地图上
                this.map.addOverlay(this.overlay);
            },
             // 绑定地图事件
            bindMapEvt() {
                // 监听鼠标点击
                this.map.on('click', (evt) => {
                    // 根据点位像素位置,获取此位置的要素feature
                    const feature = this.map.forEachFeatureAtPixel(evt.pixel, function(feature) {
                        return feature;
                    });
                    if (feature) {
                        // 获取要素的坐标
                        const coordinates = feature.getGeometry().getCoordinates();
                        // 获取要素的信息
                        this.clickPointInfo = feature.get('info');
                        if (this.overlay) {
                             // 设置弹出框的位置为点击的位置 
                            console.log(coordinates);
                            this.overlay.setPosition(coordinates);
                            // 显示弹出框
                            this.overlay.getElement().style.display = 'block';
                        } else {
                            alert(`点击的是${this.clickPointInfo.name},经纬度:${this.clickPointInfo.center.join(',')}`)
                        }
                    } else {
                        // 如果没有点击到要素,隐藏弹出框
                        this.overlay && (this.overlay.getElement().style.display = 'none');
                    }
                });
                // 监听鼠标移动,移动到feature上时,鼠标变为可点击的状态
                this.map.on('pointermove', (e) => {
                    // 获取像素位置
                    let pixel = this.map.getEventPixel(e.originalEvent);
                    // 根据点位像素位置,获取此位置的要素feature
                    let feature = this.map.forEachFeatureAtPixel(pixel, (feature) => {
                        return feature;
                    });
                    // 要素存在,并且是需要改变鼠标样式为pointer的feature,鼠标样式变为pointer,否则auto
                    if (feature && feature.get('pointer')) {
                        this.map.getTargetElement().style.cursor = 'pointer';
                    } else {
                        this.map.getTargetElement().style.cursor = 'auto';
                    }
                });
            },
            // 删除Overlay
            handleClickRemoveOverlay() {
                if (this.overlay != null) {
                    this.overlay.setMap(null);
                    this.overlay = null;
                }
                alert('overlay已删除');
            },
            // 点击上的关闭
            handleCloseOverlay() {
                this.overlay.getElement().style.display = 'none';
            },
            // 在地图上添加点位
            renderPoint(numPoints) {
                // 根据positions创建一个新的数据源和要素数组,
                const vectorSource = new ol.source.Vector({
                    features: pointData.map(e => {
                        // ol.proj.fromLonLat用于将经纬度坐标从 WGS84 坐标系转换为地图投影坐标系
                        const feature = new ol.Feature({
                            geometry: new ol.geom.Point(ol.proj.fromLonLat(e.center)),
                            info: {
                                random: Math.ceil(Math.random() * 100000),
                                ...e
                            },
                            pointer: true
                        });
                        feature.setStyle(baseStyle);
                        return feature;
                    })
                });
                // 创建带有数据源的矢量图层
                this.pointLayer = new ol.layer.Vector({
                    source: vectorSource,
                    layerID: 'addpointLayer',
                    name: '点位图层1'
                });
                // 将矢量图层添加到地图上
                this.map.addLayer(this.pointLayer);
            }
        },
        mounted() {
            this.initMap();
        }
    }).mount('#app')
    </script>
</body>
</html>

参考文章

Overlay官方文档

Overlay官方栗子