OpenLayers 地图标注之Popup标注

关注我,带你一起学GIS ^

注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key

地图标注是将空间位置信息点与地图关联、通过图标、窗口等形式把相关信息展现到地图上。在 我WebGIS中地图标注是重要的功能之一,可以为用户提供个性化服务,如兴趣点等。地图标注表现方式有图文标注、Popup标注、聚合标注等。本节主要介绍加载Popup标注

1. 地图标注基本原理

地图标注通过在已知坐标点添加图片、文字或者图文的方式展现内容信息。可以通过鼠标点击获取目标点位置坐标,也可以通过属性传递获取。

2. 创建图标标注以及Popup加载图片

在图标网站如[https://www.iconfont.cn/](https://www.iconfont.cn/)下载定位图片,搜索框输入locate寻找适合的图片。创建图标样式如下,调整图标显示位置anchor,图标大小size以及图片路径src

// 添加图片标注
const imageStyle = new ol.style.Style({
    image: new ol.style.Icon({
        anchor: [32, 10], // 调整图标位置,正值向左向下,负值相反
        anchorOrigin: "bottom-left", // 图标位置起始位置,默认'top-left'
        anchorXUnits: 'pixels',
        anchorYUnits: 'pixels',
        offsetOrigin: 'top-left',   // 图标位置偏移起始位置,默认'top-left'
        opacity: 0.75,
        size: [64, 64], // 图片大小
        src: './locate.png' // 图片路径
    }),
    text: new ol.style.Text({
        font: "normal 14px"
    })
})

在地图上添加标注点。

// 添加标注点
const pointLayer = new ol.layer.Vector({
    source: new ol.source.Vector({
        features: [
            new ol.Feature({
                geometry: new ol.geom.Point(chengdu),
                name: "chengdu",
                properties: {
                    province: "四川省", region: "成都市", population: "2140.3万人"
                }
            }),
            new ol.Feature({
                geometry: new ol.geom.Point(xjq),
                name: "xjq",
                properties: {
                    province: "四川省", region: "新津区", population: "36.36万人"
                }
            })
        ]
    }),
    style: imageStyle
})
pointLayer.setProperties({ "layerName""pointLayer" })
map.addLayer(pointLayer)

3. 创建Popup内容

创建函数createPopupEle,通过传入HTML元素或者描述文字生成Popup HTML

/**
 * 创建Popup 内容
 * @mainContent:HTML 或者 文字
 */
function createPopupEle(mainContent) {
    const htmlEle = document.createElement("div")
    const closeEle = document.createElement('span')
    const mainEle = document.createElement('div')

    mainEle.className = 'ol-popup-main'
    console.log(typeof mainContent)
    const contentType = typeof mainContent
    if (contentType === 'object') {
        mainEle.appendChild(mainContent)
    } else if (contentType === 'string') {
        mainEle.innerHTML = mainContent
    }

    closeEle.textContent = "×"
    closeEle.className = "ol-popup-close"
    htmlEle.className = "ol-popup"

    htmlEle.appendChild(mainEle)
    htmlEle.appendChild(closeEle)

    // 注册关闭popup事件
    closeEle.addEventListener('click', evt => {
        removeOverlayByName("overlay")
        // removeOverlayByLayer(marker)
    })

    return htmlEle
}

4. 创建叠加图层

OpenLayers中,Popup弹窗的实现通过创建叠加图层实现。在函数createOvrlay中传入Popup HTML元素以及定位Position,并将其添加到地图中。

/**
 * 创建Overlay
 * @popupEle:popup html 结构
 * @position:popup 定位坐标,形式为 [longitude,latitude]
 */
function createOvrlay(popupEle, position) {
    const marker = new ol.Overlay({
        id: "maker",
        position: position,
        element: popupEle,
        offset: [0, -30], // x、y轴偏移量,正值向右向下,负值相反
        positioning: 'bottom-center', // 定位方式,顶部居中
        aotuPan: true,
        autoPanMargin: 1.25,
    })
    marker.setProperties({ layerName: 'overlay' })
    return marker
}

5. 打开和关闭Popup

打开和关闭Popup相对简单,只需要调用map对象的addOverlay方法进行加载,removeOverlay方法进行移除。其中移除Overlay有两种操作方式,一种是根据图层移除,另一种是根据图层名称进行移除。根据图层名称进行移除时需要遍历图层,通过图层名属性进行判断移除的目标图层。

/**
 * 打开Popup
 */
function openPopup(overlay) {
    map.addOverlay(overlay)
}
/**
 * 根据图层名关闭Popup
 */
function removeOverlayByName(layerName) {
    const overlays = map.getOverlays().getArray()
    // console.log("overlays:", overlays)
    overlays.forEach(layer => {
        console.log("layer.layerName:", layer.get('layerName'))
        if (layer.get('layerName') === layerName) map.removeOverlay(layer)
    });
}
/**
 * 根据图层关闭Popup
 */
function removeOverlayByLayer(overlay) {
    map.removeOverlay(overlay)
}

6. 图层点击打开Popup

当在地图上点击到目标要素时,通过获取点击要素的坐标和属性信息,从而打开对应的Popup。在打开目标Popup时,需要先将已经打开的Popup关闭。

/**
 * 根据点击要素打开Popup
 */
function openPopupByClickFeature() {
    const popupColumns = [
        { name: 'province', comment: "省" },
        { name: 'region', comment: "行政区" },
        { name: 'population', comment: "人口" }
    ]
    map.on('click', evt => {
        const feature = map.forEachFeatureAtPixel(evt.pixel, (feature, layer) => feature)
        if (feature) {
            // removeOverlayByLayer(overlay)
            removeOverlayByName("overLay")
            const property = feature.get("properties")
            const geometry = feature.getGeometry()
            const position = geometry.getCoordinates()
            openPopupTable(property, popupColumns, position)
        }
    })
}

7. 改变鼠标形状

在地图上监听'pointermove'(鼠标移动)事件,当鼠标移到目标元素时,获取当前屏幕坐标,改变指针形状为"poiter"

// 当鼠标移过元素时,改变鼠标形状
function changeCursor() {
    map.on('pointermove', evt => {
        const pixel = map.getEventPixel(evt.originalEvent)
        const hit = map.hasFeatureAtPixel(pixel)
        map.getTargetElement().style.cursor = hit ? 'poiter' : ''
    })
}

8. 封装PopupTable结构

自定义Popup HTML结构,在表格中展示属性数据。

/**
 * 封装Popup表格内容,显示表格数据
 * @properties:要素属性信息,如{province:"四川省",region:"成都市",population:2140.3万人}
 * @popupColumns:显示字段对应中文名称:如[{name:province,commnet:"省"},{name:region,comment:行政区},{name:population,comment:"人口"}]
 * @position: popup 定位坐标,形式为 [longitude,latitude]
 */
function openPopupTable(property, popupColumns, position) {
    const tableEle = document.createElement('table')
    const tbodyEle = document.createElement('tbody')
    tableEle.appendChild(tbodyEle)
    tableEle.className = "popup-table"
    tableEle.setAttribute('border''1')
    tableEle.setAttribute('cellpadding''0')
    tableEle.setAttribute('cellspacing''0')
    Object.values(popupColumns).forEach((prop, index) => {
        // 过滤无效属性字段
        if (prop["name"] === 'id' || prop["name"] === 'oid'return
        const trEle = document.createElement('tr')
        trEle.className = 'table-tr'
        const firstTdEle = document.createElement('td')
        const secondTdEle = document.createElement('td')
        firstTdEle.innerText = popupColumns[index].comment
        secondTdEle.innerText = property[popupColumns[index].name] || '暂无'

        trEle.appendChild(firstTdEle)
        trEle.appendChild(secondTdEle)
        tbodyEle.appendChild(trEle)
    })
    // 创建Overlay popup
    const overlay = new ol.Overlay({
        id: "temp-",
        position: position,
        element: tableEle,
        offset: [0, 20], // x、y轴偏移量,正值向右向下,负值相反
        autoPan: false,
        autoPanMargin: 1.25,
        positioning: 'top-center' // 定位方式,顶部居中
    })
    overlay.setProperties({ layerName: "overLay" })
    map.addOverlay(overlay)
}

Popup样式

/**
 * openLayers 样式
 */

.ol-overlay-container p {
    margin: 5px 0 !important;
}

.ol-overlay-container {
    padding: 2px;
    max-height: 300px;
    color: #fff;
    border-radius: 2px;
}

.popup-analyse-btn {
    width: 100%;
    background-color: rgb(219, 197, 137);
    padding: 10px;
    border-radius: 2.5px;
    margin-top: 10px;
    display: inline-block;
    color: #fffefe;
    border-color: #fff;
    text-align: center;
}

.popup-analyse-btn:hover {
    cursor: pointer;
    color: #fff;
    filter: brightness(110%) opacity(100%);
    transition: all .5s ease-in;
    background: linear-gradient(to bottom right, #9a99f1, #0167cc);
}

.popup-analyse-btn:focus {
    filter: brightness(120%);
    transition: all .5s ease-in;
    background: radial-gradient(circle at center, #9a99f1, #0167cc);
}

.popup-table {
    background: #979797ba;
    border: 1px solid #d9d9d9ad;
    border-collapse: collapse;
}

.table-tr {
    width: 100%;
}

.table-tr:hover {
    cursor: pointer;
    background-color: #0c698d61;
}

.table-tr td {
    padding: 10px 5px;
    line-height: 1.5;
}

.table-tr td:first-child {
    text-align: right;
    width: 45%;
}

.table-tr td:last-child {
    text-align: left;
    width: 55%;
}

9. 完整代码

其中libs文件夹下的包需要更换为自己下载的本地包或者引用在线资源。

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Popup标注</title>
    <meta charset="utf-8" />
    <script src="../libs/js/ol-5.3.3.js"></script>
    <script src="../libs/js/jquery-2.1.1.min.js"></script>
    <link rel="stylesheet" href="../libs/css/ol.css">
    <link rel="stylesheet" href="./popup.css">
    <style>
        * {
            padding: 0;
            margin: 0;
            font-size: 14px;
            font-family: '微软雅黑';
        }

        html,
        body {
            width: 100%;
            height: 100%;
        }

        #map {
            position: absolute;
            width: 100%;
            height: 100%;
        }

        .ol-mouse-position {
            padding: 5px;
            top: 10px;
            height: 40px;
            line-height: 40px;
            background: #060505ba;
            text-align: center;
            color: #fff;
            border-radius: 5px;
        }

        .ol-popup {
            position: relative;
            font-size: 16px;
            color: #4c4c4c;
            background-color: #ddd;
            border-radius: 5px;
        }

        .ol-popup::before {
            display: block;
            content: "";
            width: 0;
            height: 0;
            border-left: 15px solid transparent;
            border-right: 15px solid transparent;
            border-top: 10px solid #ddd;
            position: absolute;
            bottom: -8px;
            left: 50%;
            transform: translateX(-50%);
        }

        .ol-popup-main {
            padding: 20px;
        }

        .ol-popup-close {
            position: absolute;
            display: inline-block;
            top: -6px;
            right: 5px;
            color: #878282b5;
            font-size: 20px;
        }

        .ol-popup-text {
            font-size: 14px;
            font-weight: bold;
            color: #434343;
        }

        .ol-popup-close:hover {
            cursor: pointer;
            color: #0e0e0eb5;
            filter: brightness(120%);
        }
    </style>
</head>

<body>
    <div id="map" title="地图显示"></div>
</body>

</html>

<script>
    //地图投影坐标系
    const projection = ol.proj.get('EPSG:3857');
    //==============================================================================//
    //============================天地图服务参数简单介绍==============================//
    //================================vec:矢量图层==================================//
    //================================img:影像图层==================================//
    //================================cva:注记图层==================================//
    //======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
    //==============================================================================//
    const TDTImgLayer = new ol.layer.Tile({
        title: "天地图影像图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const TDTImgCvaLayer = new ol.layer.Tile({
        title: "天地图影像注记图层",
        source: new ol.source.XYZ({
            url: "http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=2a890fe711a79cafebca446a5447cfb2",
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const map = new ol.Map({
        target: "map",
        loadTilesWhileInteracting: true,
        view: new ol.View({
            // center: [11421771, 4288300],
            // center: [102.6914059817791, 25.10595662891865],
            center: [104.0635986160487, 30.660919181071225],
            zoom: 10,
            worldsWrap: true,
            minZoom: 1,
            maxZoom: 20,
            projection: "EPSG:4326"
        }),
        // 鼠标控件:鼠标在地图上移动时显示坐标信息。
        controls: ol.control.defaults().extend([
            // 加载鼠标控件
            new ol.control.MousePosition()
        ])
    })

    map.addLayer(TDTImgLayer)
    map.addLayer(TDTImgCvaLayer)

    map.on('click', evt => {
        console.log(evt.coordinate)
    })
    // 成都市
    const chengdu = [104.0635986160487, 30.660919181071225]

    // 新津区
    const xjq = [103.80322263948621, 30.415924063883722]

    // 添加图片标注
    const imageStyle = new ol.style.Style({
        image: new ol.style.Icon({
            anchor: [32, 10], // 调整图标位置,正值向左向下,负值相反
            anchorOrigin: "bottom-left", // 图标位置起始位置,默认'top-left'
            anchorXUnits: 'pixels',
            anchorYUnits: 'pixels',
            offsetOrigin: 'top-left',   // 图标位置偏移起始位置,默认'top-left'
            opacity: 0.75,
            size: [64, 64], // 图片大小
            src: './locate.png'
        }),
        text: new ol.style.Text({
            font: "normal 14px"
        })
    })

    // 添加标注点
    const pointLayer = new ol.layer.Vector({
        source: new ol.source.Vector({
            features: [
                new ol.Feature({
                    geometry: new ol.geom.Point(chengdu),
                    name: "chengdu",
                    properties: {
                        province: "四川省", region: "成都市", population: "2140.3万人"
                    }
                }),
                new ol.Feature({
                    geometry: new ol.geom.Point(xjq),
                    name: "xjq",
                    properties: {
                        province: "四川省", region: "新津区", population: "36.36万人"
                    }
                })
            ]
        }),
        style: imageStyle
    })
    pointLayer.setProperties({ "layerName""pointLayer" })
    map.addLayer(pointLayer)

    // 创建文字内容标注
    // const text = "这是四川省"
    // const popupEle = createPopupEle(text)
    // openPopup(popupEle, chengdu)

    // 创建图片内容标注
    const mainContent = document.createElement('div')
    const p = document.createElement('p')
    p.className = 'ol-popup-text'
    p.textContent = "成都·历史文化名城市"
    const imgEle = document.createElement('img')
    imgEle.src = './chengdu.jpg'
    imgEle.style.width = '120px'
    imgEle.style.height = '50px'
    mainContent.appendChild(p)
    mainContent.appendChild(imgEle)

    const popupEle = createPopupEle(mainContent)
    const overlay = createOvrlay(popupEle, chengdu)

    openPopup(overlay)

    /**
     * 打开Popup
     */
    function openPopup(overlay) {
        map.addOverlay(overlay)
    }

    /**
     * 创建Overlay
     * @popupEle:popup html 结构
     * @position:popup 定位坐标,形式为 [longitude,latitude]
     */
    function createOvrlay(popupEle, position) {
        const marker = new ol.Overlay({
            id: "maker",
            position: position,
            element: popupEle,
            offset: [0, -30], // x、y轴偏移量,正值向右向下,负值相反
            positioning: 'bottom-center', // 定位方式,顶部居中
            aotuPan: true,
            autoPanMargin: 1.25,
        })
        marker.setProperties({ layerName: 'overlay' })
        return marker
    }

    /**
     * 创建Popup 内容
     * @mainContent:HTML 或者 文字
     */
    function createPopupEle(mainContent) {
        const htmlEle = document.createElement("div")
        const closeEle = document.createElement('span')
        const mainEle = document.createElement('div')

        mainEle.className = 'ol-popup-main'
        console.log(typeof mainContent)
        const contentType = typeof mainContent
        if (contentType === 'object') {
            mainEle.appendChild(mainContent)
        } else if (contentType === 'string') {
            mainEle.innerHTML = mainContent
        }

        closeEle.textContent = "×"
        closeEle.className = "ol-popup-close"
        htmlEle.className = "ol-popup"

        htmlEle.appendChild(mainEle)
        htmlEle.appendChild(closeEle)

        // 注册关闭popup事件
        closeEle.addEventListener('click', evt => {
            removeOverlayByName("overlay")
            // removeOverlayByLayer(marker)
        })

        return htmlEle
    }

    function removeOverlayByName(layerName) {
        const overlays = map.getOverlays().getArray()
        // console.log("overlays:", overlays)
        overlays.forEach(layer => {
            console.log("layer.layerName:", layer.get('layerName'))
            if (layer.get('layerName') === layerName) map.removeOverlay(layer)
        });
    }
    function removeOverlayByLayer(overlay) {
        map.removeOverlay(overlay)
    }

    openPopupByClickFeature()

    /**
     * 根据点击要素打开Popup
     */
    function openPopupByClickFeature() {
        const popupColumns = [
            { name: 'province', comment: "省" },
            { name: 'region', comment: "行政区" },
            { name: 'population', comment: "人口" }
        ]
        map.on('click', evt => {
            const feature = map.forEachFeatureAtPixel(evt.pixel, (feature, layer) => feature)
            if (feature) {
                // removeOverlayByLayer(overlay)
                removeOverlayByName("overLay")
                const property = feature.get("properties")
                const geometry = feature.getGeometry()
                const position = geometry.getCoordinates()
                openPopupTable(property, popupColumns, position)
            }
        })
    }

    changeCursor()
    // 当鼠标移过元素时,改变鼠标形状
    function changeCursor() {
        map.on('pointermove', evt => {
            const pixel = map.getEventPixel(evt.originalEvent)
            const hit = map.hasFeatureAtPixel(pixel)
            map.getTargetElement().style.cursor = hit ? 'poiter' : ''
        })
    }

    /**
     * 封装Popup表格内容,显示表格数据
     * @properties:要素属性信息,如{province:"四川省",region:"成都市",population:2140.3万人}
     * @popupColumns:显示字段对应中文名称:如[{name:province,commnet:"省"},{name:region,comment:行政区},{name:population,comment:"人口"}]
     * @position: popup 定位坐标,形式为 [longitude,latitude]
     */
    function openPopupTable(property, popupColumns, position) {
        const tableEle = document.createElement('table')
        const tbodyEle = document.createElement('tbody')
        tableEle.appendChild(tbodyEle)
        tableEle.className = "popup-table"
        tableEle.setAttribute('border''1')
        tableEle.setAttribute('cellpadding''0')
        tableEle.setAttribute('cellspacing''0')
        Object.values(popupColumns).forEach((prop, index) => {
            // 过滤无效属性字段
            if (prop["name"] === 'id' || prop["name"] === 'oid'return
            const trEle = document.createElement('tr')
            trEle.className = 'table-tr'
            const firstTdEle = document.createElement('td')
            const secondTdEle = document.createElement('td')
            firstTdEle.innerText = popupColumns[index].comment
            secondTdEle.innerText = property[popupColumns[index].name] || '暂无'

            trEle.appendChild(firstTdEle)
            trEle.appendChild(secondTdEle)
            tbodyEle.appendChild(trEle)
        })
        // 创建Overlay popup
        const overlay = new ol.Overlay({
            id: "temp-",
            position: position,
            element: tableEle,
            offset: [0, 20], // x、y轴偏移量,正值向右向下,负值相反
            autoPan: false,
            autoPanMargin: 1.25,
            positioning: 'top-center' // 定位方式,顶部居中
        })
        overlay.setProperties({ layerName: "overLay" })
        map.addOverlay(overlay)
    }
</script>

OpenLayers示例数据下载,请在公众号后台回复:ol数据

全国信息化工程师-GIS 应用水平考试资料,请在公众号后台回复:GIS考试

GIS之路公众号已经接入了智能助手,欢迎大家前来提问。

欢迎访问我的博客网站-长谈GIShttp://shanhaitalk.com

都看到这了,不要忘记点赞、收藏+关注 

本号不定时更新有关 GIS开发  相关内容,欢迎关注 

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部