Openlayers 聚合图、权重聚合图以及聚合图点击事件

发布于:2022-08-09 ⋅ 阅读:(350) ⋅ 点赞:(0)

Openlayers 聚合图、权重聚合图以及聚合图点击事件

OpenLayers 教程

在实际工作中,Openlayers 渲染数据的方式有很多种(WMS、瓦片、矢量数据等),一次性渲染较大数据量的情况下,需要做成静态切片,比如WMTS、TMS;或者矢量切片,比如 Geojson、mvt 等。

对于数据量不是很大的点状数据,常常使用 热力图、聚合图 的方式在前端渲染,能够更好的体现数据特征。

本示例基于实际项目中的应用,介绍: 加载聚合图、权重聚合图、聚合图参数、聚合图点击事件 等功能的用法。

PS:如果数据量很大的话,建议数据入库,使用数据库的聚合函数来实现。

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;
        }
    </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 Cluster</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,
        })
    });

    /**
     * @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 颜色十六进制转为 rgba
     * @param sColor 格式数据
     * @param opacity
     * @returns rgba颜色字符串
     */
    function colorToRgb(sColor, opacity) {

        //用于十六进制颜色和rgb转换的正则
        var REG = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;

        sColor = sColor.toLowerCase();
        if (/^[A-Za-z]+$/.test(sColor))
            return sColor;
        if (sColor && REG.test(sColor)) {
            if (sColor.length === 4) {
                let sColorNew = "#";
                for (let i = 1; i < 4; i += 1) {
                    sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
                }
                sColor = sColorNew;
            }
            //处理六位的颜色值
            let sColorChange = [];
            for (let i = 1; i < 7; i += 2) {
                sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
            }

            if (opacity)
                sColorChange.push(opacity);
            return "rgba(" + sColorChange.join(",") + ")";
            // return sColorChange;
        } else {
            return sColor;
        }
    };

    // 地图事件
    function clickFunction(evt) {

        let feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, layerVetor) {
            return feature.getProperties().features;
        });

        // 图形要素的点击事件
        // 这里可以使用气泡框展示信息
        if (feature && feature.length == 1) {
            console.log(feature[0]);
            alert('点击了:' + feature[0].get('name'));
        } else {
            feature && console.log(feature);
            feature && alert('点击了包含:' + feature.length + ' 个图形要素的聚合图!');
        }
    }

    // 初始化点击事件标记
    var initClickFlag = false;

    // 点击事件
    function activateClickFunc() {
        initClickFlag && alert('已开启点击事件!');
        map.on('click', clickFunction);
        initClickFlag = true;
    }

    // 关闭点击事件
    function shutDownClick() {
        alert('已关闭点击事件!');
        map.un('click', clickFunction);
    }

    // 点线面数组
    var features = undefined;

    // 聚合图图层对象
    var clusterLayer = undefined;
    // 资源对象
    var vectorSource = undefined;
    // 聚合图对象
    var clusterSource = undefined;

    // 初始化聚合图
    function initCluster() {

        features = [];

        // 模拟数据
        for (var i = 1; i <= 15; i++) {
            var feature = getFeatureByWKT(
                "POINT(" + (113 + (i / 10)) + " " + (35 + (i / 10)) + ")"
            );
            var feature2 = getFeatureByWKT(
                "POINT(" + (113 + ((i + 5) / 10)) + " " + (35 + (i / 10)) + ")"
            );
            var feature3 = getFeatureByWKT(
                "POINT(" + (113 + ((i + 9) / 10)) + " " + (35 + (i / 10)) + ")"
            );

            var point = new ol.style.Style({
                // 点样式
                image: new ol.style.Icon({
                    // 允许跨域,如果不设置,打印地图不会打印
                    crossOrigin: 'anonymous',
                    // 标注图片和文字之间的距离
                    anchor: [0.5, 0],
                    // 图片的偏移
                    offset: [0.2, 0],
                    // 图片的锚点,一般来说,都是右下角
                    anchorOrigin: 'bottom-right',
                    //图标的url
                    src: "http://api.tianditu.gov.cn/v4.0/image/marker-icon.png",
                    scale: 1,
                })
            });
            feature.setStyle(point);
            feature.set('name', 'feature1-' + i);
            feature.set('capability', i / 15);

            feature2.setStyle(point);
            feature2.set('name', 'feature2-' + i);
            feature2.set('capability', i / 15);

            feature3.setStyle(point);
            feature3.set('name', 'feature3-' + i);
            feature3.set('capability', i / 15);

            features.push(feature)
            features.push(feature2)
            features.push(feature3)
        }

        /**
         * 资源
         */
        vectorSource = new ol.source.Vector({});

        // 聚合图
        clusterSource = new ol.source.Cluster({
            wrapX: false,
            source: vectorSource,
        });

        // 图层
        clusterLayer = new ol.layer.Vector({
            source: clusterSource,
            zIndex: 1,
        });
        map.addLayer(clusterLayer);
    }

    /**
     * todo 增加聚合图
     * @param dynamicData (参数是features)
     */
    function addData(dynamicData, distance, original) {

        // 最大图形要素数量
        var maxFeatureCount;
        // 当前分辨率
        var currentResolution;

        // 普通样式,普通小圆圈
        var originalStyle = function (feature) {

            var features = feature.get('features');

            var size = features.length;

            return new ol.style.Style({
                image: new ol.style.Circle({
                    radius: 30,
                    stroke: new ol.style.Stroke({
                        color: '#fff'
                    }),
                    fill: new ol.style.Fill({
                        color: '#969696'
                    })
                }),
                text: new ol.style.Text({
                    text: size.toString(),
                    fill: new ol.style.Fill({
                        color: '#fff'
                    })
                })
            });
        }

        // 动态样式样式,根据权重和数量计算
        var varyStyle = function (feature) {

            var originalFeatures = feature.get('features');
            var size = feature.get('features').length;

            var capability_avg = 0;
            var textName = "";
            var j = (void 0), jj = (void 0);
            for (j = 0, jj = originalFeatures.length; j < jj; ++j) {
                capability_avg += (originalFeatures[j].get("capability") || 1)
                textName = originalFeatures[j].get("name") || "";
            }
            capability_avg = (capability_avg / size).toFixed(2);
            var round = getPointArray(capability_avg);
            var opacity = Math.min(0.8, 0.4 + (size / maxFeatureCount));
            var style = new ol.style.Style({
                image: new ol.style.Circle({
                    radius: feature.get('radius'),
                    fill: new ol.style.Fill({
                        color: colorToRgb(round.split(",")[1], opacity)
                    })
                }),
                text: new ol.style.Text({
                    // text: textName ? (textName + ":" + capability_avg) : feature.get('radius'),
                    text: "权重平均值:" + capability_avg,
                    font: 'normal bold  14px  Arial,sans-serif',
                    fill: new ol.style.Fill({
                        color: '#fff'
                    }),
                    stroke: new ol.style.Stroke({
                        color: 'rgba(0, 0, 0, 0.6)',
                        width: 3
                    })
                })
            });
            return style;
        }

        // 单体 feature 样式
        var featureStyle = function (feature) {

            var originalFeatures = feature.get('features');

            if (originalFeatures.length != 1) {
                return;
            }

            var originalFeature = originalFeatures[0];

            var style = originalFeature.getStyle();
            style && style.setText(
                new ol.style.Text({
                    text: originalFeature.get("name"),
                    // 偏移
                    offsetX: 0,
                    offsetY: -54,
                    // 居中
                    textAlign: 'center',
                    // 比例
                    scale: 1,
                    textBaseline: 'middle',
                    // 边距
                    padding: [2, 2, 2, 2],
                    // 覆盖显示:即文字超过多边形也会显示
                    overflow: true,
                    // 字体颜色
                    fill: new ol.style.Fill({
                        color: 'rgba(51,51,51, 1)'
                    }),
                    // 字体边框,可以配合 fill 是文字高亮
                    stroke: new ol.style.Stroke({
                        color: 'rgba(0, 255, 255, 0.8)',
                        width: 2,
                    }),
                    // 背景色
                    backgroundFill: new ol.style.Fill({
                        color: 'rgba(252,254,255, 1)'
                    }),
                })
            )
            return style;
        }

        // 样式方法
        function styleFunction(feature, resolution) {

            // 如果是拖动地图,则不重新渲染样式
            if (resolution != currentResolution) {
                calculateClusterInfo(resolution);
                currentResolution = resolution;
            }

            var style;
            var size = feature.get('features').length || 0;

            // size大于1,则表示是聚合状态
            if (size > 1) {
                if (original == true) {
                    style = originalStyle(feature);
                } else {
                    style = varyStyle(feature);
                }
                // size 等于1,表示是单体 feature
            } else if (size == 1) {
                style = featureStyle(feature);
            }
            return style;
        }

        // 计算聚合图样式信息
        var calculateClusterInfo = function () {

            if (!clusterLayer) {
                return;
            }

            maxFeatureCount = 0;
            var features = clusterLayer.getSource().getFeatures();
            var feature, radius;
            for (var i = features.length - 1; i >= 0; --i) {
                feature = features[i];
                var originalFeatures = feature.get('features');
                // 计算权重
                var capability = 0;
                var j = (void 0), jj = (void 0);
                for (j = 0, jj = originalFeatures.length; j < jj; ++j) {
                    // 这是使用 capability 自定义属性的值计算权重
                    capability += (originalFeatures[j].get("capability") || 1)
                }

                // 根据实际的数据量,调整聚合显示半径的大小
                // PS:这个需要更新项目实际调整
                if (originalFeatures.length < 10) {
                    radius = capability + originalFeatures.length;
                    while (radius > 100) {
                        radius = radius / 10;
                    }
                    radius = radius + 10;
                } else if (originalFeatures.length >= 10 && originalFeatures.length <= 50) {
                    radius = capability + originalFeatures.length;
                    while (radius > 100) {
                        radius = radius / 10;
                    }
                    radius = radius + 20;
                } else if (originalFeatures.length > 100 && originalFeatures.length <= 5000) {
                    radius = capability + originalFeatures.length;
                    while (radius > 100) {
                        radius = radius / 10;
                    }
                } else if (originalFeatures.length > 5000 && originalFeatures.length <= 10000) {
                    radius = capability + originalFeatures.length;
                    while (radius > 100) {
                        radius = radius / 10;
                    }
                } else if (originalFeatures.length > 10000) {
                    radius = capability + originalFeatures.length;
                    while (radius > 100) {
                        radius = radius / 10;
                    }
                    radius = radius;
                }

                // 取二者最大值
                maxFeatureCount = Math.max(maxFeatureCount, jj);

                feature.set('radius', radius);
            }
        };

        // 获取聚合图颜色
        // 自定义颜色(图例和颜色以逗号拼接)
        var getPointArray = function (v) {
            if (v >= 0.8 && v <= 1) return '0.8-1.0,#FF0000'
            else if (v >= 0.6 && v < 0.8) return '0.6-0.8,#FFFF00'
            else if (v >= 0.4 && v < 0.6) return '0.4-0.6,#DAA520'
            else if (v >= 0.2 && v < 0.4) return '0.2-0.4,#0000FF'
            else if (v >= 0 && v < 0.2) return '0-0.2,#228B22'
        }

        // 设置聚合距离,也就是半径范围内聚合
        clusterSource.setDistance(distance);
        // 添加数据
        vectorSource.addFeatures(features);

        // 设置样式
        clusterLayer && clusterLayer.setStyle(styleFunction)
    }

    // 添加聚合图
    // flag, true 为加载原始样式,其他为加载权重样式
    function addCluster(flag) {
        closeCluster();
        initCluster();
        flag ? addData(features, 60, flag) : addData(features, 40);
    }

    // 关闭聚合图
    function closeCluster() {
        clusterLayer && map.removeLayer(clusterLayer);
        clusterLayer = undefined;
    }

    // 默认加载原始聚合图
    addCluster(true);
    // 默认开始点击事件
    activateClickFunc();
</script>
<button id="addCluster" onClick="addCluster(true)">添加聚合图</button>
<button id="addWeightCluster" onClick="addCluster()">添加权重聚合图</button>
<button id="closeCluster" onClick="closeCluster()">关闭聚合图</button>
<button id="activateClickFunc" onClick="activateClickFunc()">开启点击事件</button>
<button id="shutDownClick" onClick="shutDownClick()">关闭点击事件</button>
</body>
</html>

在线示例

Openlayers 聚合图:Openlayers-cluster


网站公告

今日签到

点亮在社区的每一天
去签到