st_geohash是经纬度栅格化的最优方案,需显式截断字符串控制精度;避免round经纬度导致地理失真;须过滤无效坐标并校验srid;大表应建生成列或预聚合提升性能。

用 ST_GeoHash 快速实现经纬度栅格化分组
PostGIS 提供的 ST_GeoHash 是最直接的方案,它把经纬度转成可截断的字符串编码,长度越短,栅格越大(如长度 4 对应约 ±2.5km 误差,长度 6 约 ±0.02km)。注意它默认返回 12 位,必须显式截断才能控制精度:
SELECT SUBSTR(ST_GeoHash(geom, 6), 1, 6) AS geohash6, COUNT(*) AS cnt FROM foot_traffic WHERE geom IS NOT NULL GROUP BY SUBSTR(ST_GeoHash(geom, 6), 1, 6);
别直接用 ST_GeoHash(geom, 6) 的原生结果——PostGIS 在某些版本里会因坐标系或边界问题补空格或换行符,导致分组错乱;SUBSTR(..., 1, 6) 强制截取更安全。
避免用 ROUND(longitude, n) 手动四舍五入经纬度
手动对 longitude 和 latitude 分别 ROUND 再拼接,看似简单,但会严重扭曲地理形状:高纬度地区经度 1° 距离远小于赤道,同样精度下栅格实际面积差异可达 3 倍以上。更糟的是,ROUND 会产生“阶梯状”边界,跨纬度带时出现大量细碎、不连续的栅格块。
如果非得用数值法(比如没有 PostGIS 的 MySQL 5.7),至少改用等距投影后切分:
- 先用
ST_Transform(geom, 3857)投影到 Web Mercator(单位米) - 再按固定米数做整除:
FLOOR(ST_X(geom_3857)/1000)和FLOOR(ST_Y(geom_3857)/1000) - 最后拼成复合键,如
CONCAT(FLOOR(x/1000), '_', FLOOR(y/1000))
聚合前务必过滤无效坐标和坐标系不一致数据
真实客流数据常混有 0,0、NULL、999999 占位符,或 GPS 坐标(WGS84 / EPSG:4326)与底图坐标系(如 GCJ-02 或 Web Mercator)不匹配。直接聚合会导致大量点堆在非洲几内亚湾(0,0)或中国东部海域(GCJ-02 偏移未纠偏)。
检查和清理建议:
- 用
ST_IsValid(geom)排除几何异常 - 加范围约束:
WHERE longitude BETWEEN -180 AND 180 AND latitude BETWEEN -90 AND 90 - 确认 SRID:
ST_SRID(geom)应为4326;若不是,用ST_SetSRID(geom, 4326)显式声明 - GCJ-02 坐标需先转 WGS84(不能靠
ST_Transform自动转换,需专用纠偏函数)
大表聚合时性能卡在 ST_GeoHash 计算上怎么办
ST_GeoHash 是计算密集型操作,对千万级客流记录全表实时计算会明显拖慢。两个实用解法:
- 建生成列(PostgreSQL 12+):
ALTER TABLE foot_traffic ADD COLUMN geohash6 TEXT GENERATED ALWAYS AS (SUBSTR(ST_GeoHash(geom, 6), 1, 6)) STORED;,再给该列建索引 - 预计算并分区:按天/小时把客流写入带
geohash6字段的汇总表,查询只扫当天分区
别依赖函数索引(如 CREATE INDEX ON t ((ST_GeoHash(geom, 6)))),PostgreSQL 对表达式索引的统计信息支持弱,JOIN 或 WHERE 中混合使用时优化器容易误判行数。
Geohash 截断长度选 5 还是 6,不能只看理论精度——得实测你业务场景下的热力分布粒度。同一城市商圈,周末下午可能需要 6 位(百米级)才看清门店排队差异;而分析跨省通勤,则 4 位(±50km)更合理。实际中,经常要同时存 5 和 6 两列,按需查。











