Java后端进行经纬度点抽稀聚合,HTML呈现及前端聚合实现点聚合~
1. 效果图~
1.1 前端实现聚合及呈现
1.2 后端实现点聚合,前端渲染呈现效果图
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}
- 后端可以设置多少个像素聚合;
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>
参考
- https://blog.csdn.net/qq_36120342/article/details/108752068
- https://gitee.com/liuhaomin/publicdemo/tree/master/dianjuhe 依赖mysql库
- https://www.cnblogs.com/haibalai/p/9049625.html