基于 Elasticsearch 实现地图点聚合

在地图类应用中,当需要展示大量地理兴趣点时,直接将所有点渲染在地图上会导致视觉混乱,影响用户体验。为此,我基于 Elasticsearch 提供的 geotile_gridgeo_bounding_box 查询能力,实现了一套高效的 POI 聚合展示方案。

🧩 问题背景:POI 数量巨大,直接渲染效率低

在地图类应用中,我们常常需要展示大量的地理兴趣点(Point of Interest, POI),例如商圈、门店、用户位置等。然而,当 POI 数量达到数万甚至数十万级别时,若将所有点一次性加载并渲染在地图上,不仅会导致页面卡顿、交互延迟,还会因标记重叠严重而降低用户体验。更严重的问题包括:

  • 前端性能瓶颈:大量 DOM 节点或图形元素导致浏览器渲染压力剧增;
  • 视觉混乱:点与点之间相互遮挡,信息难以辨识;
  • 无差别展示:无法根据地图缩放层级动态调整展示粒度;
    在这里插入图片描述
    因此,我们需要一种既能减少前端渲染压力,又能保留关键信息的地图聚合方案。

🛠️ 技术方案:基于 Elasticsearch 的 geotile_grid 聚合机制

本方案基于 Elasticsearch 的 geotile_gridgeo_bounding_box 查询能力,结合球面几何算法,实现了poi高效聚合与展示。整个流程如下:

  1. 获取地图视口范围:通过左上角和右下角坐标限定查询范围;
  2. 映射地图缩放层级到 precision,根据当前地图 zoom level 动态计算合适的 geotile_grid 聚合精度;
  3. 提取聚合点并计算代表点,对每个 tile 内最多 100 个点进行球面几何中位数计算,得出一个最具代表性的点用于展示。

流程图:
在这里插入图片描述
效果展示:

Screen-2025-07-03-163545

📌 地图聚合的核心:geotile_grid

在本方案中,我们使用了 Elasticsearch 的 geotile_grid 聚合方式来实现地图兴趣点的分区聚合。
geotile_grid 本质上是一种基于 Geohash 编码的空间划分机制。它将整个地图视图划分为多个大小一致的矩形区域(称为 tile),每个 tile 包含落在其范围内的所有 POI。

  • tile 的粒度由 precision 控制:precision 越高,tile 越小,聚合越精细;
  • 结合 zoom level 动态映射 precision:根据当前地图缩放层级,动态设置合适的精度值,使聚合结果与地图展示粒度保持一致;
  • 支持子聚合操作:我们可以在每个 tile 中嵌套 top_hits 聚合,获取最多 100 个原始点用于后续中心点计算;

通过这种方式,我们可以:

  • 高效筛选出当前地图视口内的所有 tile;
  • 快速获取每个 tile 内的 POI 数据;
  • 为每个 tile 计算出最具代表性的“中心点”,用于前端展示;
  • 这不仅显著提升了后端查询效率,也为前端提供了结构清晰、层次分明的地图聚合展示能力。

🧑‍💻 示例代码片段

核心代码:基于 geotile_grid 实现地图点聚合

// 1. 构建筛选条件(仅保留地理范围查询)
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.geoBoundingBoxQuery("latLng").setCorners(new GeoPoint(topLeftLat, topLeftLon), new GeoPoint(bottomRightLat, bottomRightLon)));// 2. 构建 geotile_grid 聚合
GeoGridAggregationBuilder geoTileGridAgg = AggregationBuilders.geotileGrid("grid_agg").field("latLng").precision(15); // 可根据缩放级别动态设置 precision// 3. 添加子聚合 top_hits(获取每个 tile 内的 latLng 坐标)
TopHitsAggregationBuilder topHitsAgg = AggregationBuilders.topHits("top_hits_agg").fetchSource("latLng", null).size(100);
geoTileGridAgg.subAggregation(topHitsAgg);// 4. 构建最终查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder).withAggregations(geoTileGridAgg).withMaxResults(0) // 不需要返回实际文档.build();// 5. 执行查询并解析聚合结果
SearchHits<ShopESEntity> searchHits = elasticsearchRestTemplate.search(searchQuery, ShopESEntity.class);// 6. 解析聚合结果并生成展示点
List<DisplayPointVO> displayPoints = new ArrayList<>();
if (searchHits.hasAggregations()) {List<? extends MultiBucketsAggregation.Bucket> buckets = getBuckets(searchHits.getAggregations(), "grid_agg");for (MultiBucketsAggregation.Bucket bucket : buckets) {List<MapUtils.Poi> poiList = getPoiList(bucket); // 获取该 tile 内最多 100 个点MapUtils.Poi meanPoi = MapUtils.computeSphericalMean(poiList); // 计算球面几何中位点if (meanPoi != null) {DisplayPointVO vo = new DisplayPointVO();vo.setCount(bucket.getDocCount());vo.setLat(meanPoi.getLat());vo.setLng(meanPoi.getLng());displayPoints.add(vo);}}
}

最终DSL如下:

{"size": 0,"aggs": {"poi_agg": {"geotile_grid": {"field": "latLng","precision": 29},"aggs": {"points": {"top_hits": {"size": 100,"_source": {"includes": ["latLng"]}}}}}},"query": {"geo_bounding_box": {"latLng": {"top_left": {"lat": 30.293813,"lon": 120.10432},"bottom_right": {"lat": 30.167403,"lon": 120.217002}}}}
}

球面几何中位数算法(简化版)

public static class MapUtils {public static class Poi {private final double lat;private final double lon;public Poi(double lat, double lon) {this.lat = lat;this.lon = lon;}public double getLat() { return lat; }public double getLng() { return lon; }}public static Poi computeSphericalMean(List<Poi> poiList) {if (poiList.isEmpty()) return null;double sumX = 0, sumY = 0, sumZ = 0;for (Poi p : poiList) {double latRad = Math.toRadians(p.lat);double lonRad = Math.toRadians(p.lon);sumX += Math.cos(latRad) * Math.cos(lonRad);sumY += Math.cos(latRad) * Math.sin(lonRad);sumZ += Math.sin(latRad);}int n = poiList.size();double avgX = sumX / n;double avgY = sumY / n;double avgZ = sumZ / n;double hyp = Math.sqrt(avgX * avgX + avgY * avgY);double latAvg = Math.toDegrees(Math.atan2(avgZ, hyp));double lngAvg = Math.toDegrees(Math.atan2(avgY, avgX));return new Poi(latAvg, lngAvg);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87472.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87472.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Prometheus 】通过 Pushgateway 上报指标数据

Prometheus 是目前最流行的开源监控系统之一&#xff0c;其拉取&#xff08;pull&#xff09;模型非常适合服务发现和静态目标的监控。然而&#xff0c;在某些场景下&#xff0c;例如短生命周期任务、批处理作业或无法暴露 HTTP 接口的服务&#xff0c;传统的拉取方式并不适用。…

服务器 - - QPS与TPS介绍

1、QPS&#xff08;Queries Per Second 每秒查询数&#xff09; 定义&#xff1a;常用于表示每秒的请求次数&#xff0c;衡量接口请求、数据库查询等动作的吞吐量&#xff08;单位时间内处理的数据量&#xff09; 计算&#xff1a;总请求数/请求时间&#xff0c;如&#xff1…

Cot2:思维链提示激发大型语言模型的推理能力

摘要 我们探讨了生成思维链——一系列中间推理步骤——如何显著提升大型语言模型执行复杂推理的能力。特别地&#xff0c;我们展示了在足够大的语言模型中&#xff0c;这种推理能力如何通过一种简单的方法——思维链提示&#xff08;chain-of-thought prompting&#xff09;自…

go交易数据后端

地址 https://gitee.com/EEPPEE_admin/go-stock-line-trading-datahttps://github.com/jerryshell/midas 需求 为了替代rust后端爬虫端: 爬取东方财富数据到index-data目录server端: 项目主要内容 todo 替代https://github.com/jerryshell/midas的前端量化概念性理解扩展: 存储…

灵巧手概览

第一章 灵巧手的技术演进与核心价值 1.1 技术演进的五个阶段 仿生学启蒙阶段&#xff08;1960-1980&#xff09; 1968年斯坦福大学首台3自由度机械夹爪标志机器人操作技术开端&#xff0c;1973年MIT提出"仿生手"概念&#xff0c;但受限于材料和控制技术&#xff0c;…

在设计提示词(Prompt)时,关于信息位置的安排z怎么 结合模型特性和任务目标

在设计提示词(Prompt)时,关于信息位置的安排z怎么 结合模型特性和任务目标 在设计提示词(Prompt)时,关于信息位置的安排确实需要结合模型特性和任务目标。从自注意力机制的原理及应用场景来看,关键信息的位置选择需遵循以下启示,并结合具体场景灵活调整: 一、核心启示…

七、性能优化

目录 1. 如何检测Flutter应用的性能问题&#xff1f;2. 什么是重绘边界&#xff08;Repaint Boundary&#xff09;&#xff1f;3. 如何避免不必要的重建&#xff1f;4. const 构造函数在优化中起什么作用&#xff1f;5. 如何优化长列表的性能&#xff1f;6. 如何减少应用启动时…

Webpack优化详解

Webpack 5提供了一系列工具和功能,可以在本地开发和线上构建过程中进行优化,以提高开发效率和构建性能。 1. 本地开发优化 1.1. 开启模块热替换(HMR) 模块热替换可以在不刷新整个页面的情况下更新模块,提高开发效率。 const webpack = require(webpack);module.export…

latency 对功耗的影响

文章目录 1、Connection Interval(连接间隔) vs. Latency(从机延迟)2、为什么不能完全依赖 Connection Interval?3、什么时候可以不用 Latency?4、如何正确配置?5、结论调节连接间隔(Connection Interval)确实可以直接影响通信频率和功耗,但 Latency(从机延迟)仍然…

10分钟搭建 PHP 开发环境教程

下载、安装 Xserver 下载 php 过程中如果提示需要安装 vc 运行环境&#xff0c;按照引导下载安装即可 安装 nginx 安装 Mysql 支持多个版本同时安装 下载 php 过程中如果提示需要安装 vc 运行环境&#xff0c;按照引导下载安装即可mysql 默认用户名为 root&#xff0c;默认密…

设计模式(六)

备忘录模式&#xff08;Memento Pattern&#xff09;详解 一、核心概念 备忘录模式允许在不破坏封装性的前提下&#xff0c;捕获并保存对象的内部状态&#xff0c;以便后续恢复。该模式通过三个角色实现&#xff1a; 原发器&#xff08;Originator&#xff09;&#xff1a;需…

迪杰斯特拉算法之解决单源最短路径问题

迪杰斯特拉算法 迪杰斯特拉(Dijkstra)算法是典型**最短路径算法**&#xff0c;用于计算一个结点到其它结点的最短路径。它的主要特点是以起始点为中心向外扩展(利用广度优先搜索思想)&#xff0c;直到扩展到终点。迪杰斯特拉(Dijkstra)算法最佳应用-最短路径 战争时期&#xf…

风平浪静、无事发生

2025年7月4日&#xff0c;16~25℃&#xff0c;阴雨紧急不紧急重要1.备考D1.物理备课不重要遇见&#xff1a;风平浪静、无事发生&#xff01;感受或反思&#xff1a;体检的结果收到了&#xff0c;医生建议多吃绿蔬多喝水&#xff01;多运动&#xff0c;少和喝饮料........

QtitanRibbon打造现代办公软件新体验:提升效率的专业界面解决方案

在现代办公环境中&#xff0c;无论是日常公文处理、文档编辑、任务协同还是数据分析&#xff0c;桌面办公软件仍扮演着不可替代的角色。然而&#xff0c;许多传统系统依旧使用菜单繁杂、图标混乱、交互老旧的界面&#xff0c;用户操作效率低、上手慢、满意度差。 QtitanRibbon…

MSPM0G3507学习笔记(一) 重置版:适配逐飞库的ti板环境配置

由于使用逐飞库&#xff0c;很多东西其实都不用配置了&#xff0c;也不需要自己移植空工程了&#xff0c;于是写一个重置版的环境配置教程。 1.下载芯片支持包 MSPM0G3507芯片支持CCS、IAR、KEIL等IDE&#xff0c;选择KEIL作为开发工具&#xff0c;首先安装芯片支持包。 前往…

如何查看自己电脑的显卡信息?

右键单击底部导航栏选择“任务管理器” 点开之后 选择左侧的性能一栏 查看你的显卡的信息

使用Go语言实现智能EXE文件重命名工具

文章目录 使用Go语言实现智能EXE文件重命名工具 &#x1f6e0;️引言工具功能概述核心技术实现Windows版本信息API调用大模型API集成交互式命令行界面 完整工作流程实际应用示例附录完整代码 使用Go语言实现智能EXE文件重命名工具 &#x1f6e0;️ 引言 在日常开发和软件管理…

3.1.1.9 安全基线检查项目九:检查是否设置限制su命令用户组

限制su配置 关于限制su命令检查项&#xff0c;对于大多数的Linux&#xff08;Redhat系列、Debian系列&#xff09;&#xff0c;进行本项检查很简单。只需要检查/etc/pam.d/su中是否配置了&#xff1a; auth required pam_wheel.so use_uid [group用户组名] 有些资料讲说需要有…

【加解密与C】对称加密(四) RC4

RC4算法概述RC4&#xff08;Rivest Cipher 4&#xff09;是由Ron Rivest在1987年设计的流密码算法&#xff0c;广泛应用于SSL/TLS、WEP等协议中。其核心是通过密钥调度算法&#xff08;KSA&#xff09;和伪随机生成算法&#xff08;PRGA&#xff09;生成密钥流&#xff0c;与明…

医科+AI!和鲸支持南京医科大学医学数据挖掘课程实践教学落地

近两年&#xff0c;生物统计学更多地进入了公众视野。作为统计学、医学与计算机科学交叉的前沿学科&#xff0c;伴随测序技术革新与人工智能算法突破&#xff0c;其发展前景也被十分看好。 市场需求的背后是人才需求的爆发与人才培养的挑战。目前&#xff0c;生物统计学专业在国…