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

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

相关推荐
菜鸟xy..4 分钟前
windows server 2008 建立ftp服务器
运维·服务器
网络研究院8 分钟前
新工具可绕过 Google Chrome 的新 Cookie 加密系统
前端·chrome·系统·漏洞·加密·绕过
长潇若雪10 分钟前
结构体(C 语言)
c语言·开发语言·经验分享·1024程序员节
feilieren12 分钟前
leetcode - 684. 冗余连接
java·开发语言·算法
The Future is mine23 分钟前
Java根据word模板导出数据
java·开发语言
一颗甜苞谷36 分钟前
开源一款前后端分离的企业级网站内容管理系统,支持站群管理、多平台静态化,多语言、全文检索的源码
java·开发语言·开源
星夜孤帆36 分钟前
Java面试题集锦
java·开发语言
论迹44 分钟前
【Java】-- 接口
java·开发语言
DARLING Zero two♡44 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.12——深入理解指针(2)
c语言·开发语言·科技·1024程序员节
x原力觉醒1 小时前
uniapp跨域问题,在开发环境中配置
javascript·vue.js·uni-app