天地图实现海量聚合marker--uniapp&后端详细实现

本文章详细的讲解了前后端代码来 实现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;
}

如果对您的工作有所启发和帮助,点个搜藏加关注吧~

相关推荐
It's now3 小时前
Spring AI 基础开发流程
java·人工智能·后端·spring
cxh_陈3 小时前
线程的状态,以及和锁有什么关系
java·线程·线程的状态·线程和锁
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
R.lin3 小时前
Java 8日期时间API完全指南
java·开发语言·python
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 高校教学质量监控平台为例,包含答辩的问题和答案
java·eclipse
高山上有一只小老虎4 小时前
翻之矩阵中的行
java·算法
火钳游侠4 小时前
java单行注释,多行注释,文档注释
java·开发语言
曼巴UE54 小时前
UE FString, FName ,FText 三者转换,再次学习,官方文档理解
服务器·前端·javascript
wanhengidc4 小时前
云手机的存储空间可以灵活扩展吗?
运维·服务器·科技·智能手机·云计算
code bean4 小时前
【CMake】为什么需要清理 CMake 缓存文件?深入理解 CMake 生成器切换机制
java·spring·缓存