OpenLayers 样式之压覆模式

关注我,带你一起学GIS ^

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

前言

WebGIS系统开发中,图文标注是一个常见的功能,怎么才能够实现高效美观的标注是一个值得思考的问题。而OpenLayers给我们提供了declutterMode,可以让我们方便的解决压覆问题。

OpenLayers中想要开启declutterMode模式,需要将图层属性declutter值设置为true。为了演示方便,本文在之前文章【OpenLayers 调整标注样式】的基础上进行修改。

1. 压覆模式

DeclutterMode类型具有三个值,当将图层属性declutter设置为true时,它可以实现在图层上稀释符号和文本样式(标注样式)。DeclutterMode可以设置为三个值,分别是"declutter"、”obstacle“和”none“。

  • declutter:覆盖的的符号何文本会被稀释(清理)。
  • obstacle:符号和文本会被渲染,但会成为后续尝试将符号或文本放置在同一位置的障碍。
  • none:无操作。

当改变declutter属性值时,可以实现标注样式在不同比例尺下的显示效果。declutter值为true时,可以看到在小比例尺地图下,之前压覆的标注内容被去除了,具有更好的视觉标注效果。要查看如何标注要素请阅读【OpenLayers 要素标注

2. 未开启压覆模式

在未开启压覆模式之前,示例中缩放级别由6调整到4的时候,可以看到其中的文本标注内容已经完全挤到了一块儿,标注效果并不理想,达不到标注可视化的要求。所以在进行图层标注的时候,开启压覆模式可以调整标注内容之间的显示优先级,从而提高标注可视化水平。

3. 开启压覆模式

在矢量图层中将declutter属性值为设置为true当开启了压覆模式后,在同样的缩放比例下,虽然显示的内容变少了,但是给人的感受更直观,从整体上提升了标注的视觉效果。

4. 压覆样式image

在开启了压覆模式之后,可以在样式属性image中设置压覆模式。此时如果不设置declutterMode属性值或者将属性值设置为"declutter"的话,随着缩放级别变化的时候,地图标注的几何图形和文本内容会发生变化,根据标注优先级进行显示。如果将declutterMode属性值设置为"none",则几何图形将会一直显示,而文字标注内容将会随着缩放级别变化。如果将declutterMode属性值设置为"obstacle",几何图形也会一直显示,并且文字标注内容也会随着缩放级别变化。此时的文字标注内容发生变化的方式与declutterMode属性值为"none"时似乎有一点儿不同,但是目前我还没有得出一个更好的归纳。

image: new ol.style.Circle({
    declutterMode: "obstacle"
})

5. 压覆样式Text

在开启了压覆模式之后,可以在样式属性text中设置压覆模式。此时如果不设置declutterMode属性值或者将属性值设置为"declutter"的话,随着缩放级别变化的时候,地图标注的几何图形和文本内容会发生变化,根据标注优先级进行显示。如果将declutterMode属性值设置为"none",则几何图形和文字标注内容都会一直显示,此时的显示效果和未开启压覆模式相同,标注内容并不会随着缩放级别发生变化。如果将declutterMode属性值设置为"obstacle",文字标注内容也会一直显示,但是文字标注内容不会随着缩放级别变化,而此时的几何却会随着缩放级别变化。对于这个现象,目前也是未能得出一个更好的归纳,猜测和现实层级有关吧。

text: new ol.style.Text({
    declutterMode: "obstacle"
})

6. 完整代码

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

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>OpenLayers 样式之压覆模式</title>
    <meta charset="utf-8" />

    <link rel="stylesheet" href="../../libs/css/ol9.2.4.css">
    <link rel="stylesheet" href="../../libs/layui/css/layui.css">

    <script src="../../js/config.js"></script>
    <script src="../../libs/js/ol9.2.4.js"></script>
    <script src="../../libs/layui/layui.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
            font-size: 14px;
            font-family: '微软雅黑';
        }

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

        #map {
            position: absolute;
            top: 50px;
            bottom: 0;
            width: 100%;
        }

        #top-content {
            position: absolute;
            width: 100%;
            height: 50px;
            line-height: 50px;
            background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
            color: #fff;
            text-align: center;
            font-size: 32px;
        }

        #top-content span {
            font-size: 32px;
        }

        .state {
            position: absolute;
            bottom: 10px;
            line-height: 30px;
            background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
            color: #fff;
            display: flex;
            justify-content: space-between;
            justify-items: center;
            height: 30px;
            left: 10%;
            right: 10%;
            border-radius: 5px;
        }

        .state-item {
            width: 100%;
            text-align: center;
            font-size: 16px;
            font-weight: bold;
        }

        #custome-scale-line {
            position: absolute;
            margin: 0 auto;
            bottom: 13px;
            right: 120px;
            width: 200px;
            text-align: center;
            color: #fff;
            border-radius: 5px;
        }

        .custome-scale-line-inner {
            margin: 1px;
            color: #eee;
            font-size: 14px;
            text-align: center;
            will-change: contents, width;
        }
    </style>
</head>

<body>
    <div id="top-content">
        <span>OpenLayers 样式之压覆模式</span>
    </div>
    <div id="map" title=""></div>
    <div class="state">
        <div class="state-item zoom-state">
            <label for="">缩放级别:</label>
            <span class="zoomValue"></span>
        </div>
        <div class="state-item lnglat-state">
            <label for="">经度:</label>
            <span class="lngValue"></span>
            <label for="">纬度:</label>
            <span class="latValue"></span>
        </div>
        <div class="state-item scale-state">
            <label for="">比例尺:</label>
            <span class="scaleValue"></span>
        </div>
    </div>
    <div class="custome-scale-line" id="custome-scale-line"></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=" + TDTTOKEN,
            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=" + TDTTOKEN,
            attibutions: "天地图注记描述",
            crossOrigin: "anoymous",
            wrapX: false
        })
    })
    const map = new ol.Map({
        target: "map",
        loadTilesWhileInteracting: true,
        view: new ol.View({
            center: [102.845864, 25.421639],
            zoom: 6.5,
            worldsWrap: false,
            minZoom: 1,
            maxZoom: 20,
            projection: 'EPSG:4326',
        }),
        layers: [TDTImgLayer],
        // 地图默认控件
        controls: ol.control.defaults.defaults({
            zoom: false,
            attribution: true,
            rotate: true
        })
    })
    map.on('click', evt => {
        console.log("获取地图坐标:", evt.coordinate)
    })

    const features = [
        // 第一组
        {
            geometry: new ol.geom.Point([94.11434654171987, 28.972402471968656]),
            textAlign: "left" // 标注文本框位于point右边,point右边是标注文本框
        }, {
            geometry: new ol.geom.Point([103.45228063165628, 28.972402471968656]),
            textAlign: "center" // 标注文本框位于point中心
        },
        {
            geometry: new ol.geom.Point([112.03997466732001, 28.972402471968656]),
            textAlign: "right" // 标注文本框位于point左边,point左边是标注文本框
        },

        // 第二组
        {
            geometry: new ol.geom.Point([94.11434654171987, 26.972402471968656]),
            textAlign: "left", // 文本对齐
            justify: "center" // 文本框内的文本对齐
        }, {
            geometry: new ol.geom.Point([103.45228063165628, 26.972402471968656]),
            textAlign: "center",
            justify: "center"
        },
        {
            geometry: new ol.geom.Point([112.03997466732001, 26.972402471968656]),
            textAlign: "right",
            justify: "center"
        },
        // 第三组
        {
            geometry: new ol.geom.Point([94.11434654171987, 24.972402471968656]),
            textAlign: "left",
            justify: "left"
        }, {
            geometry: new ol.geom.Point([103.45228063165628, 24.972402471968656]),
            textAlign: "center",
            justify: "left"
        },
        {
            geometry: new ol.geom.Point([112.03997466732001, 24.972402471968656]),
            textAlign: "right",
            justify: "left"
        },
    ]

    function createStyle({ textAlign, justify }) {
        return new ol.style.Style({
            image: new ol.style.Circle({
                radius: 10, // 单位像素
                fill: new ol.style.Fill({
                    color: "rgba(255, 0, 0, 0.8)"
                }),
                stroke: new ol.style.Stroke({
                    color: "red",
                    width: 1.5 // 单位像素
                }),
                // declutterMode: "declutter"
                // declutterMode: "obstacle"
                // declutterMode: "none"
            }),
            text: new ol.style.Text({
                font: '16px sans-serif',
                textAlign,
                justify,
                text: `Justify text inside boxntextAlign: ${textAlign}` +
                    (justify ? `\njustify: ${justify}` : ''),
                fill: new ol.style.Fill({
                    color: [148, 0, 211, 1],
                }),
                backgroundFill: new ol.style.Fill({
                    color: [128, 0, 128, 0.25]
                }),
                // 设置边框边线色
                backgroundStroke: new ol.style.Stroke({
                    color: [255, 182, 193, 0.5],
                    width: 2.5,
                    lineJoin: 'miter',
                    lineCap: "round"
                }),
                padding: [10, 10, 10, 10], // 文本框内间距
                // offsetX: 100
                // declutterMode: "obstacle"
                // declutterMode: "declutter"
                // declutterMode: "none"
            })
        })
    }

    const vectorPoints = new ol.layer.Vector({
        source: new ol.source.Vector({
            features: new ol.Collection(
                features.map(featureOptions => {
                    const feature = new ol.Feature({
                        geometry: featureOptions.geometry
                    })
                    feature.setStyle(createStyle(featureOptions))
                    return feature
                })
            ),
            format: new ol.format.GeoJSON()
        }),
        // 开启图层标注稀释模式
        declutter: true
    })
    map.addLayer(vectorPoints)

    const scaleControl = new ol.control.ScaleLine({
        units: 'metric', // 'degrees-度''imperial-英制单位''nautical-海里''metric-米''us',默认值'metric'
        className: 'custome-scale-line',// 自定义css类样式名
        // minWidth: 200,// 最小宽度
        target: document.querySelector(".custome-scale-line"),// 放置比例尺控件的目标容器
    })

    map.addControl(scaleControl)

    const lngEle = document.querySelector(".lngValue")
    const latEle = document.querySelector(".latValue")
    map.on('pointermove', evt => {
        const coords = evt.coordinate
        lngEle.textContent = coords[0].toFixed(6)
        latEle.textContent = coords[1].toFixed(6)
    })

    const zoomEle = document.querySelector(".zoomValue")
    map.on('postrender', evt => {
        const view = map.getView()
        zoomEle.textContent = parseInt(view.getZoom())
    })
</script>

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

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

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

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

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

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

发表评论

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

滚动至顶部