代码实现地图截图功能

mac2025-09-02  10

问题描述:已知左上角点(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();

 

最新回复(0)