• Openlayers 自定义气泡框以及定位到气泡框


    OpenLayers 教程

    Openlayers 气泡框功能,主要用来展示地图要素的属性信息,也被称为气泡、卡片、弹出框等等,笔者习惯称为气泡框。

    气泡框功能很常用,场景一般是,在地图叠加点线面的时候同时展示当前要素(Feature)的基础信息,比如名称、数量、地址等;还有就是点击地图叠加元素的时候再弹出气泡框,来展示信息。

    气泡框因容器大小限制,一般只展示简略信息,想展示详细信息的话,往往通过新开页面或者弹出大的 div 容器展示。

    一般来说,气泡框属于地图覆盖物(Overlay),属于 dom 元素,没有在 canvas 内部,层级位于图层之上,作为顶层元素存在;气泡框一般都会包含关闭图标或按钮,可以点击关闭,也有特殊场景,不允许关闭,不过如果不关闭的话,直接使用 dom 元素实现就可以。

    气泡框作为地图对象元素加载的好处之一,就是可以跟随地图移动,效果更加符合使用习惯。

    本文主要介绍,气泡框的初始化、气泡框展示信息、气泡框样式、气泡框定位

    Openlayers 自定义气泡框以及定位到气泡框

    <html lang="en">
    <head>
        <meta charSet="utf-8">
        <!--注意:openlayers 原版的比较慢,这里引起自己服务器版-->
        <link rel="stylesheet" href="http://openlayers.vip/examples/css/ol.css" type="text/css">
        <style>
            /* 注意:这里必须给高度,否则地图初始化之后不显示;一般是计算得到高度,然后才初始化地图 */
            .map {
                height: 700px;
                width: 100%;
                float: left;
            }
    
            /*气泡框容器样式*/
            .leaflet-container {
                position: absolute;
                -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
                -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
                filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
                bottom: -12px;
            }
    
            /*气泡框关闭按钮样式*/
            .leaflet-container a.leaflet-popup-close-button {
                position: absolute;
                top: 0;
                right: 0;
                padding: 4px 4px 0 0;
                width: 18px;
                height: 14px;
                background: 0;
                color: #ffffff;
                text-align: center;
                text-decoration: none;
                font: 1pc/14px Tahoma, Verdana, sans-serif;
                font-weight: 700
            }
    
            /*气泡框内容样式*/
            .common-popup .leaflet-popup-content-wrapper {
                background: transparent;
                box-shadow: none;
                padding: 0;
                border-radius: 0;
            }
    
            /*气泡框下角*/
            .common-popup .leaflet-popup-tip {
                margin-left: 120px
            }
            .leaflet-popup-tip-container {
                position: relative;
                overflow: hidden;
                margin: 0;
                height: 28px
            }
            .leaflet-popup-tip {
                margin: -10px auto 0;
                padding: 1px;
                width: 17px;
                height: 18px;
                -webkit-transform: rotate(45deg);
                transform: rotate(45deg);
                -ms-transform: rotate(45deg)
            }
            .leaflet-popup-content-wrapper, .leaflet-popup-tip {
                background: #fff;
                box-shadow: 0 3px 14px rgba(0, 0, 0, .4)
            }
    
    
            /*自定义气泡框样式*/
            .customer-popup-southejor-style {
                border: 1px solid #fff;
                border-radius: 5px;
                background: #1a90c587;
                color: #fff;
                box-shadow: inset 0 0 4px 2px #fff;
            }
            /*自定义气泡框标题样式*/
            .customer-popup-southejor-title {
                height: 10%;
                background-color: #03a9f48f;
                padding: 2px;
                font-size: 20px;
                text-align: center;
            }
        </style>
        <!--注意:openlayers 原版的比较慢,这里引起自己服务器版-->
        <script src="http://openlayers.vip/examples/resources/ol.js"></script>
        <script src="http://openlayers.vip/examples/resources/jquery-3.5.1.min.js"></script>
        <script src="./tiandituLayers.js"></script>
        <title>OpenLayers example</title>
    </head>
    <body>
    <h2>OpenLayers Popup</h2>
    <!--地图容器,需要指定 id -->
    <div id="map" class="map"></div>
    
    <script type="text/javascript">
        var map = new ol.Map({
            // 地图容器
            target: 'map',
            // 地图图层,比如底图、矢量图等
            layers: [
                getIMG_CLayer(),
                getIBO_CLayer(),
                getCIA_CLayer(),
            ],
            // 地图视野
            view: new ol.View({
                projection: "EPSG:4326",
                // 定位
                center: [115.67724700667199, 37.73879478106912],
                // 缩放
                zoom: 6,
                maxZoom: 18,
                minZoom: 1,
            })
        });
    
        // 开启图形要素点击事件
        map.on('click', function (event) {
            this.forEachFeatureAtPixel(event.pixel, function (feature) {
                // 为点击的feature发送自定义的click消息
                feature.dispatchEvent && feature.dispatchEvent({type: 'click', event: event});
            });
        });
    
        // 初始化气泡框
        createPopup();
    
        // 默认样式
        var defaultStyle = new ol.style.Style({
            //边框样式
            stroke: new ol.style.Stroke({
                color: 'white',
                width: 2,
            }),
            //填充样式
            fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.7)',
            }),
            // 点样式
            image: new ol.style.Icon({
                // 允许跨域,如果不设置,打印地图不会打印
                crossOrigin: 'anonymous',
                // 标注图片和文字之间的距离
                anchor: [0.5, 0],
                // 图片的偏移
                offset: [0, 0],
                // 图片的锚点,一般来说,都是右下角
                anchorOrigin: 'bottom-right',
                //图标的url
                src: "http://api.tianditu.gov.cn/v4.0/image/marker-icon.png",
                scale: 1,
            })
        })
        // 初始化图层
        var layer = initVectorLayer();
        // 面和点
        var feature = undefined, featurePoint = undefined;
    
        addFeatures();
    
        // 添加点线面
        function addFeatures() {
    
            featurePoint = getFeatureByWKT("POINT(116.17983834030585 39.98298600752048)");
    
            feature = getFeatureByWKT("POLYGON((115.13540462521966 37.877850766866445,116.31094173459466 39.042401548116445,117.23379329709466 38.130536313741445,117.10195735959466 37.295575376241445,115.64077571896966 36.976971860616445,115.26724056271966 37.174725766866445,115.13540462521966 37.877850766866445))");
    
            //设置显示属性
            feature.set('content:内容', '这是一个面状图形要素');
            feature.set('other:其他属性', '还有其他面状属性');
    
            featurePoint.set('content:内容', '这是一个点图形要素');
            featurePoint.set('other:其他属性', '还有其他点属性');
    
            //定义绑定事件
            let clickFunc = function (e) {
    
                // 获取要展示的属性信息
                let popupStr = getPopupHtml(e.target);
                //获取地图对象
                let temp = '<div class="customer-popup-southejor-style">' +
                    '                <div class="customer-popup-southejor-title">\n' +
                    '                  <span>气泡框</span>\n' +
                    '                </div>\n' +
                    '                <div style="font-size: 16px; padding: 5px;">' +
                    '                   <div class="customer-popup-southejor-index" style="padding-left: 10px;">' +
                    popupStr.content +
                    '                   </div>' +
                    '                </div>\n' +
                    '              </div>'
                //打开气泡框
                openInfoWindowHtml(e.target, temp)
            }
    
            //绑定事件
            feature.on('click', clickFunc);
            featurePoint.on('click', clickFunc);
    
            layer.getSource().addFeatures([feature, featurePoint]);
        }
    
        /**
         * @todo 矢量图层
         * @returns {VectorLayer}
         * @constructor
         */
        function initVectorLayer() {
            //实例化一个矢量图层Vector作为绘制层
            let source = new ol.source.Vector();
            //创建一个图层
            let customVectorLayer = new ol.layer.Vector({
                source: source,
                zIndex: 2,
                //设置样式
                style: defaultStyle,
            });
            //将绘制层添加到地图容器中
            map.addLayer(customVectorLayer);
    
            return customVectorLayer;
        }
    
        /**
         * @todo wkt格式数据转化成图形对象
         * @param {string} wkt   "POINT(112.7197265625,39.18164062499999)" 格式数据
         * @param {string|Projection} sourceCode 源投影坐标系
         * @param {string|Projection} targetCode 目标投影坐标系
         * @returns {Feature}
         */
        function getFeatureByWKT(wkt, sourceCode, targetCode) {
            try {
                let view = map.getView();
                if (!wkt) {
                    return null;
                }
                let format = new ol.format.WKT();
    
                let feature;
    
                feature = format.readFeature(wkt, {
                    featureProjection: targetCode || view.getProjection(),
                    dataProjection: sourceCode || view.getProjection(),
                });
    
                return feature;
            } catch (e) {
                console.log(e);
                return null;
            }
        }
    
        /**
         * todo 创建 popup 气泡框元素
         * @param
         */
        function createPopup() {
    
            // 气泡框容器字符串
            var popupStr = "<div id='popup-leaflet-container-southejor' class=\"leaflet-container\" > \n" +
                "      <div class=\"leaflet-popup-pane\">\n" +
                "       <div class=\"leaflet-popup common-popup\">\n" +
                "        <a id='popup-closer' class=\"leaflet-popup-close-button\" href=\"JavaScript:void(0)\">&times;</a>\n" +
                "        <div class=\"leaflet-popup-content-wrapper\">\n" +
                "        </div>\n" +
                "        <div class=\"leaflet-popup-tip-container\">\n" +
                "         <div class=\"leaflet-popup-tip\"></div>\n" +
                "        </div>\n" +
                "       </div>\n" +
                "      </div>\n" +
                "     </div>";
    
            // 创建气泡框容器
            let popupDiv = document.createElement("div"); //创建一个div标签
    
            // 设置 id
            popupDiv.id = "mapPopup";
    
            // 初始化不显示
            popupDiv.style.display = 'none';
    
            // 初始化容器
            popupDiv.innerHTML = popupStr;
    
            //限制页面只能出现一个气泡框
            if (document.getElementsByTagName('body')[0].contains(popupDiv)) {
                document.getElementsByTagName('body')[0].removeChild(popupDiv);
            }
            document.getElementsByTagName('body')[0].appendChild(popupDiv);
    
            return initPopup(popupDiv);
        }
    
        // 气泡框,主体,关闭按钮
        var popup, contentBody, closerPopup;
    
        /**
         * todo 初始化气泡框
         * @returns {Overlay}
         */
        function initPopup(popupContainer) {
    
            //气泡框-内容ID
            contentBody = $('#popup-leaflet-container-southejor .leaflet-popup-content-wrapper')[0];
    
            //气泡框-关闭ID
            closerPopup = document.getElementById('popup-closer');
    
            // 设置为显示
            popupContainer.style.display = 'block';
    
            //气泡框覆盖层
            popup = new ol.Overlay({
                // 容器
                element: popupContainer,
                // 当Popup超出地图边界时,为了Popup全部可见,自动移动到可视范围
                autoPan: true,
                autoPanAnimation: {
                    //当Popup超出地图边界时,为了Popup全部可见,地图移动的速度.
                    duration: 250
                }
            });
    
            return popup;
        }
    
        function openInfoWindowHtml(featureTemp, content) {
    
            try {
    
                // 如果没有图形要素参数,则打开默认气泡框
                if (!featureTemp) {
                    contentBody.innerHTML.trim() ? map.addOverlay(popup) : alert('请先点击图形要素(面或点)!');
                    return;
                }
    
                // 更新显示属性
                contentBody.innerHTML = content;
    
                // 获取图形要素中心点
                var center = ol.extent.getCenter(featureTemp.getGeometry().getExtent());
    
                // 气泡框设置中心点
                popup.setPosition(center);
    
                // 移除上一个气泡框
                map.removeOverlay(popup);
    
                // 获取图形要素类型
                let featureType = featureTemp.getGeometry().getType();
    
                //这 里面和线图形与点图形不一样;如果是点图层(图标),则气泡框上移
                // 气泡框的 setOffset 可以根据实际调整
                if (featureType == 'Point') {
                    let styleTemp = featureTemp.getStyle() || layer.getStyle();
                    if (styleTemp && styleTemp.getImage()) {
                        let size = styleTemp.getImage().getSize();
                        let scale = styleTemp.getImage().getScale();
                        if (size && size.length > 1) {
                            popup.setOffset([-10, -size[1] * scale]);
                        } else {
                            popup.setOffset([0, -40]);
                        }
                    } else {
                        popup.setOffset([0, -5]);
                    }
                    //其他图形则正常
                } else {
                    popup.setOffset([-10, 0]);
                }
    
                // 添加气泡框
                map.addOverlay(popup);
    
                // 绑定关闭按钮事件
                closerPopup.onclick = function () {
                    map.removeOverlay(popup);
                };
    
                // 气泡框宽度
                let popupWidth = 300;
    
                // 调整位置
                let popupPointLeft = (popupWidth / 2);
    
                // 调整横向偏移
                $(map.getTargetElement()).find(".leaflet-container").width(popupWidth + "px");
                $(map.getTargetElement()).find(".leaflet-container").css('right', -popupPointLeft + "px");
                $(map.getTargetElement()).find(".leaflet-popup-tip-container .leaflet-popup-tip").css("margin-left", popupPointLeft + "px");
    
                return popup;
            } catch (e) {
                console.log(e)
            }
        }
    
        // 获取气泡框属性
        function getPopupHtml(feature) {
    
            if (!feature) {
                return;
            }
    
            let popup = {};
    
            // 定义 table 容器
            let content = "<table class='popup-table-html-show-southejor' style='width: 100%;color:white'>";
    
            // 获取图形要素所有属性
            let featureProperties = feature.getProperties();
    
            // 设置字段和属性宽度
            let tdColumn = '35%', tdField = '65%';
    
            for (let featureElement in featureProperties) {
    
                // 这里需要跟前边 feature 属性定义对应;
                // 前边定义为:字段:值
                if (featureElement && featureElement.indexOf(":") != -1) {
    
                    let featureElementArr = featureElement.split(":");
    
                    content += "<tr style='height: 25px;'>";
    
                    // 拼接属性信息
                    content += "<td style='border-width: 0px 0px 1px 0px;word-wrap:break-word;" +
                        "word-break:break-all;width: " + tdColumn + "'><div>" + featureElementArr[1] + " : </div></td>" +
                        "<td style='border-width: 0px 0px 1px 0px;word-wrap:break-word;word-break:break-all;" +
                        "width: " + tdField + "'><div class='popupIdentity-" + featureElementArr[0] + "'>" +
                        featureProperties[featureElement] + "</div></td>";
    
                    content += "</tr>";
                }
            }
    
            popup.content = content += "</table>";
    
            return popup;
        }
    
        /**
         *  todo 定位到气泡框
         */
        function moveToPopup() {
    
            // 用于定位的[x,y]
            let xy;
    
            // 图形要素的类型;面线和点获取 xy 方式不同
            let type = feature.getGeometry().getType();
    
            // 获取面线和点的 xy
            if ('Polygon' == type || 'LineString' == type) {
                xy = ol.extent.getCenter(feature.getGeometry().getExtent());
            } else {
                xy = feature.getGeometry().getCoordinates();
            }
    
            // 定位位置(y 轴)
            let init = 500;
            // 获取气泡框高度
            let heightTemp = init + $('#popup-leaflet-container-southejor').height() - 40;
            heightTemp = heightTemp || 680;
    
            // 计算 y 轴间隔
            let gap = map.getCoordinateFromPixel([300, heightTemp])[1] - map.getCoordinateFromPixel([300, init])[1];
    
            xy && xy.length > 1 && (xy[1] = xy[1] - gap);
    
            // 定位;位置,定位时长
            let flyTo = function (location, time) {
    
                let duration = time ? time : 2000;
                let zoom = map.getView().getZoom();
                let parts = 2;
                let called = false;
    
                // 定位回调
                function callback(complete) {
                    --parts;
                    if (called) {
                        return;
                    }
                    if (parts === 0 || !complete) {
                        called = true;
                    }
                }
    
                // 定位动作
                map.getView().animate({
                    center: location,
                    duration: duration
                }, callback);
    
                // 定位动作
                map.getView().animate({
                    zoom: zoom - 1,
                    duration: duration / 2
                }, {
                    zoom: zoom,
                    duration: duration / 2
                }, callback);
            }
    
            flyTo(xy, 1000);
        }
    
        /**
         *  todo 关闭气泡框
         * @param _map
         */
        function closeInfoWindowHtml() {
            map.removeOverlay(popup);
        }
    </script>
    <button id="openInfoWindowHtml" onClick="openInfoWindowHtml()">打开气泡框</button>
    <button id="moveToPopup" onClick="moveToPopup()">定位到气泡框</button>
    <button id="closeInfoWindowHtml" onClick="closeInfoWindowHtml()">关闭气泡框</button>
    </body>
    </html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525

    在线示例

    Openlayers 自定义气泡框:Openlayers feature popup

  • 相关阅读:
    uni-app、小程序项目对pages.json文件拆分、动态生成pages.json文件、动态生成分包配置pages.json文件的解决方案
    Python-数据爬取(爬虫)
    .net程序员的android studio 初体验 (环境设置2022年10月)
    鸿蒙HarmonyOS应用开发:多码识别
    VS Code 配置C语言
    Java可变参数和不可变集合
    隔离式DC/DC高压模块5V12V24V转50V110V250V300V380V600V1100V短路保护直流升压可调开关控制电源模块
    MTK平台Metadata的加载(3)——其他Static和Request加载
    S32 Design Studio for ARM 2.2 快速入门
    CentOS8.2安装Nginx1.23.2
  • 原文地址:https://blog.csdn.net/linzi19900517/article/details/123570938