美文网首页
打造地图拼接利器(五)地图采集与拼接

打造地图拼接利器(五)地图采集与拼接

作者: 安静的林哥 | 来源:发表于2021-06-28 14:39 被阅读0次

获取到经纬度范围后,我们需要计算出瓦片的范围。

本文涉及的地图瓦片都以左上角为原点开始编号的,从左至右为 x 轴, 从上到下为 y轴。

坐标示意图

为保证地图是方形,基于 Web 墨卡托投影的地图左上角经纬度坐标为(180°,85.0511 °),右下角经纬度为(-180°,-85.0511°)。纬度范围是[-85.0511, 85.0511 ]。

假设z为需要拼接的图层的层数,设n=2的z次方,lon为经度,lat为维度,则经纬度、层级和瓦片的坐标x、y的关系为:

TileX =(lon+180)÷360×n;
TileY = (1-(log(tan(lat))+sec(lat)))÷2×n;

在C#中,Math函数的三角参数要求为弧度,所以纬度值牵扯到三角函数的还需要×PI÷180。两个函数如下:

     /// <summary>
        /// 计算瓦片所在X值
        /// </summary>
        /// <param name="lng"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public static double getTileX(double lng, int zoom)
        {
            double tileX = (lng + 180.0) / 360 * Math.Pow(2, zoom);
            return tileX;
        }

        /// <summary>
        /// 墨卡托投影下,通过维度计算瓦片所在Y值
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="zoom"></param>
        /// <returns></returns>
        public static double getTileY(double lat, int zoom)
        {
            if (lat > 90)
                lat = lat - 180;
            if (lat < -90)
                lat = lat + 180;
            double tileY = Math.Pow(2, zoom) / 2 * (1 - (Math.Log(Math.Tan(Math.PI * lat / 180) + 1 / Math.Cos(Math.PI * lat / 180))) / Math.PI);
            return tileY;
        }

前面将下载图片的函数已在第三章中写好,直接通过xyz和瓦片地址下载图片,然后使用GDI+拼接即可。

这里有一个很严重的问题,就是受GDI+技术限制,图幅过大可能拼接失败。一般10000像素×10000像素以内为妥。所以,过大范围的需求,只能拼接为系统承受范围内的大小了。我们做一个参数,可以设置这个值,最大到10240像素。用户最终在ps里再量力而行的拼接了。

写一个类DownloadWork,用于在线程里展开下载任务,在构造函数中声明下载范围和下载图片的参数。

GetArea(WorkArg arg)用于对任务进行拆解,以防止GDI+内存溢出报错:

  /// <summary>
        /// 以maxnum×maxnum为一组数据
        /// </summary>
        /// <param name="Arg"></param>
        /// <returns></returns>
        private List<AreaConfig> GetAreas(WorkArg Arg)
        {
            List<AreaConfig> results = new List<AreaConfig>();

            for (int j = Arg.Starty, n = 0; j <= Arg.Endy; n++)
            {
                for (int i = Arg.Startx, m = 0; i <= Arg.Endx; m++)
                {
                    AreaConfig area = new AreaConfig();
                    area.Startx = i;
                    area.Endx = i + GlobalConfig.maxtilecount < Arg.Endx ? i + GlobalConfig.maxtilecount : Arg.Endx + 1;
                    area.Starty = j;
                    area.Endy = j + GlobalConfig.maxtilecount < Arg.Endy ? j + GlobalConfig.maxtilecount : Arg.Endy + 1;
                    area.Posx = m;
                    area.Posy = n;
                    i += GlobalConfig.maxtilecount;
                    results.Add(area);
                }
                j += GlobalConfig.maxtilecount;
            }
            return results;
        }

主下载拼接函数主要有以下任务:一是拆分任务,二是根据任务大小,创建一个bitmap,三是循环x和y下载图,并根据坐标绘制在bitmap上,四是保存bitmap到本地磁盘。其中,在三中还完成了复合图的制作,代码如下。

  public void DoWork(object arg)
        {
            WorkArg Arg = (WorkArg)arg;
            FileInfo fInfo=new FileInfo(Arg.Filename);
            string savedir = "";

            int xNum = (Arg.Endx - Arg.Startx + 1), yNum = (Arg.Endy - Arg.Starty + 1);
            int totalnum=xNum*yNum,count = 0;
            //受GDI的限制,每次拼图不能超过20000像素×20000像素,但实际操作过程中,远不止这个数,有时候10000像素也会出错,为了程序的稳定性,
            //如果设置单幅图最大不超过20×20个瓦片,就是5120×5120像素
            //此处需要对数据进行拆分
            List<AreaConfig> tasks = GetAreas(Arg);

            for (int taski = 0; taski < tasks.Count; taski++)
            {
                int xnum = tasks[taski].Endx - tasks[taski].Startx, ynum = tasks[taski].Endy - tasks[taski].Starty;
                Bitmap bit = new Bitmap(xnum * 256, ynum * 256);
                Graphics g = Graphics.FromImage(bit);

                for (int i = tasks[taski].Startx, m = 0; i < tasks[taski].Endx; i++, m++)
                {
                    for (int j = tasks[taski].Starty, n = 0; j < tasks[taski].Endy; j++, n++)
                    {
                        if (!iswork)
                            break;
                        foreach (var url in Arg.Downloadurls)
                        {
                            string durl = Tools.getUrl(url, i, j, Arg.Zoom);
                            byte[] bts = Tools.downloadTile(durl);
                            if (bts == null)
                            {
                                g.DrawString("此区域请求失败", new Font("宋体", 12f), new SolidBrush(Color.Red), new Point(m * 256, n * 256));
                            }
                            else
                            {
                                MemoryStream ms = new MemoryStream(bts);
                                Image img = Bitmap.FromStream(ms);
                                g.DrawImage(img, m * 256, n * 256, 256, 256);
                            }
                        }
                        count++;
                        myWorkPercent(count, totalnum,taski+1,tasks.Count);
                    }
                    if (!iswork)
                        break;
                }
                string filename = fInfo.DirectoryName + "\\" + fInfo.Name + "\\";
                if (!Directory.Exists(filename))
                    Directory.CreateDirectory(filename);
                savedir = filename;
                if (tasks.Count > 1)
                {
                    filename += tasks[taski].Posy + "-" + tasks[taski].Posx + "_";
                }
                filename+=fInfo.Name;
                bit.Save(filename, ImageFormat.Jpeg);
                g.Dispose();
                bit.Dispose();
            }
            myFinishWork(savedir);
        }

最后,启动线程:

 public void StartWork()
        {
            iswork = true;
            th = new Thread(new ParameterizedThreadStart(DoWork));
            th.Start(this.workarg);
        }

增加2个事件代理,分别传回工作状态和合并完成状态:

    public delegate void Workpercent(int curnum, int maxnum, int ctasknum, int tasknum);
    public delegate void FinishWork(string filedir);

合并完成后,通知主界面打开任务文件夹:

      void dlw_myFinishWork(string savedir)
        {
            MessageBox.Show("任务完成!");
            enableControls(true);
            System.Diagnostics.Process.Start("explorer.exe", savedir);
            dlw = null;
        }

至此,所有工作基本完成。最后在啰嗦2点:

1.最好使用天地图,占位高,不足就是每天数量有限制,可以自己申请开发者的tk,改config.txt后下载。

2.国外图只作为参考,边境问题敏感,立场要坚定,使用要谨慎。

链接:https://pan.baidu.com/s/1dGzf3GRY50cqmBL-oA-PGA
提取码:v77a
复制这段内容后打开百度网盘手机App,操作更方便哦

本章参考:

芒果香蕉_《地图瓦片编号与经纬度的换算关系》简书

相关文章

网友评论

      本文标题:打造地图拼接利器(五)地图采集与拼接

      本文链接:https://www.haomeiwen.com/subject/qcktultx.html