经纬志
首页 / 实战手册 / 坐标与投影

经纬度只存两位小数,热力图整体错开 1.1 公里

先看结论

数据库字段是 decimal(9,2),存进去的点全被吸到 0.01 度的网格上。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

前端录入 116.397428, 39.909230,写入数据库后读出来是 116.40, 39.91。单点在地图上看起来只差一点点, 但同一商圈的两三百个点被压到同一格,热力图出现规则的方格状色块, 整体位置与真实路网错开约 1.1 公里。

原因

decimal(9,2) 表示总共 9 位、小数 2 位,步长是 0.01。 1 度纬度约 111.3 公里,0.01 度约 1.11 公里; 经度在北纬 40° 处约 85.3 公里/度,0.01 度约 0.85 公里。 四舍五入后最坏误差是半个步长,即 0.55 公里。

小数位纬度步长实地步长(北纬 40°)适用场景
20.01°约 1.1 km省级示意
40.0001°约 11 m街区级
60.000001°约 0.11 mPOI、导航
70.0000001°约 1.1 cm测绘成果

排查过程

  1. 执行 SHOW CREATE TABLE 或 \d+ poi,看经纬度列的实际类型。
  2. 取一个已知点,写入再读回,比较差值的量级。差值恰好是 0.005 的整数倍,就是精度截断。
  3. 用 COUNT(DISTINCT lon, lat) 与总行数比较,若远小于总行数,说明大量点被量化到同一格。
  4. 确认 ORM 层有没有把 double 映射成 numeric(9,2)。
-- 量化痕迹:不同坐标数远少于总点数
SELECT COUNT(*) AS total, COUNT(DISTINCT lon, lat) AS distinct_pts
FROM poi;
-- total 12840 | distinct_pts 913   -> 大量点被压到同一格

解决方案

经纬度用双精度浮点或 7 位小数的 numeric。改字段后必须从原始数据重导, 被截断的值无法还原。

-- MySQL 8.0
ALTER TABLE poi
  MODIFY lon DECIMAL(10,7) NOT NULL,
  MODIFY lat DECIMAL(10,7) NOT NULL;

-- PostgreSQL + PostGIS:直接存几何,精度由 double 保证
ALTER TABLE poi ADD COLUMN geom geometry(Point, 4326);
UPDATE poi SET geom = ST_SetSRID(ST_MakePoint(lon, lat), 4326);
CREATE INDEX poi_geom_idx ON poi USING GIST (geom);

-- 新表建议
CREATE TABLE poi (
  id     bigserial PRIMARY KEY,
  name   text,
  lon    double precision NOT NULL CHECK (lon BETWEEN -180 AND 180),
  lat    double precision NOT NULL CHECK (lat BETWEEN -90 AND 90)
);

验证

写入 116.397428, 39.909230 后读回应完全一致(浮点允许 1e-7 误差); 热力图不再出现方格,点密度分布与卫星底图上的建筑轮廓吻合。

SELECT lon, lat FROM poi WHERE id = 1;
-- 116.397428 | 39.909230

SELECT count(*) FROM poi
WHERE lon = round(lon::numeric, 2) AND lat = round(lat::numeric, 2);
-- 应为 0,说明不再有量化痕迹

适用版本

数据库版本建议类型
MySQL8.0 及以上DECIMAL(10,7) 或 DOUBLE
PostgreSQL14 及以上double precision + PostGIS geometry(Point,4326)
SQL Server2019 及以上geography 类型
引用本文

经纬志(2026)。《经纬度只存两位小数,热力图整体错开 1.1 公里》。https://www.jingweizhi.com/cookbook/coordinate-precision.html(访问日期 2026-11-03)。