本文字数:9099字
预计阅读时间:25分钟
x = \lfloor \frac{256}{2\pi} * 2^{zoom\_level}(lng + \pi) \rfloor * pixels
\\
y = \lfloor \frac{256}{2\pi} * 2^{zoom\_level}(\pi - \ln[\tan(\frac{\pi}{4} + \frac{lat}{2})]) * pixels
X_{proj} = lng * \frac{2 * \pi * R}{2.0}
\\
Y_{proj} = R * log(tan(\frac{(90+lat)*\pi}{360}))
# 地理坐标投影
def LatLongToMeterXY(lng, lat) {
# 地球的周长的一半 20037508.342789244 单位米
circumferenceHalf = math.pi * 2 * 6378137 / 2.0
meterX = lng * circumferenceHalf / 180
temp = math.log(math.tan((90 + lat) * (math.pi / 360.0))) / (math.pi / 180.0)
meterY = circumferenceHalf * temp / 180
return meterX,meterY
# [-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]
X_{pixel} = \frac{(X_{proj}+\pi*R)*tile_size*z^{zoom}}{2*\pi*R}
\\
Y_{pixel} = \frac{(Y_{proj}+\pi*R)*tile_size*z^{zoom}}{2*\pi*R}
# 投影坐标转像素坐标
def metersToPixelXY(meterX, meterY, zoom, tileSize=256) {
# 地球的周长的一半 20037508.342789244 单位米
circumferenceHalf = math.pi * 2 * 6378137 / 2.0
# 米/每像素
resolution = math.pi * 2 * 6378137 / (tileSize * math.pow(2, zoom))
pixelX = (meterX + circumferenceHalf) / resolution
pixelY = (meterY + circumferenceHalf) / resolution
return pixelX,pixelY
X_{pixel} = \frac{lng+180}{360}*2^z*256 \mod 256
\\
Y_{pixel} = (1-\frac{\ln(\tan(\frac{lat*\pi}{180}) + \sec(\frac{lat * \pi}{180}))}{2* \pi} *2^z*256\mod256
# 经纬度转像素
def lnglatToPixel(lng,lat,zoom):
pixelX=round((lng+180)/360*math.pow(2,zoom)*256%256)
pixelY=round((1-math.log(math.tan(math.radians(lat))+1/math.cos(math.radians(lat)))/(2*math.pi))*math.pow(2,zoom)*256%256)
return pixelX,pixelY
X_{tile} = \lfloor \frac{X_{pixel}}{tile\_size} \rfloor
\\
Y_{tile} = \lfloor \frac{Y_{pixel}}{tile\_size} \rfloor
def pixelXYToTileXY(pixelX, pixelY, tileSize=256) {
tileX = Math.floor(pixelX / tileSize)
tileY = Math.floor(pixelY / tileSize)
return tileX,tileY
X_{pixel} = \frac{lng+180}{360}*2^z*256 \mod 256
\\
Y_{pixel} = (1-\frac{\ln(\tan(\frac{lat*\pi}{180}) + \sec(\frac{lat * \pi}{180}))}{2* \pi} *2^z*256\mod256
# 经纬度转瓦片
def lnglatToTile(lng,lat,zoom):
tileX=int((lng+180)/360*math.pow(2,zoom))
tileY=int((1-math.asinh(math.tan(math.radians(lat)))/math.pi)*math.pow(2,zoom-1))
return tileX,tileY
lng = \frac{X_{tile}}{2^z}*360-180
\\
lat = arctan(sinh(\pi*(1-\frac{2*Y_{tile}}{2^z})))*\frac{180}{\pi}
# 瓦片转经纬度
def tileToLnglat(tileX,tileY,zoom):
lng=tileX/math.pow(2,zoom)*360-180
lat=math.degrees(math.atan(math.sinh(math.pi*(1-2*tileY/math.pow(2,zoom)))))
return lng,lat
lng = \frac{X_{tile}+\frac{X_{pixel}}{256}}{2^z}*360-180
\\
lat = arctan(sinh(\pi*(1-\frac{2*(Y_{tile}+{\frac{Y_{pixel}}{256}})}{2^z})))*\frac{180}{\pi}
# 瓦片坐标的像素坐标转经纬度
def pixelToLnglat(tileX,tileY,pixelX,pixelY,level):
lng=(tileX+pixelX/256)/math.pow(2,level)*360-180
lat=math.degrees(math.atan(math.sinh(math.pi-2*math.pi*(tileY+pixelY/256)/math.pow(2,level))))
return lng,lat
https://{sub_domain}.tile.openstreetmap.org/{z}/{x}/{y}.png
<html>
<head>
<title>瓦片原理-OSM</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
<script>
var map = new L.Map('map', { center: [40, 116], zoom: 17});
var tiles = new L.GridLayer();
tiles.createTile = function(coords) {
console.log('coords:');
console.log(coords);
// 创建DOM装载瓦片
var tile = L.DomUtil.create('canvas', 'leaflet-tile');
var ctx = tile.getContext('2d');
// 获取瓦片尺寸
var size = this.getTileSize();
console.log('size:');
console.log(size);
tile.width = size.x;
tile.height = size.y;
// 计算瓦片左上角的像素坐标
var nwPoint = coords.scaleBy(size);
// 计算瓦片左上角的地理坐标
var nw = map.unproject(nwPoint, coords.z);
// 计算瓦片右下角的像素坐标
seCoords = new L.Point(coords.x+1,coords.y+1);
seCoords.z = coords.z;
console.log('seCoords:');
console.log(seCoords);
// 计算瓦片右下角的地理坐标
var sePoint = seCoords.scaleBy(size);
var se = map.unproject(sePoint, coords.z);
// 加载瓦片图像
var img = new Image();
var subdomains = ['a', 'b', 'c'];
var index = Math.abs(coords.x + coords.y) % subdomains.length;
img.src = 'http://'+subdomains[index]+'.tile.openstreetmap.org/'+coords.z+'/'+coords.x+'/'+coords.y+'.png';
console.log(img);
ctx.drawImage(img,0,0,size.x,size.y);
console.log(ctx);
// 标识对应的瓦片信息
// 背景颜色
ctx.fillStyle = 'white';
// 背景尺寸
ctx.fillRect(0, 0, size.x, 90);
// 字体颜色
ctx.fillStyle = 'black';
ctx.fillText('x: ' + coords.x + ', y: ' + coords.y + ', zoom level: ' + coords.z, 20, 20);
ctx.fillText('minlng: ' + nw.lng + ', minlat: ' + se.lat, 20, 40);
ctx.fillText('maxlng: ' + se.lng + ', matlat: ' + nw.lat, 20, 60);
var dlng = se.lng - nw.lng;
var dlat = nw.lat - se.lat;
ctx.fillText('dlng: ' + dlng + ', dlat: ' + dlat, 20, 80);
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(size.x-1, 0);
ctx.lineTo(size.x-1, size.y-1);
ctx.lineTo(0, size.y-1);
ctx.closePath();
ctx.stroke();
return tile;
};
// 加载OSM瓦片代码
// L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
// attribution: 'Map data © <a href="http://www.osm.org">OpenStreetMap</a>'
// }).addTo(map);
tiles.addTo(map);
</script>
</html>