在使用arcgis js api 或是 openlayers 来进行百度地图切片加载的时候,由于百度地图采用的是私有、非标准的切片方案,不能像加载谷歌切片或是天地图切片一样,api原生支持、开箱即用,而是需要进行定制化的开发来实现切片的加载

定制化百度地图切片的加载策略,需要知道百度地图切片方案的切片起点(origin)、切片分辨率(resolutions)、切片范围(full extent)、切片正方向定义(x-axis positive direction 、y-axis positive direction)、切片尺寸(tile size)等。百度切片方案的以上信息,都与标准web mercator切片方案有着差异。我们知道,标准web mercator切片方案的切片起点为切片范围的左上角(top,left),切片范围为[-180,-85,180,85]范围,切片x正方向由left到right列号递增,切片y正方向由top到bottom行号递增,切片尺寸为[256,256]大小。那百度地图是怎样的呢?

网上的信息

带着这些问题,我查阅了很多资料。汇总查找到的资料,百度地图切片方案:

  1. 起点是[-20037508.3427892,20037508.3427892]

  2. 切片的范围是[-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892],

  3. 切片x正方向是由left到right,切片y正方向是从bottom到top。

带着以上配置,我编写了代码进行了测试。不过很遗憾,测试结果表明以上的信息有假有真,使用以上的配置,切片死活加载不出来,或者加载出来坐标也是错误的。

那百度地图切片方案到底是如何定义的呢?下面我们就来试着推导一下。

推导的信息

positive directions

我们已经知道,百度地图的最小层级为3。在level=3中,百度地市使用8 * 8张切片来表示整个切片范围。如下图所示。结合下图和组成下图的每一个小图片具体的行号和列号,可以确定“切片x正方向是由left到right,切片y正方向是从bottom到top”这句话是正确的。

baidu

可以发现,关于正方向的定义,百度切片方案和标准web mercator切片方案对于x正方向的定义相同,对于y正方向的定义是相反的。此处定义的不同,结合百度切片方案对于起点的定义,就会产生一个百度方案独有的地方,下面我们介绍了起点之后会着重说。

origin

在以上的图片中,行号和列号为0的图片,如下所示。可以看到,图片最左端、上半部分为英国区域,为本初子午线穿过的地方。因此可以确定,百度地图切片方案的起点确确实实的是[0,0],而不是[-20037508.3427892,20037508.3427892]。关于20037508.3427892这个数字是如何计算出来的,我们可以简单说一下:

标准web mercator切片方案下,整个切片范围为[180,85]经纬度范围,即整个经度范围。web mercator投影坐标使用的地球半径为6378137米,那么地球的周长为6378137 * Math.PI * 2米,均匀的分成2部分,即为20037508.342789244米。

1x1

说完了正方向和起点之后,细心的人应该已经发现了,在百度的方案中,起点不在地图的左上角,而是在中心,那么在百度切片方案中,存在着行号、列号为负数的清明的。在百度地图中,对于负数的行号和列号进行了特殊规定。简而言之,对于负数的行列号,使用M + 绝对值来进行表示。即,对于(-3,-4)这种行列号,在百度地图中使用(M3, M4)来进行表示。

full extent

关于整个切图范围,我始终没有找到推导的方式。只能基于实际测试数据,得出一个经验值。此经验值是基于实际的代码加载测试得出的:[-33554432, -33554432, 33554432, 33554432]。如果此数值不正确,或是哪位有推导方式,还望告知。

虽然此范围,没有任何的科学推导过程,但好在可以经受住实际代码的测试。并且,使用百度在线坐标转换服务将此坐标进行转换,得到的结果如下图所示,也算是从侧面进行了验证吧。

1
2
3
4
5
6
7
8
9
10
// https://api.map.baidu.com/geoconv/v1/?coords=-33554432,33554432&from=6&to=5
{
"status": 0,
"result": [
{
"x": -179.99999999851323,
"y": 89.40915130133585
}
]
}

说句题外话,使用百度在线坐标转换服务,对坐标[-33554432, 33554432]进行连续的转换测试,先从投影坐标转换成经纬度,然后将经纬度再转换生成投影坐标,最后得到的结果与原始的坐标千差万别。不知道问题出在哪里。能想到的最可能的原因是,此坐标超过了接口适用范围。

resolution

确定了切片范围之后,分辨率(分辨率的意义是:图片中每px像素所表示的实际距离的大小。)也就可以确定了。我们已知百度地图最小层级为3,在level=3上,使用64张切片来表示整个切片范围。那么,level=3时,分辨率为

1
resolution = 33554432 * 2 / 256 / 8

随着level的递增,分辨率会成倍的递减。汇总可知百度切片的分辨率信息为

1
[262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5]

tilesize

关于切片尺寸,由切片图片可以确定,为[256,256]大小。

切片加载

汇总了以上信息之后,我们就可以定义百度切片地图tileinfo进行切片加载了。

tileinfo

1
2
3
4
5
6
{
resolutions: [262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5],
origin: [0, 0],
tileSize: [256, 256],
fullExtent: [33554432, -33554432, 33554432, 33554432]
}

切片地址拼接

前面说到,百度切片方案的y正方向和标准web mercator方案不同,因此在进行切片加载的时候需要自定义切片获取函数,进行行号和列号的转换。具体如何转换呢,查阅的资料大多都是按照如下的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const zoom = level - 1;
const offsetX = parseInt(2 ** zoom, 10);
const offsetY = offsetX - 1;
let numX = col - offsetX;
let numY = (-row) + offsetY;
if (numX < 0) {
numX = `M${-numX}`;
}
if (numY < 0) {
numY = `M${-numY}`;
}
const url = getLayerUrl(this.layerType);
return url
.replaceAll('{server}', Math.floor(Math.random() * 10))
.replaceAll('{x}', numX)
.replaceAll('{y}', numY)
.replaceAll('{z}', level);

我翻遍了全网,只能看到此代码片段,却找不到任何对此代码的解释。不知道是不是太简单了,不值一提?还是大家都不求甚解,能解决问题即可,底层原因懒得深究(包括我)。

其实,以上的转换方式某种意义上是对的,但前提是建立在定义origin在[-33554432, 33554432],即切片方案左上角的基础上。

以上关于行号和列号的转换方式,如果你感觉到困惑的话,配合下面这张图,就会让你豁然开朗了。

convert

上图中,左侧为百度切片方案对x和y正方向的定义,右侧为标准web mercator切片方案对的定义。我们使用js api是使用右侧的定义对切片进行加载,要想获取到正确的百度切片地址,必须要进行转换。观察以上左右两侧的图片,可以发现,标准切片方案下获取到的x列号,需要减去全图范围一行中一半的切片个数,是百度切片方案下的列号。而标准切片方案下的y列号,被全图范围一行中一半切片个数减,再减去1,即使百度切片方案下的列号。即如下公式表示:

1
2
x1 = x2 - offset
y1 = offset - 1 - y2

其中,(x1,y1)为百度切片方案下的行列号,(x2,y2)为标准web mercator切片方案下的行列号,offset为当前层级下全图切片范围一行中切片个数的一半。x为列号,y为行号。如果得到的x1和y1为负数,还需要按照前面介绍的规则进行转换(添加M)。

回头再来看,上面的转换代码。为了查看的方便,在此处再贴一遍。

1
2
3
4
5
6
7
8
9
10
11
const zoom = level - 1;
const offsetX = parseInt(2 ** zoom, 10);
const offsetY = offsetX - 1;
let numX = col - offsetX;
let numY = (-row) + offsetY;
if (numX < 0) {
numX = `M${-numX}`;
}
if (numY < 0) {
numY = `M${-numY}`;
}

在以上的代码中,level为当前的层级,此层级下一行总的切片个数为math.pow(2, level)个,一半还需要除以2,这种方式和先计算level-1,再进行乘方运算等价。因此,就有了上述代码。

前面说到,这段代码正确的前提是orign定义在切片方案的左上角。那么,如果origin定义在[0,0]的话,还可以实现正确加载吗?关于此问题,感兴趣的可以自行探索一番。答案当然是可以的,转换方式参见下图即可。

convert-2

明确了以上信息之后,就可以正确的进行百度地图切片的加载了。使用以上的参数进行切片的加载,可以保证坐标精度十分精准。下图中,上方为百度坐标拾取页面,下方为自行加载页面,使用的坐标为(13267851.39994815, 3876775.155571565)。

baidu

map-sdk

PS

关于tileinfo中origin的定义,arcgis js api和openlayers对此的定义不尽相同。

在openlayers中对origin的定义是

The tile grid origin, i.e. where the x and y axes meet ([z, 0, 0]). Tile coordinates increase left to right and downwards. If not specified, extent or origins must be provided.

而在arcgis js api中对origin的定义是

The tiling scheme origin. The upper left corner of the tiling scheme, in coordinates of the spatial reference of the source data.

即,openlayers对origin的定义是行列号坐标原点,而arcgis js api中对origin的定义是切片范围的左上角点。因此,在使用arcgis js api来加载百度切片的时候,只能将origin定义在[-33554432, 33554432],并使用转换方式一。而在openlayers中,则两种方式都可以。




👨‍💻本站使用 Stellar 主题创建

📃本"页面"访问 次 | 👀总访问 次 | 🥷总访客

⏱️本站已运行 小时