Google、搜搜、百度三家卫星地图URL研究

By -

前一段时间因为公司有个项目需要我研究一下 Google 的卫星图的地址规律,开始我以为 会很艰难认为Google那么大公司 URL 应该会加密什么的,结果发现 Google 和搜搜百度搜狗 这几个比较起来Google的 url演算是最简单且明了的,废话不说了。

打开Google Map我们可以看到一张地图,将它放大或者缩小就可以看到不同的地图(很多张的图片)。url像这样:http://khm1.google.com/kh/v=125&src=app&x=0&y=0&z=0   (红色部分均为可变的参数)

Google 的作法是世界的第0层(z=0)就是一张图构成:

v=125&src=app&x=-2&y=0&z=0

z=1的效果就是将它切分为4份见下图:

 v=125&src=app&x=0&y=0&z=1  v=125&src=app&x=1&y=0&z=1
 v=125&src=app&x=0&y=1&z=1  v=125&src=app&x=1&y=1&z=1

依次类推,我们可以得到一个公式:NUM=2^(N+1)

NUM:x或者y方向上的图片数

N:第几层z轴

然后引入Mercator库(我自己写的麦卡拓转换)code:

[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace MapsDownloader
{
class Mercator
{
private double NormalToMercator(double y)
{
y -= 0.5;
y *= 2.0 * Math.PI;
y = Math.Exp(y * 2.0);
y = (y – 1) / (y + 1.0);
y = Math.Asin(y);
y = y * -180.0 / Math.PI;
return y;
}
private double MercatorToNormal(double y)
{
y = -y * Math.PI / 180.0;
y = Math.Sin(y);
y = (1.0 + y) / (1.0 – y);
y = 0.5 * Math.Log(y);
y *= 1.0 / (2.0 * Math.PI);
y += 0.5;
return y;
}
private double getNormailByY(double y,int picnum)
{
return y/picnum;
}
private double getYByNormail(double y, int picnum)
{
return picnum * y;
}
///////////////////////////////////////////////
///传入:-26.851029008675013 返回:-26° 51′ 3"
public string GetSexagesimalNotation(double x)
{
//to string format: 23° 27′ 30"
var ret = "";
if (x < 0)
{
ret += ‘-‘;
x = -x;
}
ret += Math.Floor(x);
ret += "° ";

x = (x – Math.Floor(x)) * 60;
ret += Math.Floor(x);
ret += "’ ";

x = (x – Math.Floor(x)) * 60;
ret += Math.Floor(x);
ret += "" ";

return ret;
}
public Point Google_XYZ_to_LatLng(int x, int y, int z) //lat [0] , lng [1]
{
Point LatLng = new Point();
int picnum = 2 << (z – 1); //z层在x、y轴上存在多少张 double onex = 360.0 / picnum; //平均每一张占用多少经纬度 double xlat = onex * x; if (xlat > 360.0) //如果超过360就剪掉它
xlat = xlat % 360.0;
if (xlat > 180.0)
LatLng.X = onex * x – 180.0;
else
LatLng.X = -(180.0 – onex * x);
LatLng.Y = NormalToMercator(getNormailByY(y, picnum));
return LatLng;
}
public Point Google_LatLngZ_to_XY(double Lat, double Lng, int z) //return x,y array
{
Point xy = new Point();
int picnum = 2 << (z – 1); //z层在x、y轴上存在多少张
double onex = 360.0 / picnum; ////平均每一张占用多少经纬度
Lat += 180.0;
xy.X = Convert.ToInt32(Lat / onex); //x
xy.Y = Convert.ToInt32(getYByNormail(MercatorToNormal(Lng), picnum)); //y
return xy;
}
}
}
[/sourcecode]

主要是Y轴方向转换麻烦需要用到这些函数,因为Mercator当初设计的缺陷就是越往两极地区越不准确。

下面两个函数Google_XYZ_to_LatLng、Google_LatLngZ_to_XY提供了将XYZ转换成为 经纬度,和经纬度转换成Google的XYZ轴,有了这些基础我们就可以用html和JQuery写出一个简易的GoogleMap 网页键盘版本:

[sourcecode language=”html”]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Keyboard Google Map——By MaxHo</title>
<script type="text/javascript" src="jquery-1.8.0.min.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
readImage();
});
function readImage()
{
var url="";
//http://khmdbs0.google.com/pm?v=9&src=app&x=0&y=4&z=4&s=Gali
//http://khm1.google.com/kh/v=125&src=app&x=
//

if($("#mapTypeSelect").val() == 1)
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
var picnum = 2<<(zz-1);
var onex = 360 / picnum;
/*var oney = 180 / picnum;
if(oney * yy > 90)
var lng = -(oney * yy – 90);
else
var lng = 90 – oney * yy;*/
var lng = NormalToMercator (getNormailByY(yy,picnum));
if(onex * xx > 180)
var lat = onex * xx – 180;
else
var lat = -(180 – onex * xx);
$("#latlng").text(lng + " , " + lat);
$("table td").each(function(i){
var ix = i%5 + parseInt($("#x").val());
var iy = parseInt(i/5) + parseInt($("#y").val());
url = "http://khm1.google.com/kh/v=125&src=app&x="+ix
+"&y="+iy+"&z="+$("#z").val();
$(this).html("<img style=’width:100%;height:100%;’ src=’"+url+"’/>");
});
}
else if($("#mapTypeSelect").val() == 2)        //soso
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
$("table td").each(function(i){
var ix = i%5 + parseInt($("#x").val());
var iy = 2 – parseInt(i/5) + parseInt($("#y").val());
var dx = Math.floor(ix/16);
var dy = Math.floor(iy/16);
url = "http://p1.map.soso.com/sateTiles/"+$("#z").val()
+"/"+dx+"/"+dy+"/"+ix+"_"+iy+".jpg";
$(this).html("<img style=’width:100%;height:100%;’ src=’"+url+"’/>");
});
}
else if($("#mapTypeSelect").val() == 3)        //baidu
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
$("table td").each(function(i){
var ix = i%5 + parseInt($("#x").val());
var iy = 2 – parseInt(i/5) + parseInt($("#y").val());
url = "http://q1.baidu.com/it/u=x="+ix+";y="+iy+";z="+$("#z").val()
+";v=009;type=sate&fm=46";
$(this).html("<img style=’width:100%;height:100%;’ src=’"+url+"’/>");
});
}
else if($("#mapTypeSelect").val() == 4)        //cangbao
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
var picnum = 2<<(zz-1);
var onex = 360 / picnum;
/*var oney = 180 / picnum;
if(oney * yy > 90)
var lng = -(oney * yy – 90);
else
var lng = 90 – oney * yy;*/
var lng = NormalToMercator (getNormailByY(yy,picnum));
if(onex * xx > 180)
var lat = onex * xx – 180;
else
var lat = -(180 – onex * xx);
$("#latlng").text(lng + " , " + lat);
$("table td").each(function(i){
var ix = i%5 + parseInt($("#x").val());
var iy = parseInt(i/5) + parseInt($("#y").val());
url = "http://khmdbs0.google.com/pm?v=9&src=app&x="+ix
+"&y="+iy+"&z="+$("#z").val();
$(this).html("<img style=’width:100%;height:100%;’ src=’"+url+"’/>");
});
}
}
function left()
{
var xx = parseInt($("#x").val());
$("#x").val(–xx);
}
function up()
{
var yy = parseInt($("#y").val());
if($("#mapTypeSelect").val() == 2 || $("#mapTypeSelect").val() == 3 )
$("#y").val(++yy);
else
$("#y").val(–yy);
}
function right()
{
var xx = parseInt($("#x").val());
$("#x").val(++xx);
}
function down()
{
var yy = parseInt($("#y").val());
if($("#mapTypeSelect").val() == 2 || $("#mapTypeSelect").val() == 3 )
$("#y").val(–yy);
else
$("#y").val(++yy);
}
function enter()
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
if($("#mapTypeSelect").val() == 1)
{
var centerX = (xx+2)*2 – 2;
var centerY = (yy+1)*2 – 1;
var centerZ = zz+1;
}
else if($("#mapTypeSelect").val() == 2 || $("#mapTypeSelect").val() == 3 )
{
var centerX = (xx+2)*2 – 2;
var centerY = (yy-1)*2 + 3;
var centerZ = zz+1;
}
$("#x").val(centerX);
$("#y").val(centerY);
$("#z").val(centerZ);
}
function exit()
{
var xx = parseInt($("#x").val());
var yy = parseInt($("#y").val());
var zz = parseInt($("#z").val());
if($("#mapTypeSelect").val() == 1)
{
var centerX = parseInt((xx+2)/2) – 2;
var centerY = parseInt((yy+1)/2) – 1;
var centerZ = zz-1;
}
else if($("#mapTypeSelect").val() == 2 || $("#mapTypeSelect").val() == 3 )
{
var centerX = parseInt((xx+2)/2) – 2;
var centerY = parseInt((yy-1)/2);
var centerZ = zz-1;
}
$("#x").val(centerX);
$("#y").val(centerY);
$("#z").val(centerZ);
}
$(this).bind(‘focus’,function(event){
$(this).select();
});
$(document).bind(‘keydown’,function(e){
e = (e) ? e : ((window.event) ? window.event : "");
var key = e.keyCode?e.keyCode:e.which;
switch(key) {
case 37:
//left
left();
readImage();
break;
case 38:
//up
up();
readImage();
break;
case 39:
//right
right();
readImage();
break;
case 40:
//down
down();
readImage();
break;
/*case 13:
//enter
enter();
readImage();
break;*/
case 69:
//E
enter();
readImage();
break;
/*case 27:
//exit
exit();
readImage();
break;*/
case 81:
//Q
exit();
readImage();
break;
default:
//alert(key);
break;
}
});
function MercatorToNormal(y)
{
y = -y * Math.PI / 180;    // convert to radians
y = Math.sin(y);
y = (1+y)/(1-y);
y = 0.5 * Math.log(y);
y *= 1.0 / (2 * Math.PI);    // scale factor from radians to normalized
y += 0.5;    // and make y range from 0 – 1
return y;
}

function NormalToMercator(y)
{
y -= 0.5;
y *= 2 * Math.PI;
y = Math.exp(y * 2);
y = (y-1)/(y+1);
y = Math.asin(y);
y = y * -180/Math.PI;
return y;
}
function getNormailByY(y,picnum)
{
/*var scale = 1.0;
var ry = 0.0;
for (var i = 0; i< y ; i++)
{
scale *= 0.5;
ry += scale;
}
return ry;*/
return y/picnum;
}
function changeMapTypeSelect()
{
if($("#mapTypeSelect").val() == 2)
{
$("#x").val(10);
$("#y").val(8);
$("#z").val(4);
}
else if($("#mapTypeSelect").val() == 1)
{
$("#x").val(10);
$("#y").val(5);
$("#z").val(4);
}
else if ($("#mapTypeSelect").val() == 3 )
{
$("#x").val(3);
$("#y").val(0);
$("#z").val(5);
}
readImage();
}

</script>
</head>
<body>
<form action="#">
x:<input id="x" type="text" value="10" style="width:60px;"/>
y:<input id="y" type="text" value="5" style="width:60px;"/>
z:<input id="z" type="text" value="4" style="width:60px;"/>
<input type="submit" value="重新載入" onclick="readImage()"/>&nbsp;&nbsp;&nbsp;<span style="font-size:12px; color:#808080">
<select id="mapTypeSelect" onchange="changeMapTypeSelect()">
<option value="1" selected="selected">GoogleMap</option>
<option value="2">SosoMap</option>
<option value="3">BaiduMap</option>
<option value="4">藏宝图</option>
</select>
使用鍵盤熱鍵上下左右鍵調節方向,E、Q控制地圖縮放</span>
<input type="button" value="←" onclick="left(),readImage()"/>
<input type="button" value="↑" onclick="up(),readImage()"/>
<input type="button" value="↓" onclick="down(),readImage()"/>
<input type="button" value="→" onclick="right(),readImage()"/>
<input type="button" value="㈩" onclick="enter(),readImage()"/>
<input type="button" value="㈠" onclick="exit(),readImage()"/>
</form>
左上角經緯度:&nbsp;&nbsp;<span id="latlng"></span>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
[/sourcecode]

soso map和baidu map的地图url规律就蛋疼许多,因为他们的Y轴非要与google的方向相反。。没法,为了适应他们单独写了函数

这还不算什么,soso还单独弄出来一个参数dx、dy,后来我还是自己看sosomap中的已经被搅乱的js代码才分析出来只是由x和y通过Math.floor(x/16)运算得到的,腾讯这样搞这有木有意义@@ ,为了后台省资源?

百度也是跟着腾讯一样把地图的Y轴弄反,不过没有dx、dy,不过他们我实测都有经过偏移模组的偏移。也就是说你给定一个经纬度通过标准的麦卡拓算 出来的xyz总有一点点几百米的偏移。这些倒是怪不到他。更变态的是sogoumap完全没有办法猜清楚是什么规律,我直接选择放弃了,都不想看它的js 代码了,还是洗洗睡吧。

发表评论

电子邮件地址不会被公开。