2013年12月30日 星期一 17:12:33 晴
by Again 2013-12-30 17.12.38
- 先说名几句题外话:
-
第一,由于需要我也是半路出家,所以多专业就谈不上了。说白了js和用到的c#我几乎也是个小白,
我还是比较熟悉c++。还好有多少编程经验,所以不至于手足无措。以下的文章,并不会细致入微地介绍所有内容,
主要是走过的路给遇到相同问题的朋友留下点足迹,不至于被坑得太惨。 -
第二,主要目的是比较明确做出离线地图的功能,所以要求快、够用就好为原则。
百度地图和谷歌地图,其实在版权上都是不允许下载他们的东西的,但是老哥在祖国没有地位,小黑一把,
百度的话权当“我只看看,不买,请不要管我,谢谢啊”。
如果对我的帖子涉及任何版权相关问题,请联系我,马上删帖。
程序主要部分以针对谷歌地图拉取为主。百度地图其实原理也通用 -
第三,不发文件,源码核心部分已经在下文有描述。市面上大部分地图捉取工具都要收费,目前还搞不清楚是不是有什么利益纠葛,
所以我就不发这个自己做的工具了,喜欢自己动手丰衣足食的朋友们,相信看完必定离做出来也不远了,希望能帮到你们。
代码部分用的都是c#自带的类库,除了下载js api以外,你还需要一个不断网的电脑(这个,作为一个穷屌丝,我不能提供) -
第四,如有什么更好的建议,恕我愚钝,请一一点明,谢谢。
...
百度地图API 由可让您在接受使用条款约束的情况下,在您的网站上显示百度地图图片;进行地点搜索、路线查询、交通流量显示;发送短信分享地理位置信息等操作。您只可使用在百度地图API文档中所列明开放的API功能来对API相关服务数据的结果进行展示,不得直接存取、使用内部数 据、图片、程序、模块或是任何其他百度地图的服务或功能。在接受使用条款约束的情况下,您可以在向最终用户提供其他信息的同时,使用 API 获得API相关服务数据。
百度地图API面向公众服务类网站是免费的,但前提是您的网站所提供给用户的地图服务模块也必须是免费的。如果您使用百度地图且用于商业应用(如GPS运营等),从而直接或间接获得收益,您需要同百度地图另行达成协议或获得百度地图的事先书面许可。
...
来源: <http://developer.baidu.com/map/law.htm>
...
2. 使用限制。未经 Google 或特定内容提供商(如果有)的事先书面授权,您不得进行以下任何操作:(a) 对内容或内容的任何部分进行复制、翻译、修改或制作衍生作品;(b) 通过再分发、转许可、出租、发布、销售、转让、租赁、交易、转移或其他方式将产品或内容提供给第三方;(c) 进行反向工程、反编译或通过其他方式试图提取服务或其任何部分的源代码,但适用法律明确允许或要求的情况除外;(d) 通过可让您或其他任何人批量下载任何内容或对其批量提供 Feed(包括但不限于纬度/经度数值坐标、图像和可显示的地图数据)的方式使用产品;(e) 删除、隐藏或通过任何方式改变产品或内容中显示的任何警告或链接;或 (f) 出于下列或相关用途将服务或内容与任何产品、系统或应用结合起来使用,这些用途包括:(i) 实时导航或路线导航(包括但不限于与用户已启用传感器的设备的位置保持同步的精细路线导航);或 (ii) 任何用于自动或自主控制交通工具行为的系统或功能;(g) 使用产品创建地方信息或其他本地商家信息数据库。
来源: <http://www.google.com/intl/zh-CN_cn/help/terms_maps.html>
...
目录
- 1.找到响应下载图片链接的地址
- 2.程序解析地址
- 3.如何推算出余下需要下载的地址
- 4.如何下载图片
- 5.如何命名目录
- 6.如何读取刚才保存的图片
正文
1.找到响应下载图片链接的地址
首先是下到各家的api,理论上来讲下的是一个js文件。然后找到被调用,
生成下载图片地址的函数,然后改成调用自己的程序,记录这个调用的规则和参数。
当然本办法是直接用浏览器看,譬如火狐,看拉取链接,自己手动记录不同层的起始和终结坐标,解析下载url
就避免了直接改代码,不过最后你要调用图片的时候还是得改,至少延后了,或者你只需要图片呢?呵呵
百度地图
百度家的是乱码加密过的,高不高得定就自求多福吧。
看看浏览器拉取图片的地址,然后搜索"?qt=tile&x="
意外收获,找到瓦片拉取的函数了,先alter一下,看看拉取图片地址正不正常。
正常的话,直接获取对应的x y z变量。或者解析url字串,随你大同小异,我的方法比较笨,获得x y z后组成字符串再传到c#里
我觉得字符串是最好处理的,特别是不同语言之间,毫无压力。
Yb.getTilesUrl = function (a, b) { //获得瓦片图片
var c = a.x;
var d = a.y;
//X Y Z
var e = "pl";
this.map.Ne() && (e = "ph");
var resultURL = (Xb[p.abs(c + d) % Xb[w]] + "?qt=tile&x=" + (c + "").replace(/-/gi, "M") + "&y=" + (d + "").replace(/-/gi, "M") + "&z=" + b + "&styles=" + e + (6 == I.O.T ? "&color_dep=32&colors=50" : "") + "&udt=20130712").replace(/-(\d+)/gi, "M$1");
//alert(resultURL);
window.external.mapLoadCallback(a.x+'|'+a.y+'|'+b);
return resultURL;
};
谷歌地图
谷歌地图的话去搞了个gmap包,里面是别人搞好的代码。现在已经不知道在哪里搞回来的了
“GoogleMapAPI3的离线JS版本”
<!-- 加载离线地图 -->
<script type="text/javascript" src="mapapi3.8.6.js"></script>
<!-- 加载在线地图
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
-->
<script type="text/javascript">
//获得瓦片函数
LocalMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var img = ownerDocument.createElement("img");
img.style.width = this.tileSize.width + "px";
img.style.height = this.tileSize.height + "px";
//地图存放路径
//谷歌矢量图 maptile/googlemaps/roadmap
//谷歌影像图 maptile/googlemaps/roadmap
//MapABC地图 maptile/mapabc/
var strURL = "maptile/googlemaps/roadmap/";
strURL += zoom + "/" + coord.x + "/" + coord.y + ".PNG";
//回调函数,用于记录用到的图片范围 by Again 2013-11-13 9.58.48
var saveFile = zoom + "|" + coord.x + "|" + coord.y;
window.external.mapLoadCallback(saveFile);
img.src = strURL;
return img;
};
</script>
在c#winfrom里面拖了个webbowser,调用一个调用了api的index.html
至于怎么样c#和html之间的js互相调用这里就不详细描述了
2.如何推算出余下需要下载的地址
我的方法是这样的,自己选取一个矩形区域的两个顶角,从某一层(最高层),到另一层(最底层)通过鼠标滚轮,
缩放地图,地图js就会调用你的函数分析下载图片地址。同时,你可以根据对应的层,填充下方数组,
这样每一层的矩形区域,x y最大最小都可以一一得出。
根据谷歌的瓦片算法(全球)、或者百度地图的瓦片算法(中国大陆地区)都是直接两个for循环嵌套即可全部地址计算出来
创建数组,用于存储矩形
int[] Level_map_x_min = new int[20];
int[] Level_map_y_min = new int[20];
int[] Level_map_x_max = new int[20];
int[] Level_map_y_max = new int[20];
3.程序解析地址
//地图回调函数
public void mapLoadCallback(string mapLocation)
{
try
{
string[] strGroup = mapLocation.Split('|');
if (strGroup.Count() == 3)
{
int z = System.Int32.Parse(strGroup[0]);//语句1
int x = System.Int32.Parse(strGroup[1]);//语句1
int y = System.Int32.Parse(strGroup[2]);//语句1
//更新X最大最小值
Level_map_x_min[z] = x < Level_map_x_min[z] ? x : Level_map_x_min[z];
Level_map_x_max[z] = x > Level_map_x_max[z] ? x : Level_map_x_max[z];
//更新Y最大最小值
Level_map_y_min[z] = y < Level_map_y_min[z] ? y : Level_map_y_min[z];
Level_map_y_max[z] = y > Level_map_y_max[z] ? y : Level_map_y_max[z];
}
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString()+"\n"+mapLocation);
}
}
4.如何命名目录
5.如何下载图片
首先通过上面的方法,得到保存了各个层次x y 坐标矩形顶角信息
然后就可以开始下载的工作了。下面是通过顶角信息生成所有下载任务地址
然后就开启线程一个个下,没什么技术含量。
我的线程比较简单,没有做什么线程同步啊之类太专业的东西,
毕竟图片多一张少一张,大不了记录起来手工下载。
//拉取url线程
private void btnGetUrl_Click(object sender, EventArgs e)
{
//生成URL链接
CreateUrl();
//获取URL
Thread t1 = new Thread(new ThreadStart(TaskRunBackgroundProcess));
t1.IsBackground = true;
t1.Start(); //启动线程t1
}
创建图片下载地址和保存地址,怎么计算地址的函数见下文
这里可以自己限制,下载的层数,我稍微贪心了点,就下载了几十万张,但是几乎用不到,自己掂量吧
//根据各层的最大最小值创建url
private void CreateUrl()
{
PathUrlShouldSave.Clear();
UrlShouldBeDownload_strList.Clear();
UrlDownloaded_strList.Clear();
UrlDownloadFailed_strList.Clear();
for (int _level = 4; _level < levelCount; _level++)
{
for (int _y = Level_map_y_min[_level]; _y < Level_map_y_max[_level]; _y++)
{
for (int _x = Level_map_x_min[_level]; _x < Level_map_x_max[_level]; _x++)
{
PathUrlShouldSave.Add(converPointToPathString(_x, _y, _level));
UrlShouldBeDownload_strList.Add(converPointToUrlString(_x, _y, _level));
}
}
}
}
创建拉取图片管理主线程,用于建立各个图片的拉取任务线程,(想象成迅雷就好了)。
一开始懒惰,开一条多线程,速度其慢,一个晚上没拉完,回来把线程开到20,一个小时的量差不多顶一个晚上,
但是20条线程,实际在用估计6-8条,其他都是轮不上的,傻等女神的屌丝线程。
//拉取图片线程
private void TaskRunBackgroundProcess()
{
succeed = 0;
displayState = "(" + Convert.ToString(succeed) + "/" + Convert.ToString(UrlShouldBeDownload_strList.Count) + ")";
for (int i = 0; i < UrlShouldBeDownload_strList.Count; i++)
{
try
{
int lastindex = PathUrlShouldSave[i].ToString().LastIndexOf("/");
string path = PathUrlShouldSave[i].ToString().Substring(0, lastindex);
Directory.CreateDirectory(path);
while (threads > 20)
{
Thread.Sleep(1);
}
Thread t2 = new Thread(new ParameterizedThreadStart(downloadProcess));
t2.Start(i);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString());
}
displayState = "(" + Convert.ToString(succeed) + "/" + Convert.ToString(UrlShouldBeDownload_strList.Count) + ")";
}
}
单独一张图片的下载线程,通过i传递下载的是哪个单元的图片,有效抑制竞争关系。
毕竟唯一的资源唯一掌控是比较靠谱的
//下载一张图片的线程
private void downloadProcess(object i)
{
int index = (int)i;
threads++;
try
{
using (System.Net.WebClient wc = new WebClient())
{
wc.Headers.Add("User-Agent", "Chrome");
FileInfo fi = new FileInfo(PathUrlShouldSave[index].ToString());
if (!fi.Exists)
wc.DownloadFile(UrlShouldBeDownload_strList[index].ToString(), PathUrlShouldSave[index].ToString());
}
succeed++;
}
catch (System.Exception ex)
{
UrlDownloadFailed_strList.Add(UrlShouldBeDownload_strList[index]);
}
threads--;
}
根据点坐标x y z计算出下载链接
//点取出url
private string converPointToUrlString(int x, int y, int z)
{
string strUrl = "";
try
{
strUrl = string.Format("http://mt0.google.cn/vt/lyrs=m@241000000&hl=zh-CN&gl=CN&src=app&x={0}&y={1}&z={2}&s=Gali",
x,y,z);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString());
}
return strUrl;
}
根据点坐标计算出保存的目录
//点取出path
private string converPointToPathString(int x, int y, int z)
{
string strUrl = "";
try
{
strUrl = string.Format("E:/workspace2/BaiduMapTry/BaiduMapTry/Gmap/maptile/googlemaps/roadmap/{0}/{1}/{2}.png",
z, x, y);
}
catch (System.Exception ex)
{
MessageBox.Show(ex.ToString());
}
return strUrl;
}
6.如何读取刚才保存的图片
把页面中js加载图片的目录计算成本地的目录,搞定