天地图实现海量聚合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;
}

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

相关推荐
没书读了14 分钟前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·22 分钟前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic1 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā1 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
懒洋洋大魔王1 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康1 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神1 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
sinat_384241091 小时前
使用 npm 安装 Electron 作为开发依赖
服务器
qq_327342732 小时前
Java实现离线身份证号码OCR识别
java·开发语言
沉默璇年2 小时前
react中useMemo的使用场景
前端·react.js·前端框架