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

纬度 89° 传给墨卡托,Y 值变成 Infinity 后瓦片编号是 NaN

先看结论

南极科考站的点一加进去,整个图层就不渲染了,控制台却一声不响。

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

现象

一份含南极站点的数据,纬度 -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°InfinityNaN

排查过程

  1. 在控制台对可疑点执行 L.CRS.EPSG3857.latLngToPoint(L.latLng(-89, 0), 0),看 y 是否超过 20037508。
  2. 遍历数据 bbox,找出 |lat| > 85.05 的记录,通常只有极少数。
  3. 检查瓦片计算函数里有没有对 Math.floor 的结果做 Number.isFinite 判断。
  4. 把该点单独移除后重试,若图层恢复正常即可确认。
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]

适用版本

库版本钳制行为
Leaflet1.x内部会钳制到 ±85.0511,但自算索引不会
OpenLayers6.x ~ 9.x超出投影范围时要素被裁掉
proj4js2.9 及以上不钳制,返回 Infinity
Mapbox GL JS2.x / 3.x相机纬度被限制在 ±85
引用本文

经纬志(2026)。《纬度 89° 传给墨卡托,Y 值变成 Infinity 后瓦片编号是 NaN》。https://www.jingweizhi.com/cookbook/mercator-lat-limit.html(访问日期 2026-11-03)。