如何将Leaflet的L.CRS.SIMPLE坐标转换为L.CRS.Earth及谷歌地图坐标
嘿,这个需求我之前帮朋友处理过类似的游戏地图适配,其实核心就是理清两个坐标系的映射关系——毕竟Leaflet的L.CRS.SIMPLE是极简的像素/瓦片索引坐标系,而谷歌地图用的是Web Mercator(EPSG:3857),咱们一步步来解决:
先搞懂两个坐标系的核心差异
- L.CRS.SIMPLE:左上角是原点
(0,0),x轴向右递增,y轴向下递增,坐标直接和你的自定义瓦片(zoom/x/y.png)的索引、像素位置绑定,完全和真实地理无关。 - 谷歌地图(Web Mercator):基于真实地球的投影,坐标是经纬度,背后用米为单位的平面坐标计算,瓦片切片规则是全球范围按zoom级别拆分,每个瓦片256x256像素。
因为你的地图是虚构游戏地图,咱们需要给它在Web Mercator坐标系里“找一块地盘”,然后把Simple坐标映射过去。
具体转换步骤(分两种场景)
场景1:你的自定义瓦片是完全独立的切片(和Web Mercator规则无关)
这种情况需要先给游戏地图设定一个在谷歌地图里的虚拟边界,再做坐标映射:
确定基础参数
- 先整理Leaflet里的关键数据:瓦片大小
tileSize(默认256)、游戏地图的总宽度(像素)totalWidth = x方向瓦片总数 * tileSize、总高度(像素)totalHeight = y方向瓦片总数 * tileSize。 - 给游戏地图选一个谷歌地图里的占位区域,比如选西南角经纬度
(lat_sw, lng_sw)和东北角(lat_ne, lng_ne)(建议选无人区,比如南太平洋海域,避免和真实地标重叠)。
- 先整理Leaflet里的关键数据:瓦片大小
把Simple坐标转成全局像素坐标
如果你的Marker坐标是瓦片索引+像素偏移(比如(tx, ty, px, py),代表zoom级别下第tx列、ty行瓦片里的(px,py)像素),先转成全局像素:const globalPxX = tx * tileSize + px; const globalPxY = ty * tileSize + py;如果已经是全局像素坐标(比如直接在Leaflet里用
map.latLngToContainerPoint得到的),直接用即可。映射到Web Mercator的米坐标
先把你选的经纬度边界转成Web Mercator的米坐标:// 经纬度转Web Mercator米坐标 function latLngToMercator(lat, lng) { const earthRadius = 6378137; // Web Mercator的地球半径 const x = lng * Math.PI * earthRadius / 180; const y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) * earthRadius; return [x, y]; } const [mercSwX, mercSwY] = latLngToMercator(lat_sw, lng_sw); const [mercNeX, mercNeY] = latLngToMercator(lat_ne, lng_ne);然后计算像素到米的缩放比例(注意y轴方向反转,因为Web Mercator的y向上递增,Simple的y向下):
const scaleX = (mercNeX - mercSwX) / totalWidth; const scaleY = (mercSwY - mercNeY) / totalHeight; // 负比例实现y轴反转转换得到Marker的Web Mercator米坐标:
const markerMercX = mercSwX + globalPxX * scaleX; const markerMercY = mercSwY + globalPxY * scaleY;转成谷歌地图可用的经纬度
把米坐标转回经纬度:// Web Mercator米坐标转经纬度 function mercatorToLatLng(x, y) { const earthRadius = 6378137; const lng = x * 180 / (Math.PI * earthRadius); const lat = 2 * Math.atan(Math.exp(y / earthRadius)) * 180 / Math.PI - 90; return [lat, lng]; } const [markerLat, markerLng] = mercatorToLatLng(markerMercX, markerMercY);这个
(markerLat, markerLng)就是谷歌地图可以直接用的坐标了。
场景2:你的自定义瓦片是按Web Mercator规则生成的(只是Leaflet用了Simple加载)
这种情况更简单,因为你的瓦片x/y/zoom和谷歌地图的瓦片规则完全匹配,只需要把Simple的坐标对应到瓦片索引+像素,再转经纬度:
比如你的Marker在Leaflet里的坐标是(tx, ty, px, py)(zoom级别下的瓦片索引+像素偏移),直接用这个函数转:
function tilePixelToGoogleLatLng(tx, ty, px, py, zoom) { const tileCount = Math.pow(2, zoom); // 计算瓦片左上角的经度,再加上像素偏移的比例 const lng = (tx / tileCount) * 360 - 180 + (px / 256) * (360 / tileCount); // 计算瓦片左上角的纬度,再调整像素偏移(注意y轴反转) const yPixelRatio = (ty + py / 256) / tileCount; const latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * yPixelRatio))); const lat = latRad * 180 / Math.PI; return [lat, lng]; }
关键注意事项
- 测试校准:先拿一个位置明确的Marker做测试,转换后在谷歌地图里定位,看是否和瓦片上的位置对齐,调整边界参数或缩放比例直到完全匹配。
- zoom级别对齐:确保Leaflet和谷歌地图的zoom级别对应,两者的zoom规则一致(0级是全局,每级缩放2倍)。
- 瓦片加载适配:在谷歌地图里加载自定义瓦片时,要确保瓦片的URL规则和谷歌的瓦片请求格式匹配,或者用谷歌地图的自定义瓦片图层API指定你的瓦片地址模板。
内容的提问来源于stack exchange,提问作者Alexis

