本文章详细的讲解了前后端代码来 实现uniapp天地图功能的实现 以及 后端海量数据的聚合查询 和网格算法实现思路。
并对当数据量增加和用户频繁请求接口时可能导致服务器负载过高做了前后端优化。
前端uniapp:
实现了天地图的行政区划边界/地图切换/比例尺/海量数据聚合marker/获取地图当前可视范围坐标/文本信息窗口 /使用节流、防抖的方式来减少过量请求 等功能
后端java:
实现了海量数据的聚合查询,并对查询语句和逻辑做了优化以及sql索引优化/并通过网格算法来解决数据精准度的难点。
效果如下:
前端uniapp实现代码如下:
uniapp/static/skymap.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://api.tianditu.gov.cn/api?v=4.0&tk=你自己的key"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100vh;
font-family: "Microsoft YaHei";
}
#viewDiv {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
</style>
</head>
<body>
<div id="viewDiv"></div>
<script>
let markerClusterer; // 用于保存聚合器
const map = new T.Map("viewDiv");
const fetchInterval = 5000; // 节流时间间隔(5秒)
let lastFetchTime = 0;
function load() {
addGeoBoundary(map);
map.enableScrollWheelZoom();
map.addControl(new T.Control.MapType());
map.addControl(new T.Control.Scale());
// 添加缩放和移动事件监听器
map.addEventListener('zoomend', () => updateMarkers(map));
map.addEventListener('moveend', () => updateMarkers(map));
// 初始加载标记
setTimeout(() => updateMarkers(map), 1000);
}
async function updateMarkers(map) {
const currentTime = Date.now();
if (currentTime - lastFetchTime < fetchInterval) {
return; // 节流:如果距离上次请求不够,则返回
}
lastFetchTime = currentTime;
const bounds = map.getBounds();
const sw = bounds.getSouthWest();
const ne = bounds.getNorthEast();
const requestData = {
bottomLeft: [sw.lng, sw.lat],
topRight: [ne.lng, ne.lat]
};
try {
const response = await fetch('http://localhost:10086/things/aggregated-geo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("响应数据:", data);
createMarkers(map, data.data);
} catch (error) {
console.error("请求失败:", error);
}
}
function createMarkers(map, data) {
// 清除旧的聚合标记
if (markerClusterer) {
markerClusterer.clearMarkers();
}
const markers = [];
const { gridCellList, noGeoThings } = data;
gridCellList.forEach(item => {
for (let i = 0; i < item.thingCount; i++) {
const marker = new T.Marker(new T.LngLat(item.position.longitude, item.position.latitude), {
title: `Thing Count: ${item.thingCount}`
});
markers.push(marker);
}
});
if (noGeoThings && noGeoThings.thingCount > 0) {
const point = new T.LngLat(noGeoThings.position.longitude, noGeoThings.position.latitude);
const marker = new T.Marker(point);
map.addOverLay(marker);
const markerInfoWin = new T.InfoWindow("无位置设备: " + noGeoThings.thingCount);
marker.addEventListener("click", () => marker.openInfoWindow(markerInfoWin));
}
// 使用聚合器聚合标记
markerClusterer = new T.MarkerClusterer(map, { markers });
}
function addGeoBoundary(map) {
fetch('https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=520322')
.then(response => response.json())
.then(data => {
const coordinates = data.features[0].geometry.coordinates;
const centroid = data.features[0].properties.centroid;
map.centerAndZoom(new T.LngLat(centroid[0], centroid[1]), 8);
coordinates.forEach(polygon => {
polygon.forEach(boundary => {
const boundaryPolygon = new T.Polygon(boundary.map(coord => new T.LngLat(coord[0], coord[1])), {
color: "#7C7BF6",
weight: 1,
opacity: 0.7,
fillColor: "#ABAAF3",
fillOpacity: 0.1
});
boundaryPolygon.addEventListener("mouseover", () => {
boundaryPolygon.setFillColor("#ABAAF3");
boundaryPolygon.setFillOpacity(0.6);
});
boundaryPolygon.addEventListener("mouseout", () => {
boundaryPolygon.setFillColor("#DCDBF0");
boundaryPolygon.setFillOpacity(0.6);
});
map.addOverLay(boundaryPolygon);
});
});
})
.catch(error => console.error('Error fetching GeoJSON:', error));
}
load();
</script>
</body>
</html>
pages/index.vue
javascript
<uni-section title="地区分布" class="item map-container" type="line">
<iframe src="/static/skymap.html" class="map-frame"></iframe>
</uni-section>
后端java实现代码如下:
impl.java
java
@Override
public ThingGeo getAggregatedThingGeo(ThingGeoReqDTO reqDTO) {
//TODO 租户过滤
Area area = areaRepository.getAreaByCode(行政区编码);
//1.行政编码区域的中心点,查询没有位置的设备总数:
JSONObject properties = area.getBound().getJSONArray("features").getJSONObject(0).getJSONObject("properties");
JSONArray centerPosition = properties.getJSONArray("center"); //中心点位置
double centerLon = centerPosition.getDouble(0);
double centerLat = centerPosition.getDouble(1);
GeoPoint centerPoint = new GeoPoint(centerLon, centerLat);
long noGeoThingCount = thingRepository.countByNoGeoPosition();
GridCellThing noGeoThings = new GridCellThing(centerPoint, noGeoThingCount);
//2.网格查询有位置信息的设备总数以及权重点
double[] topRight = reqDTO.getTopRight();
double[] bottomLeft = reqDTO.getBottomLeft();
// 计算X和Y的差值(视图长和宽)
double deltaX = topRight[0] - bottomLeft[0];
double deltaY = topRight[1] - bottomLeft[1];
// 计算X和Y的平均值
double avgX = deltaX / 4;
double avgY = deltaY / 4;
// 使用右上角作为起始点
double x = topRight[0];
double y = topRight[1];
List<GridCellThing> gridCellThings = new ArrayList<>();
// 循环生成4*4=16网格
for (int a = 0; a < 4; a++) {
for (int i = 0; i < 4; i++) {
// 计算网格边界
double minX = x - (i + 1) * avgX;
double maxX = x - i * avgX;
double minY = y - (a + 1) * avgY;
double maxY = y - a * avgY;
//小网格(矩形)的两个对角的经纬度
double[] boxTopRight = new double[]{maxX, maxY};
double[] boxBottomLeft = new double[]{minX, minY};
long count = thingRepository.countByBoundingBox(boxBottomLeft, boxTopRight);
if (count > 0) {
GeoPoint center = thingRepository.findWeightedCenter(boxBottomLeft, boxTopRight);
GeoPoint geoPoint = new GeoPoint(center.getLongitude(), center.getLatitude());
GridCellThing gridCellThing = new GridCellThing();
gridCellThing.setThingCount(count);
gridCellThing.setPosition(geoPoint);
gridCellThings.add(gridCellThing);
}
}
}
ThingGeo thingGeo = new ThingGeo();
thingGeo.setGridCellList(gridCellThings);
thingGeo.setNoGeoThings(noGeoThings);
return thingGeo;
}
ThingRepository.java
java
public interface ThingRepository extends MongoRepository<Thing, String> {
@CountQuery("{$and: [{'position': {$exists: true}}, {'deletedAt': null},"
+ "{'position': {$geoWithin: { $box: [?0, ?1] }}}]}")
long countByBoundingBox(double[] bottomLeft, double[] topRight);
@Aggregation(pipeline = {
"{ $match: { $and: [ { 'position': { $exists: true } }, { 'deletedAt': null }, { 'position': { $geoWithin: { $box: [?0, ?1] } } } ] } }",
"{ $group: { _id: null, longitude: { $avg: '$position.longitude' }, latitude: { $avg: '$position.latitude' } } }"
})
GeoPoint findWeightedCenter(double[] bottomLeft, double[] topRight);
@CountQuery("{ $or: [ { 'position': { $exists: false } }, { 'position': null }, { 'position.longitude': 0, 'position.latitude': 0 } ], 'deletedAt': null }")
long countByNoGeoPosition();
}
Entity
java
Entity
ThingGeo.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ThingGeo {
private List<GridCellThing> gridCellList; //各个网格单元内的设备总数
private GridCellThing noGeoThings; // 编码区域内没有地理位置的设备总数
}
//**************************//
GridCellThing .java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GridCellThing {
private GeoPoint position;
private long thingCount;
}
如果对您的工作有所启发和帮助,点个搜藏加关注吧~