獲取到經(jīng)緯度范圍后,我們需要計(jì)算出瓦片的范圍。
本文涉及的地圖瓦片都以左上角為原點(diǎn)開始編號的娃肿,從左至右為 x 軸, 從上到下為 y軸珠十。
為保證地圖是方形料扰,基于 Web 墨卡托投影的地圖左上角經(jīng)緯度坐標(biāo)為(180°,85.0511 °)焙蹭,右下角經(jīng)緯度為(-180°晒杈,-85.0511°)。緯度范圍是[-85.0511, 85.0511 ]孔厉。
假設(shè)z為需要拼接的圖層的層數(shù)拯钻,設(shè)n=2的z次方,lon為經(jīng)度撰豺,lat為維度粪般,則經(jīng)緯度、層級和瓦片的坐標(biāo)x郑趁、y的關(guān)系為:
TileX =(lon+180)÷360×n刊驴;
TileY = (1-(log(tan(lat))+sec(lat)))÷2×n;
在C#中寡润,Math函數(shù)的三角參數(shù)要求為弧度,所以緯度值牽扯到三角函數(shù)的還需要×PI÷180舅柜。兩個(gè)函數(shù)如下:
/// <summary>
/// 計(jì)算瓦片所在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>
/// 墨卡托投影下梭纹,通過維度計(jì)算瓦片所在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;
}
前面將下載圖片的函數(shù)已在第三章中寫好,直接通過xyz和瓦片地址下載圖片致份,然后使用GDI+拼接即可变抽。
這里有一個(gè)很嚴(yán)重的問題,就是受GDI+技術(shù)限制,圖幅過大可能拼接失敗绍载。一般10000像素×10000像素以內(nèi)為妥诡宗。所以,過大范圍的需求击儡,只能拼接為系統(tǒng)承受范圍內(nèi)的大小了塔沃。我們做一個(gè)參數(shù),可以設(shè)置這個(gè)值阳谍,最大到10240像素蛀柴。用戶最終在ps里再量力而行的拼接了。
寫一個(gè)類DownloadWork矫夯,用于在線程里展開下載任務(wù)鸽疾,在構(gòu)造函數(shù)中聲明下載范圍和下載圖片的參數(shù)。
GetArea(WorkArg arg)用于對任務(wù)進(jìn)行拆解训貌,以防止GDI+內(nèi)存溢出報(bào)錯(cuò):
/// <summary>
/// 以maxnum×maxnum為一組數(shù)據(jù)
/// </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;
}
主下載拼接函數(shù)主要有以下任務(wù):一是拆分任務(wù)制肮,二是根據(jù)任務(wù)大小,創(chuàng)建一個(gè)bitmap递沪,三是循環(huán)x和y下載圖豺鼻,并根據(jù)坐標(biāo)繪制在bitmap上,四是保存bitmap到本地磁盤区拳。其中拘领,在三中還完成了復(fù)合圖的制作,代碼如下樱调。
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像素,但實(shí)際操作過程中笆凌,遠(yuǎn)不止這個(gè)數(shù)圣猎,有時(shí)候10000像素也會(huì)出錯(cuò),為了程序的穩(wěn)定性乞而,
//如果設(shè)置單幅圖最大不超過20×20個(gè)瓦片送悔,就是5120×5120像素
//此處需要對數(shù)據(jù)進(jìn)行拆分
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("此區(qū)域請求失敗", 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);
}
最后,啟動(dòng)線程:
public void StartWork()
{
iswork = true;
th = new Thread(new ParameterizedThreadStart(DoWork));
th.Start(this.workarg);
}
增加2個(gè)事件代理爪模,分別傳回工作狀態(tài)和合并完成狀態(tài):
public delegate void Workpercent(int curnum, int maxnum, int ctasknum, int tasknum);
public delegate void FinishWork(string filedir);
合并完成后欠啤,通知主界面打開任務(wù)文件夾:
void dlw_myFinishWork(string savedir)
{
MessageBox.Show("任務(wù)完成!");
enableControls(true);
System.Diagnostics.Process.Start("explorer.exe", savedir);
dlw = null;
}
至此屋灌,所有工作基本完成洁段。最后在啰嗦2點(diǎn):
1.最好使用天地圖,占位高共郭,不足就是每天數(shù)量有限制祠丝,可以自己申請開發(fā)者的tk疾呻,改config.txt后下載。
2.國外圖只作為參考写半,邊境問題敏感岸蜗,立場要堅(jiān)定,使用要謹(jǐn)慎叠蝇。
鏈接:https://pan.baidu.com/s/1dGzf3GRY50cqmBL-oA-PGA
提取碼:v77a
復(fù)制這段內(nèi)容后打開百度網(wǎng)盤手機(jī)App璃岳,操作更方便哦
本章參考:
芒果香蕉_《地圖瓦片編號與經(jīng)緯度的換算關(guān)系》簡書