代码实现地图截图功能

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

 

================================================================================================== 电子地图一把抓V1.0 Google Earth非完美版及无损压缩版 解决了电子地图一把抓原版的下列问题: ● 抓Google Earth卫图时导致地球旋转,无法正确抓图的问题 ● 抓非卫图地图时,在道路边界及文字附近出现噪点问题。去除噪点后,可以制作出更清晰、也更小的最终文件 GE非完美版的非完美性表现在: 抓Google Earth带KML/KMZ地标显示的卫图时,在某次自动移动地球时,若GE抓手下方恰好有图标,在目前最新的 GE V4.3beta版中测试的结果看,抓手会“滑”一小段,导致错位。 但实测无图标显示的KML/KMZ或关闭其图标,显示道路及面状物时,未测出问题(未进行大量测试)。因此电子地 图一把抓GE非完美版可以制作GE卫图底图+不带图标的KML/KMZ的地图。有图标时,需仔细检查,有问题可稍微改 变一下起点位置或抓图区域大小重抓试验。 可执行文件说明: MapCap.exe 原版本 MapCap_LZW.exe 24位模拟式下将TIF文件从有损JPEG压缩改为无损LZW压缩,解决图像出现噪点的问题。 推荐用于抓取非卫图的地图。 MapCap_GE.exe 用于Google Earth,非完美(屏幕上图标较密集时有可能导致错位)。存储的结果TIF 文件24位模式下与原版一样,采用有损JPEG压缩。 MapCap_GE_LZW.exe 同MapCap_GE.exe,但24位模拟式下采用无损LZW压缩。 推荐用于抓取Google Earth卫图,特别是带KML显示时,但有图标显示时需仔细检查结果 是否有错位的现象。 注:电子地图一把抓的所有权利完全归原作者Kenchang所有。感谢Kenchang编写这么实用又有生命力的软件。 2009.1.2 ================================================================================================== kenchang对原版的说明: 1、软硬件要求 2、安装卸载说明 3、已知问题 4、版权声明 1、软硬件要求 可运行在Windows98/ME/2000/XP之上,CPU为奔腾133以上,内存32M以上。抓取大图时会需要大量内存, 建议内存为512M以上。 2、安装卸载说明 本软件为绿色软件,将所有文件复制到同一目录中,运行mapcap.exe即可。删除时将该目录下的所有文件删除。 3、已知问题 本软件未经广泛测试,谬误在所难免 4、版权声明 本软件为免费软件,不提供任何形式的技术支持。本软件仅供学习交流用途, 不得用于任何形式的商业目的或其他非法目的,在抓图之前应先取得原版权所有者的同意, 使用本软件造成的任何后果均与本软件作者无关。 kenchang
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值