直接group by经纬度会导致热力图失真,因高精度浮点数使每组仅1个点;需空间离散化:用round()粗略网格化、floor()+步长精准对齐,或postgis的st_snaptogrid实现球面投影下的可靠网格化。

为什么直接 GROUP BY 经纬度会导致热力图失真
原始经纬度是高精度浮点数(如 39.9042、116.4074),直接 GROUP BY lat, lng 几乎每个点都唯一,分组后每组计数为 1,根本无法形成“热区”。热力图需要把地理上邻近的点聚合到同一格子中——本质是空间离散化,不是数值等值分组。
用 ROUND() 对经纬度做网格化截断(最简可行方案)
适用于中小数据量、对精度要求不高的快速分析。核心思路是把经纬度缩放到指定精度后取整,让落在同一网格内的坐标映射为相同整数对。
-
ROUND(lat * 100)和ROUND(lng * 100)可实现约 ±500 米网格(赤道附近),乘数越大网格越细 - 注意:
ROUND()在不同数据库行为略有差异(如 PostgreSQL 默认四舍五入,SQL Server 需显式指定ROUND(x, 0)) - 必须同时处理
lat和lng,否则东西向/南北向网格错位 - 示例 SQL:
SELECT ROUND(lat * 100) / 100.0 AS grid_lat, ROUND(lng * 100) / 100.0 AS grid_lng, COUNT(*) AS cnt FROM locations GROUP BY ROUND(lat * 100), ROUND(lng * 100) ORDER BY cnt DESC LIMIT 100;
用 FLOOR() + 固定步长实现可控网格大小(推荐)
ROUND() 的网格中心在 0.005、0.015 等位置,不利于对齐和后续空间索引;FLOOR() 可让每个网格左下角严格对齐步长整数倍,更可控且便于与 GIS 工具对接。
- 步长 0.01° ≈ 1.1 km(赤道),0.001° ≈ 110 m;实际步长需按业务区域纬度微调(高纬度经度距离压缩)
- 正确写法:
FLOOR(lat / 0.01) * 0.01,不是FLOOR(lat * 100) / 100(后者在负数时结果不同) - 避免负数陷阱:某些数据库中
FLOOR(-1.2)= -2,而你需要的是 -1.2 所在网格的左下角,即-1.21→-1.21 / 0.01 = -121.0 → FLOOR = -121 → * 0.01 = -1.21,逻辑成立
PostgreSQL + PostGIS 中用 ST_SnapToGrid 最可靠
如果已启用 PostGIS,这是真正意义上的空间网格化:它考虑球面投影、支持自定义 SRID,并能输出几何对象供可视化直连。
-
ST_SnapToGrid(geom, 0.01)将点几何吸附到 0.01° 网格,返回新的POINT - 必须先用
ST_SetSRID(ST_MakePoint(lng, lat), 4326)构造带坐标的几何体(注意:PostGIS 参数顺序是lng, lat) - 聚合后可直接导出 GeoJSON,或用
ST_AsText()提取 snapped 坐标 - 性能提示:对大表务必在
lat/lng上建 B-tree 索引,PostGIS 网格函数本身不走索引
真实热力图分组的关键不在“怎么写 GROUP BY”,而在“你选的网格大小是否匹配数据密度和展示尺度”——同一份数据,城市级热力用 0.005°,省级就得用 0.05°,差一个数量级,热区就全散了。











