GeoJSON 没写坐标系,3857 的数据被当成经纬度
文件能正常解析,features.length 也对,就是地图上什么都看不见。
现象
从内部接口导出的 districts.geojson 有 312 个要素,
在 QGIS 里打开提示「图层范围无效」,在网页里用 L.geoJSON(data).addTo(map)
既不报错也不显示,调用 map.fitBounds(layer.getBounds())
得到 LatLngBounds(20037508, 20037508) 这种越界值。
打开文件看第一个坐标:[13522390.4, 3669871.9],是米,不是度。
原因
GeoJSON 的现行规范 RFC 7946 取消了 crs 成员,规定坐标必须是 WGS84 经纬度,
顺序是 [经度, 纬度]。所有主流解析器因此默认按 4326 处理,
见到 13522390 这个数字就当成 13522390°,直接丢到视野之外。
旧版 GeoJSON 2008 规范允许 "crs": {"type":"name","properties":{"name":"EPSG:3857"}},
但现在 Leaflet 1.x 和 Mapbox GL 都不读这个字段,写了也没用。
服务端用 ST_AsGeoJSON(geom, 15) 直接从 3857 几何导出时,就会产生这种文件。
能解析不等于坐标系对。GeoJSON 是「无类型」的坐标数组, 解析器没有能力判断单位,只能相信调用方。
排查过程
- 读第一个坐标对,判断数值量级:绝对值大于 180 就不是经纬度。
- 看文件顶层有没有
crs成员。有也不代表解析器会用它。 - 算一遍要素的 bbox。
x在7.3e6~1.5e7、y在2.4e6~6.1e6就是中国区域的 3857。 - 回到导出环节,确认 SQL 里有没有做
ST_Transform(geom, 4326)。
// 快速判断一份 GeoJSON 的坐标系
const c = fc.features[0].geometry.coordinates;
const is4326 = Math.abs(c[0]) <= 180 && Math.abs(c[1]) <= 90;
console.log(is4326 ? '经纬度' : '投影坐标,量级 ' + c[0].toExponential(2));
// 13522390.4 -> "投影坐标,量级 1.35e+7"
解决方案
根治在导出端:GeoJSON 一律转成 4326 再序列化。
-- PostGIS:导出前必须转换
SELECT jsonb_build_object(
'type', 'FeatureCollection',
'features', jsonb_agg(ST_AsGeoJSON(t, 15)::jsonb)
)
FROM (
SELECT id, name, ST_Transform(geom, 4326) AS geom FROM districts
) AS t;
导出端暂时改不了时,前端可以兜底转换。注意要在 L.geoJSON 之前完成,
不要依赖 coordsToLatLng 去猜。
// 前端兜底:判定为米制后整份转换
const projected = Math.abs(fc.features[0].geometry.coordinates[0]) > 180;
if (projected) fc = reproject(fc, 'EPSG:3857', 'EPSG:4326');
// Leaflet 也支持自定义 coordsToLatLng,但只适合单一坐标系
L.geoJSON(fc, {
coordsToLatLng: coords => L.CRS.EPSG3857.unproject(L.point(coords[0], coords[1]))
}).addTo(map);
验证
转换后首个坐标应为 [121.4737, 31.2304] 这种量级;
layer.getBounds() 返回的经纬度都在合法范围内,
地图缩放到 10 级能看到完整的行政区轮廓。
const b = L.geoJSON(fc).getBounds();
console.assert(b.isValid(), '范围仍无效');
console.assert(b.getNorth() <= 90 && b.getEast() <= 180, '仍在投影坐标');
适用版本
| 工具 | 版本 | 说明 |
|---|---|---|
| RFC 7946 | 2016 起 | 取消 crs,只允许 WGS84 |
| Leaflet | 1.0 及以上 | 不读 crs 成员,默认 4326 |
| Mapbox GL JS | 2.x / 3.x | 只接受 4326,其它坐标必须预处理 |
| PostGIS | 3.x | ST_AsGeoJSON 不改变坐标系 |
经纬志(2026)。《GeoJSON 没写坐标系,3857 的数据被当成经纬度》。https://www.jingweizhi.com/cookbook/geojson-no-crs.html(访问日期 2026-11-03)。