首页 / 实战手册 / 坐标与投影
纬度 89° 传给墨卡托,Y 值变成 Infinity 后瓦片编号是 NaN
先看结论
南极科考站的点一加进去,整个图层就不渲染了,控制台却一声不响。
现象
一份含南极站点的数据,纬度 -89.0。加载后:
- Leaflet 中该点消失,其它点正常。
- OpenLayers 中图层整体空白,
source.getFeatures().length仍然是正确数量。 - 自研瓦片脚本打印出的瓦片索引是
NaN,请求 URL 里出现undefined。
原因
Web 墨卡托的投影公式是 y = R·ln(tan(π/4 + φ/2))。
当 φ 趋近 ±90° 时,tan 趋于无穷,ln 也趋于无穷。
EPSG:3857 把定义范围截断在 ±85.0511287798°,
此时 y 恰好等于 ±20037508.34,整个投影面成为一个正方形。
超出这个纬度,y 会大于 20037508 甚至变成 Infinity,
再除以瓦片跨度和 Math.pow(2, z) 就得到 NaN,
后续所有整数运算都失效。
| 纬度 | EPSG:3857 的 y | 结果 |
|---|---|---|
| 0° | 0 | 正常 |
| 85.0511287798° | 20037508.34 | 边界 |
| 89° | 约 3.0e7 | 超出投影面,瓦片索引越界 |
| 90° | Infinity | NaN |
排查过程
- 在控制台对可疑点执行
L.CRS.EPSG3857.latLngToPoint(L.latLng(-89, 0), 0),看 y 是否超过 20037508。 - 遍历数据 bbox,找出
|lat| > 85.05的记录,通常只有极少数。 - 检查瓦片计算函数里有没有对
Math.floor的结果做Number.isFinite判断。 - 把该点单独移除后重试,若图层恢复正常即可确认。
const p = L.CRS.EPSG3857.latLngToPoint(L.latLng(-89, 0), 0);
console.log(p.y); // 3.0e7 左右,已超出 20037508
console.log(Number.isFinite(p.y)); // true,但越界
const bad = L.CRS.EPSG3857.latLngToPoint(L.latLng(-90, 0), 0);
console.log(bad.y); // Infinity
console.log(Math.floor(bad.y / 256)); // NaN
解决方案
在数据进入投影之前钳制纬度,而不是在渲染后补救。 确实需要展示极区时,改用极射赤面投影,不要强行塞进 3857。
const MAX_LAT = 85.0511287798;
function clampLat(lat) {
return Math.max(-MAX_LAT, Math.min(MAX_LAT, lat));
}
function toMercator(lng, lat) {
const phi = clampLat(lat) * Math.PI / 180;
const x = lng * 20037508.34 / 180;
let y = Math.log(Math.tan(Math.PI / 4 + phi / 2)) * 6378137;
y = Math.max(-20037508.34, Math.min(20037508.34, y));
return [x, y];
}
// 瓦片索引加一层兜底
function tileIndex(lng, lat, z) {
const [x, y] = toMercator(lng, lat);
const n = Math.pow(2, z);
const tx = Math.floor((x + 20037508.34) / (2 * 20037508.34) * n);
const ty = Math.floor((20037508.34 - y) / (2 * 20037508.34) * n);
if (!Number.isFinite(tx) || !Number.isFinite(ty)) return null;
return [tx, ty];
}
验证
钳制后 -89 的点会被放到 -85.0511287798 的投影边界上,
y 等于 -20037508.34,瓦片索引是整数且不越界。
console.assert(toMercator(0, -89)[1] === -20037508.34, 'y 未钳制');
console.assert(tileIndex(0, -89, 4) !== null, '索引仍为 NaN');
console.log(tileIndex(0, -89, 4)); // [8, 15]
适用版本
| 库 | 版本 | 钳制行为 |
|---|---|---|
| Leaflet | 1.x | 内部会钳制到 ±85.0511,但自算索引不会 |
| OpenLayers | 6.x ~ 9.x | 超出投影范围时要素被裁掉 |
| proj4js | 2.9 及以上 | 不钳制,返回 Infinity |
| Mapbox GL JS | 2.x / 3.x | 相机纬度被限制在 ±85 |
经纬志(2026)。《纬度 89° 传给墨卡托,Y 值变成 Infinity 后瓦片编号是 NaN》。https://www.jingweizhi.com/cookbook/mercator-lat-limit.html(访问日期 2026-11-03)。