Java后端进行经纬度点抽稀聚合,HTML呈现及前端聚合实现点聚合~

Java后端进行经纬度点抽稀聚合,HTML呈现及前端聚合实现点聚合~

1. 效果图~

1.1 前端实现聚合及呈现

Java后端进行经纬度点抽稀聚合,HTML呈现及前端聚合实现点聚合~

1.2 后端实现点聚合,前端渲染呈现效果图

Java后端进行经纬度点抽稀聚合,HTML呈现及前端聚合实现点聚合~

2. 原理

  1. 构建测试数据
  2. 依赖前端传递的参数:bounds、mPXInMeters、zoom;分别是窗口的可视经纬度范围、单位像素距离、地图级别;

单位像素距离也可以根据地图级别进行计算;

{“level”: 0, “resolution”: 156543.033928},
{“level”: 1, “resolution”: 78271.5169639999},
{“level”: 2, “resolution”: 39135.7584820001},
{“level”: 3, “resolution”: 19567.8792409999},
{“level”: 4, “resolution”: 9783.93962049996},
{“level”: 5, “resolution”: 4891.96981024998},
{“level”: 6, “resolution”: 2445.98490512499},
{“level”: 7, “resolution”: 1222.99245256249},
{“level”: 8, “resolution”: 611.49622628138},
{“level”: 9, “resolution”: 305.748113140558},
{“level”: 10, “resolution”: 152.874056570411},
{“level”: 11, “resolution”: 76.4370282850732},
{“level”: 12, “resolution”: 38.2185141425366},
{“level”: 13, “resolution”: 19.1092570712683},
{“level”: 14, “resolution”: 9.55462853563415},
{“level”: 15, “resolution”: 4.77731426794937},
{“level”: 16, “resolution”: 2.38865713397468},
{“level”: 17, “resolution”: 1.19432856685505},
{“level”: 18, “resolution”: 0.597164283559817}

  1. 后端可以设置多少个像素聚合;

3. 源码

3.1 前端JS实现点聚合及呈现源码

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>创建样式聚类</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css' rel='stylesheet' />
    <style>
        body { margin:0; padding:0; }
        #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
</head>
<body>


<div id='map'></div>

<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiemhhbmcyMDIxIiwiYSI6ImNrbGthdmFvbDM0cTMyb3M2eXFiODR5d3QifQ.ow97I1ikb9zlP3p9qmFuBQ';
var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/dark-v10',
    center: [-103.59179687498357, 40.66995747013945],
    zoom: 3
});

map.on('load', function() {
    // Add a new source from our GeoJSON data and set the
    // 'cluster' option to true. GL-JS will add the point_count property to your source data.
    map.addSource("earthquakes", {
        type: "geojson",
        // Point to GeoJSON data. This example visualizes all M1.0+ earthquakes
        // from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
        data: "https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
        cluster: true,
        clusterMaxZoom: 14, // Max zoom to cluster points on
        clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
    });

    map.addLayer({
        id: "clusters",
        type: "circle",
        source: "earthquakes",
        filter: ["has", "point_count"],
        paint: {
            // Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
            // with three steps to implement three types of circles:
            //   * Blue, 20px circles when point count is less than 100
            //   * Yellow, 30px circles when point count is between 100 and 750
            //   * Pink, 40px circles when point count is greater than or equal to 750
            "circle-color": [
                "step",
                ["get", "point_count"],
                "#51bbd6",
                100,
                "#f1f075",
                750,
                "#f28cb1"
            ],
            "circle-radius": [
                "step",
                ["get", "point_count"],
                20,
                100,
                30,
                750,
                40
            ]
        }
    });

    map.addLayer({
        id: "cluster-count",
        type: "symbol",
        source: "earthquakes",
        filter: ["has", "point_count"],
        layout: {
            "text-field": "{point_count_abbreviated}",
            "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
            "text-size": 12
        }
    });

    map.addLayer({
        id: "unclustered-point",
        type: "circle",
        source: "earthquakes",
        filter: ["!", ["has", "point_count"]],
        paint: {
            "circle-color": "#11b4da",
            "circle-radius": 4,
            "circle-stroke-width": 1,
            "circle-stroke-color": "#fff"
        }
    });

    // inspect a cluster on click
    map.on('click', 'clusters', function (e) {
        var features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
        var clusterId = features[0].properties.cluster_id;
        map.getSource('earthquakes').getClusterExpansionZoom(clusterId, function (err, zoom) {
            if (err)
                return;

            map.easeTo({
                center: features[0].geometry.coordinates,
                zoom: zoom
            });
        });
    });

    map.on('mouseenter', 'clusters', function () {
        map.getCanvas().style.cursor = 'pointer';
    });
    map.on('mouseleave', 'clusters', function () {
        map.getCanvas().style.cursor = '';
    });
});
</script>

</body>
</html>

3.2 后端点聚合(返回geojson)及前端自实现渲染源码

/**
 * @param visibleBounds 屏幕的范围 前端传
 * @param list          所有的点数据
 * @param mClusters     聚合后的数据集合
 * @param isLattice     是否是方格和距离的算法 , 是 方格和距离的算法   , 否 距离算法
 * @return
 */
private static void calculateOrgClusters(LatLngBounds visibleBounds, List<Map<String, Object>> list, List<Cluster> mClusters, double mClusterDistance, Integer zoom, boolean isLattice) {
    mIsCanceled = false;
    mClusters.clear();
    ClusterItem clusterItem = new RegionItem();
    LatLng mLatLng;
    for (int i = 0; i < list.size(); i++) {
        Map<String, Object> data = list.get(i);
        mLatLng = new LatLng(Double.parseDouble(data.get("lat").toString()), Double.parseDouble(data.get("lng").toString()));
        clusterItem = new RegionItem(mLatLng, data);
        if (mIsCanceled) {
            return;
        }
        LatLng latlng = clusterItem.getPosition();
        if (visibleBounds.contains(latlng)) {
            Cluster cluster = null;
            if (isLattice) {
                // 方格和距离的算法
                cluster = getClusterFangGe(latlng, mClusters);
            } else {
                // 距离算法
                cluster = getCluster(latlng, mClusters, mClusterDistance, zoom);
            }

            if (cluster != null) {
                cluster.addCount();
            } else {
                cluster = new Cluster(latlng);
                mClusters.add(cluster);
                cluster.addClusterItem(clusterItem);
                cluster.addCount();
                cluster.setMap(data);
                // 设置中心点的范围
                if (isLattice) {
                    LatLngBounds latLngBounds = AMapUtils.getAround(latlng, mClusterDistance);
                    cluster.setLatLngBounds(latLngBounds);
                }
            }
        }
    }
}
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no'/>
    <!--本地加载的js及css文件-->
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.1.1/mapbox-gl.css' rel='stylesheet'/>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <style>
        #m_infobar {
            position: absolute;
            bottom: 100px;
            left: 20px;
            width: 200px;
            height: 200px;
            overflow: auto;
            z-index: 201;
            background: greenyellow;
            padding-left: 10px;
        }

        #menu {
            background: #fff;
            position: absolute;
            z-index: 1;
            top: 70px;
            right: 50px;
            border-radius: 3px;
            width: 120px;
            border: 1px solid rgba(0, 0, 0, 0.4);
            font-family: 'Open Sans', sans-serif;
        }

        #menu a {
            font-size: 13px;
            color: #404040;
            display: block;
            margin: 0;
            padding: 0;
            padding: 10px;
            text-decoration: none;
            border-bottom: 1px solid rgba(0, 0, 0, 0.25);
            text-align: center;
        }

        #menu a:last-child {
            border: none;
        }

        #menu a:hover {
            background-color: #f8f8f8;
            color: #404040;
        }

        #menu a.active {
            background-color: #3887be;
            color: #ffffff;
        }

        #menu a.active:hover {
            background: #3074a4;
        }
    </style>
</head>
<body style="height: 100%;">

<div class="panel panel-default">
    <div class="panel-heading">
        <div class="row">
            <div class="col-md-4"><strong>Geojson后端聚合轨迹点</strong></div>
            <div class="col-md-8"><label id="i_click"></label></div>
        </div>
    </div>
    <div class="panel-body">
        <nav id="menu"></nav>
        <div id="map" style="width: 100%; height: 800px;">
            <div id='m_infobar'>
                <div id="tooltip-name"></div>
                <div id='tooltip'></div>
            </div>
        </div>

    </div>
    <div class="panel-footer" style="bottom: 0px;">
        <div class="row">
            <div class="col-xs-4"><label id="i_coordinate"></label></div>
            <div class="col-xs-8"><label id="i_show"></label></div>
        </div>
    </div>
</div>

<script>
    mapboxgl.accessToken = 'pk.eyJ1IjoiemhhbmcyMDIxIiwiYSI6ImNrbGthdmFvbDM0cTMyb3M2eXFiODR5d3QifQ.ow97I1ikb9zlP3p9qmFuBQ';
    var map = new mapboxgl.Map({
        resizeEnable: true,
        container: 'map',
        style: 'mapbox://styles/mapbox/dark-v10',
        center: [105, 34],
        zoom: 4
    });

    map.on('load', function () {
        // Add a new source from our GeoJSON data and set the
        // 'cluster' option to true. GL-JS will add the mag property to your source data.
        map.addSource("earthquakes", {
            type: "geojson",
            // Point to GeoJSON data. This example visualizes all M1.0+ earthquakes       
            // from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
            // data: "https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
            // 只需修改数据来源~~
            data: "/geo?bounds=" + map.getBounds().toString().replaceAll('LngLat', '').replace('Bounds', '').replace('),', ';').replaceAll('(', '').replaceAll(')', '') + "&zoom=" + map.getZoom(),
//            cluster: true,
            // clusterMaxZoom: 20, // Max zoom to cluster points on
            //clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
        });

        var url = "/geo?bounds=" + map.getBounds().toString().replaceAll('LngLat', '').replace('Bounds', '').replace('),', ';').replaceAll('(', '').replaceAll(')', '') + "&zoom=" + map.getZoom();
        var zoomOri = map.getZoom();
        console.log('url------: ' + url);
        map.addLayer({
            id: "clusters",
            type: "circle",
            source: "earthquakes",
            filter: ["has", "mag"],
            paint: {
                // Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
                // with five steps to implement three types of circles:
                //   * 11b4da,1px circles when point mag is 1
                //   * 99CCCC,1px circles when point mag is less than 20
                //   * Blue, 20px circles when point mag is less than 100
                //   * Yellow, 30px circles when point mag is between 100 and 750
                //   * Pink, 40px circles when point mag is greater than or equal to 750
                "circle-color": [
                    "step",
                    ["get", "mag"],
                    "#11b4da",
                    1,
                    "#99CCCC",
                    20,
                    "#3366FF",
                    100,
                    "#f1f075",
                    750,
                    "#f28cb1"
                ],
                "circle-radius": [
                    "step",
                    ["get", "mag"],
                    20,
                    100,
                    30,
                    750,
                    40
                ]
            }
        });

        map.addLayer({
            id: "cluster-mag",
            type: "symbol",
            source: "earthquakes",
            filter: ["has", "mag"],
            layout: {
                "text-field": "{mag}",
                "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
                "text-size": 12
            }
        });

        // inspect a cluster on click
        map.on('click', 'clusters', function (e) {
            var features = map.queryRenderedFeatures(e.point, {layers: ['clusters']});
            var deviceId = features[0].properties.deviceId;
            var clusterId = features[0].properties.clusterId;
            console.log("deviceId: ", deviceId);
            console.log("clusterId: ", clusterId);
            console.log("lng,lat: ", "[" + features[0].geometry.coordinates[0] + "," + features[0].geometry.coordinates[1] + "]");
            console.log("lng,lat: ", JSON.stringify(features[0].geometry.coordinates));

            map.easeTo({
                center: features[0].geometry.coordinates,
                zoom: map.getZoom()
            });
        });

        map.on("mousemove", function (e) {
            var features = map.queryRenderedFeatures(e.point, {layers: ["clusters"]});
            if (features.length) {
                //show name and value in sidebar
                var item = features[0].properties;
                var arr = [];
                arr.push("设备id:" + item.deviceId);
                arr.push("在线:" + item.online);
                arr.push("故障:" + item.fault);
                arr.push("重叠点数:" + item.mag);
                viewMapOption();

                document.getElementById('tooltip').innerHTML = arr.join('<br/>');
            } else {
                //if not hovering over a feature set tooltip to empty
                document.getElementById('tooltip').innerHTML = "";
            }
        });

        map.on('mouseenter', 'clusters', function () {
            map.getCanvas().style.cursor = 'pointer';
        });
        map.on('mouseleave', 'clusters', function () {
            map.getCanvas().style.cursor = '';
        });

        // 获取点数据
        function queryData(str) {
            zoom = map.getZoom();
            zoom = zoom.toString().substr(0, zoom.toString().indexOf("."));
            if (zoom) {

            } else {
                console.log("queryData0---: ", zoom);
                zoom = zoomOri + 1;
                console.log("queryData1---: ", zoom);
            }
            var url = "/geo?bounds=" + map.getBounds().toString().replaceAll('LngLat', '').replace('Bounds', '').replace('),', ';').replaceAll('(', '').replaceAll(')', '') + "&zoom=" + zoom;
            console.log('queryData: ', str, url);
            $.ajax({
                url: url,  // ajax请求要请求的地址
                type: "get", // 请求的类型  get  post
                success: function (data) {
                    // 请求成功之后要执行的方法
                    // data  接收请求成功之后的返回值
                    map.getSource('earthquakes').setData(data);
                },
                error: function (error) {
                    // 请求失败之后要执行的内容
                }
            })
        }

        // 移动结束事件
        map.on('moveend', queryData);
        // 缩放完成事件
        map.on('zoomend', queryData);
        // 拖拽完成事件
        map.on('dragend', queryData);
        // 鼠标滚动
        map.on("wheel", queryData);

        viewMapOption();
        map.on('move', viewMapOption);
        map.on('mousemove', viewCoordinate);
    });

    function locationByXY() {
        var px = parseFloat($("#m_x").val());
        var py = parseFloat($("#m_y").val());
        map.setCenter([px, py]);
    }

    /**
     * 容器改变触发
     */
    function resizeMap() {
        //初始化宽度、高度
        $("#map").height($(window).height() - 150);
        //当文档窗口发生改变时 触发
        $(window).resize(function () {
            $("#map").height($(window).height() - 150);
        });
    }

    function viewCoordinate(evt) {
        if (evt != null) $("#i_coordinate").text("当前坐标:" + evt.lngLat.lng.toFixed(7) + "," + evt.lngLat.lat.toFixed(7));
    }

    /**
     * 显示地图状态信息
     */
    function viewMapOption() {
        var bounds = map.getBounds();
        console.log("级别=" + map.getZoom() + ",中心(" + bounds.getCenter().lng.toFixed(7) + "," + bounds.getCenter().lat.toFixed(7) + ")");
        console.log("边界[" + bounds.getSouthWest().lng.toFixed(7) + "," + bounds.getSouthWest().lat.toFixed(7) + ";" + bounds.getNorthEast().lng.toFixed(7) + "," + bounds.getNorthEast().lat.toFixed(7) + "]");
        $("#i_click").text("级别=" + map.getZoom() + ",中心(" + bounds.getCenter().lng.toFixed(7) + "," + bounds.getCenter().lat.toFixed(7) + ")");
        $("#i_show").text("边界[" + bounds.getSouthWest().lng.toFixed(7) + "," + bounds.getSouthWest().lat.toFixed(7) + ";" + bounds.getNorthEast().lng.toFixed(7) + "," + bounds.getNorthEast().lat.toFixed(7) + "]");
    }
</script>

</body>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet">
</html>

参考

上一篇:HashMap 与 Hashtable的区别


下一篇:HashMap 死锁全网没有一个可以复现的demo