问题描述:已知左上角点(lon1,lat1),右下角点(lon2,lat2),以及当前地图的放大倍数zoom,把左上角点到右下角点之间的地图区域保存成图片。
一、基础知识
一般的地图是由一张张瓦片式的图片拼成的,就像装修时铺地砖一样。而且这些瓦片都是正方形的,比较普遍的大小是256*256。当zoom为0时,整个世界的地图就是一张256*256的图片。当zoom为1时,世界地图就被切成了2^1*2^1=4块。以此类推,当zoom为10时,横向和纵向都分了2^10=1024份,也就是总共有1024*1024块了。
瓦片坐标以左上角为(0,0),右边的一块是(1,0),下边的一块是(0,1)。
百度、高德、Google、天地图、OpenStreet等地图提供方都有根据瓦片坐标来获取图片的URL,以天地图为例,其URL形式为:
http://t1.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}
上面的x、y就是瓦片坐标,而z是zoom。http://t1.tianditu.gov.cn/DataServer?T=vec_w&x=13350&y=7111&l=14这个URL能下载到的图片如下图所示(实际URL里还有一些用户信息,需要到地图官网注册哦):
二、地理坐标转瓦片坐标
我们已知点的经纬度,那这个点在哪块瓦片上呢?我们需要把地理坐标转换到瓦片坐标。公式如下:
使用C#代码实现为:
private void GetTile(int zoom, double lon, double lat, out double tileX, out double tileY)
{
tileX = ((lon + 180.0) / 360.0 * (1 << zoom));
tileY = ((1.0 - Math.Log(Math.Tan(lat * Math.PI / 180.0) +
1.0 / Math.Cos(lat * Math.PI / 180.0)) / Math.PI) / 2.0 * (1 << zoom));
}
实际上公式里用到取整,但我们暂时保留小数,后面小数是有作用的。
我们把左上角和右下角的地理坐标转换后,得到瓦片坐标(x1,y1)和(x2,y2),利用这个坐标我们就能把图片都下载下来。
GetTile(zoom, lon1, lat1, out double x1, out double y1);
GetTile(zoom, lon2, lat2, out double x2, out double y2);
三、剔除多余部分
我们所截的图片,肯定不会刚刚好是整块瓦片的,如下图所示:
红框是我们所截的图片,那我们需要用到(420,215)、(421,215)、(420,216)、(421,216)四张瓦片。当保存成图片时,我们需要把不需要的部分剔除。
在上面的代码里,我们得到的瓦片坐标是有小数部分的。举个例子,我们红框的左上角,可能对应的瓦片坐标是(420.9,215.4)。对于左上角的瓦片,假设我们要截取的矩形是(x,y,w,h),那么
x=0.9*256
y=0.4*256
w=256-x
h=256-y
下面是4个边距的计算代码:
int TILE_SIZE = 256;
int BLOOD = 0;//出血位,如果需要比截取区域稍微大一点的时候用
int ix1 = (int)x1;
int iy1 = (int)y1;
int ix2 = (int)x2;
int iy2 = (int)y2;
int margin_left = (int)((x1 - ix1) * TILE_SIZE - BLOOD);
margin_left = margin_left < 0 ? 0 : margin_left;
int margin_right = (int)((1 + ix2 - x2) * TILE_SIZE - BLOOD);
margin_right = margin_right < 0 ? 0 : margin_right;
int margin_top = (int)((y1 - iy1) * TILE_SIZE - BLOOD);
margin_top = margin_top < 0 ? 0 : margin_top;
int margin_bottom = (int)((1 + iy2 - y2) * TILE_SIZE - BLOOD);
四、完整代码
完整代码如下:
GetTile(zoom, lon1, lat1, out double x1, out double y1);
GetTile(zoom, lon2, lat2, out double x2, out double y2);
int TILE_SIZE = 256;
int BLOOD = 0;//出血位,如果需要比截取区域稍微大一点的时候用
int ix1 = (int)x1;
int iy1 = (int)y1;
int ix2 = (int)x2;
int iy2 = (int)y2;
int margin_left = (int)((x1 - ix1) * TILE_SIZE - BLOOD);
margin_left = margin_left < 0 ? 0 : margin_left;
int margin_right = (int)((1 + ix2 - x2) * TILE_SIZE - BLOOD);
margin_right = margin_right < 0 ? 0 : margin_right;
int margin_top = (int)((y1 - iy1) * TILE_SIZE - BLOOD);
margin_top = margin_top < 0 ? 0 : margin_top;
int margin_bottom = (int)((1 + iy2 - y2) * TILE_SIZE - BLOOD);
int bmp_w = (ix2 - ix1 + 1) * TILE_SIZE - margin_left - margin_right;
int bmp_h = (iy2 - iy1 + 1) * TILE_SIZE - margin_top - margin_bottom;
Bitmap big_bmp = new Bitmap(bmp_w, bmp_h);
Graphics graphics = Graphics.FromImage(big_bmp);
for (int i = ix1; i <= ix2; i++)
{
for (int j = iy1; j <= iy2; j++)
{
WebClient client = new WebClient();
client.Headers.Add("Accept: */*");
client.Headers.Add("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET4.0E; .NET4.0C; InfoPath.2; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; SE 2.X MetaSr 1.0)");
client.Headers.Add("Accept-Language: zh-cn");
client.Headers.Add("Accept-Encoding: gzip, deflate");
client.Headers.Add("Cache-Control: no-cache");
string url = string.Format("http://t1.tianditu.gov.cn/DataServer?T=vec_w&x={0}&y={1}&l={2}&tk=*******************", i, j, zoom);//*******************是用户信息,需要到官网获取
byte[] data = client.DownloadData(url);
MemoryStream ms = new MemoryStream(data);
Bitmap bmp = new Bitmap(ms);
int source_x = 0;
int source_w = TILE_SIZE;
if (i == ix1)
{
source_x = margin_left;
source_w = TILE_SIZE - margin_left;
}
else if (i == ix2)
{
source_w = TILE_SIZE - margin_right;
}
int source_y = 0;
int souce_h = TILE_SIZE;
if (j == iy1)
{
source_y = margin_top;
souce_h = TILE_SIZE - margin_top;
}
else if (j == iy2)
{
souce_h = TILE_SIZE - margin_bottom;
}
int target_x = 0;
if (i != ix1)
{
target_x = (i - ix1) * TILE_SIZE - margin_left;
}
int target_y = 0;
if (j != iy1)
{
target_y = (j - iy1) * TILE_SIZE - margin_top;
}
graphics.DrawImage(bmp, target_x, target_y, new Rectangle(source_x, source_y, source_w, souce_h), GraphicsUnit.Pixel);
bmp.Dispose();
ms.Close();
}
}
big_bmp.Save(@"D:\result.png");
graphics.Dispose();
big_bmp.Dispose();