天地图实现海量聚合marker--uniapp后端详细实现
本文章详细的讲解了前后端代码来 实现uniapp天地图功能的实现 以及 后端海量数据的聚合查询 和网格算法实现思路。
并对当数据量增加和用户频繁请求接口时可能导致服务器负载过高做了前后端优化。
前端uniapp:
实现了天地图的行政区划边界/地图切换/比例尺/海量数据聚合marker/获取地图当前可视范围坐标/文本信息窗口 /使用节流、防抖的方式来减少过量请求 等功能
后端java:
实现了海量数据的聚合查询,并对查询语句和逻辑做了优化以及sql索引优化/并通过网格算法来解决数据精准度的难点。
效果如下:
前端uniapp实现代码如下:
uniapp/static/skymap.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
<uni-section title="地区分布" class="item map-container" type="line"><iframe src="/static/skymap.html" class="map-frame"></iframe></uni-section>
后端java实现代码如下:
impl.java
@Overridepublic 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
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
EntityThingGeo.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;
}
如果对您的工作有所启发和帮助,点个搜藏加关注吧~
相关文章:

天地图实现海量聚合marker--uniapp后端详细实现
本文章详细的讲解了前后端代码来 实现uniapp天地图功能的实现 以及 后端海量数据的聚合查询 和网格算法实现思路。 并对当数据量增加和用户频繁请求接口时可能导致服务器负载过高做了前后端优化。 前端uniapp: 实现了天地图的行政区划边界/地图切换/比例尺/海量数…...

Bug | 项目中数据库查询问题
问题描述 理论上,点击查询后,表头应当显示中文。而不是上面的在数据库中的表头【如上图示】 正常点击查询后,如果没有输入值,应当是查询所有的信息。 原因分析: 这里是直接使用SELECT * 导致的。例如: S…...

C++入门基础知识129—【关于C 库函数 - time()】
成长路上不孤单😊😊😊😊😊😊 【14后😊///C爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C 库函数 - time()的相关内容࿰…...

大文件秒传,分片上传,断点续传
大文件分片上传 一 功能描述 1.文件通过web端分片多线程上传到服务端,然后web端发起分片合并,完成大文件分片上传功能 2.上传过的大文件,实现秒传 3.上传过程中,服务异常退出,实现断点续传 二 流程图 三 代码运行…...

多生境扩增子探秘:深度溯源与多样性解析
分析微生物组数据的组成结构的一个主要挑战是确定其潜在来源。在微生物来源分析中,随机森林、SourceTracker和FEAST都有较广泛应用。今天,小编就带大家看一篇发表在《iMeta》的文章,使用溯源技术追踪微生物的来源与去向,揭示生物在…...

Selenium4自动化测试常用函数总结,各种场景操作实战
🍅 点击文末小卡片 ,免费获取软件测试全套资料,资料在手,涨薪更快 seleninum作为自动化测试的工具,自然是提供了很多自动化操作的函数,下面列举下比较常用的函数,更多可见官方文档:…...

图像生成新范式:智源推出全能视觉生成模型 OmniGen
大型语言模型(LLM)的出现统一了语言生成任务,并彻底改变了人机交互。然而,在图像生成领域,能够在单一框架内处理各种任务的统一模型在很大程度上仍未得到探索。近日,智源推出了新的扩散模型架构 OmniGen&am…...
实现RPC接口的demo记录
1.Thrift RPC 接口实现 Demo Service public class DemoServiceImpl implements DemoService.Iface {private static final Logger logger LoggerFactory.getLogger(DemoServiceImpl.class);Overridepublic String sayHello(Context context, String msg) throws TException …...
Python期末题目 | 期末练习题【概念题+代码】
一、前言 Python 是一门功能强大且易于学习的编程语言,在高校中被广泛用作教学语言。Python 的期末考试通常会包含基础知识和编程实践,以考察学生的理解与应用能力。本文整理了一套 Python 期末练习题,包括选择题、填空题、判断题和代码题。…...
OpenCV基本操作(python开发)——(6)视频基本处理
OpenCV——视频基本处理 一、读取摄像头 import numpy as np import cv2cap cv2.VideoCapture(0) # 实例化VideoCapture对象, 0表示第一个摄像头 while cap.isOpened():ret, frame cap.read() # 捕获帧cv2.imshow("frame", frame)c cv2.waitKey(1) # 等待1毫…...

详解Java之Spring MVC篇一
目录 Spring MVC 官方介绍 MVC RequestMapping 传递参数 无参数 单个参数 针对String类型 针对Integer类型 针对int类型 针对自定义类型 多个参数 参数重命名 参数强制一致 参数不强制一致 传递数组 编辑传递List 编辑 传递JSON 编辑 从路径中获取参…...

ubuntu20.04上使用 Verdaccio 搭建 npm 私有仓库
安装nvm 首先安装必要的工具: apt update apt install curl下载并执行nvm安装脚本: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash添加环境变量(如果安装脚本没有自动添加)。编辑 ~/.bash…...

Python实现办公自动化的数据可视化与报表生成
在 Python 中,可以利用多个库来实现办公自动化中的数据可视化与报表生成。以下是具体的方法: 一、数据可视化 使用 matplotlib 库 matplotlib 是一个强大的 Python 绘图库,可以创建各种类型的静态、动态和交互式图表。示例代码:i…...

前端知识串联笔记(更新中...)
1.MVVM MVVM 是指 Model - View - ViewModel,Model 是数据与业务逻辑,View 是视图,ViewModel 用于连接 View 和 Model Model ---> View:将数据转化成所看到的页面,实现的方式:Data Bindings -- 数据绑定…...
PostgreSQL根据字符串的长度排序
PostgreSQL根据字符串的长度排序 在 PostgreSQL 中,你可以使用 LENGTH 函数来获取字符串的长度,并根据这个长度进行排序。LENGTH 函数会返回字符串的字符数。 以下是一个基本的 SQL 查询示例,它根据 some_column 字符串列的长度对表中的行进…...

计算机网络:网络层 —— IP数据报的发送和转发过程
文章目录 IP数据报的发送和转发过程主机发送IP数据报路由器转发IP数据报示例 IP数据报的发送和转发过程 IP 数据报的发送和转发过程包含以下两个过程: 主机发送IP数据报路由器转发IP数据报 直接交付:源主机与目的主机在同一网络中间接交付:…...

【算力基础】GPU算力计算和其他相关基础(TFLOPS/TOPS/FP32/INT8...)
文章目录 :one: 算力的常见指标:two: 算力计算:three: 常用链接 🚀 本文主要是聚焦于深度学习领域的 GPU的算力估计,其他类型的硬件设备如CPU可以类比参考。 1️⃣ 算力的常见指标 算力衡量主要与运算速度和精度这两个指标有关。 🌔速度指…...

UI自动化测试(app端)4.0
✨博客主页: https://blog.csdn.net/m0_63815035?typeblog 💗《博客内容》:.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 📢博客专栏: https://blog.csdn.net/m0_63815035/cat…...

C#与C++交互开发系列(十):数组传递的几种形式
前言 在C#和C的交互开发中,数组传递是一个非常常见且实用的场景。数组可以作为方法的参数,也可以作为响应结果返回。在本篇博客中,我们将探讨几种常见的数组传递方式,展示如何在C#与C之间进行有效的数据交换。我们将主要介绍以下…...
【C++复习】第一弹-基础性语法
前言 学习了C语法这么久了,我其实觉得,我们学习一门语言应该更加注重使用性,对于语法的细节可以通过具体的项目去重新造轮子的时候再去抠细节,也就是说你得学会先走,在去想我们如何走的,身体的哪些肌肉在发…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...