手绘地图制作的关键点之“图层覆盖”

 前面介绍了《景区手绘地图(电子地图、智慧导览系统)如何制作》以及《景区手绘地图的绘制流程》,接下来介绍一些手绘地图制作的关键点。

手绘地图最关键的一点,就是把手绘地图准确的覆盖到地图平台。

​手绘地图体验

一、地图开放平台接口

这是首要的关键点。

通过前面的介绍,我们知道,手绘地图是基于地图平台针对某一区域进行美化及手绘,从而生产一张精美的手绘图片文件。但这个文件并不能拿来直接使用,而必须要覆盖到地图平台之上才可发挥其价值。

要实现这个目的,我先简单介绍几个概念(这些概念在前面的文章都有比较详细的介绍,这里只做简单的回顾):

1.瓦片图

瓦片图是256像素的正方形图片,整个地图都是由瓦片图构成的。用瓦片图原因是,可按需要加载(按屏幕显示范围内的区域加载),解决的问题是,服务器及客户端设备内存问题,以及网络流量问题。

2.地图开放平台

高德、百度、腾讯、谷歌等等地图平台,都有开放的接口。通过这些开放接口,便可实现让手绘图覆盖到地图平台上。

 ​高德地图demo

二、手绘地图覆盖到地图平台的方式

每个地图开放平台都提供了多种把手绘地图覆盖到平台的实现方式。

此处以高德地图为例说明。高德地图一共提供了这么多接口,分别都可以实现自定义图层覆盖到地图平台:

AMap.TileLayer.Flexible
AMap.ImageLayer
AMap.CanvasLayer
AMap.VideoLayer
AMap.CustomLayer

根据我的经验,在手绘地图覆盖这个场景,比较适用的是:

AMap.TileLayer.Flexible 
AMap.ImageLayer

这两个接口,分别对应上文描述的“瓦片图实现地图”及“一整张图实现地图”。ImageLayer这个接口,可以实现把整张手绘地图覆盖到地图平台上。事实上,依我的了解,业内也有采取这个解决方案的系统。但是就个人而言,我更推荐接口TileLayer.Flexible,此接口就是采取瓦片图的方案来把手绘地图覆盖到地图平台。

ImageLayer接口代码如下所示(高德官方代码),由其图片参数可见,是一张整图:

var imageLayer = new AMap.ImageLayer({
    url: '//amappc.cn-hangzhou.oss-pub.aliyun-inc.com/lbs/static/img/dongwuyuan.jpg',
    bounds: new AMap.Bounds(
        [116.327911, 39.939229],
        [116.342659, 39.946275]
    ),
    zooms: [15, 18]
});

TileLayer.Flexible接口代码如下所示:

var tileLayer = new AMap.TileLayer.Flexible({
    cacheSize: 30,
    zIndex: 200,
    createTile: function (x, y, zoom, success, fail) {
        var imagePath = tileHost + '/uploades/map/" + zoom + "/tile" + x + "_" + y + 'amap.png';
        var img = document.createElement('img');
        img.onload = function () {
            success(img);
        };
        img.crossOrigin = "anonymous";
        img.onerror = function () {
            fail();
        };
        img.src = imagePath; 
        success(img);
    }
}); 

此接口有createTile方法,开放了3个参数,就是至关重要的地图层级zoom,以及瓦片图的坐标x/y。通过这3个参数,我们就可以把整张手绘地图切为瓦片图来覆盖到地图平台之上,达到用户使用时也按需加载的效果。

​三个参数决定瓦片图

三、更复杂和完善的手绘地图覆盖的效果

高德地图的TileLayer.Flexible接口还有一个参数,便是success这个函数。在createTile方法里,我们其实还可以自定义更加复杂的内容,然后传入success这个函数,高德地图会自动把传入的对象渲染到瓦片里。

根据这个原理,我们可以实现更加复杂的需求,如:因为国内无法正常访问谷歌地图,而高德、百度等地图在国外访问网络延迟严重且地图数据欠缺,所以对于国外的区域或景区这种场景,我们就可以通过此接口实现全球可访问的手绘地图。

具体的实现方式如下:

var tileLayer = new AMap.TileLayer.Flexible({
    cacheSize: 30,
    zIndex: 200,
    createTile: function (x, y, zoom, success, fail) { 
        var div = document.createElement('div');

        var img2 = document.createElement('img');
        img2.setAttribute("crossOrigin", 'Anonymous');
        img2.onload = function () {
            div.appendChild(img2);
        };
        img2.style = "position:absolute;width:256px;height:256px;z-index:-2;";
        img2.onerror = function () {
            fail()
        };
        img2.src = tileHost + '/uploades/map/map_'+ mId +"/" + zoom + "/tile" + x + "_" + y + 'amapgoogle.png';
          
        var img = document.createElement('img');
        img.onload = function () {
            div.appendChild(img);
        };
        img.style = "position:absolute;width:256px;height:256px;z-index:-1;";
        img.crossOrigin = "anonymous";
        img.onerror = function () {
            fail()
        }; 
        img.src = tileHost + '/uploades/map/map_'+ mapId +"/" + zoom + "/tile" + x + "_" + y + 'amap.png';

        success(div); 
    }
}); 

这样就实现了加载两个瓦片图,重叠到瓦片区里。其中除了我们自己的手绘地图的瓦片之外,另外的瓦片图是谷歌地图的底图瓦片。

谷歌地图在国内无法访问,我们可以采用把它缓存到服务器的方案,这样就可以达到国内能通过高德地图来实现访问谷歌地图的底图。而国外区域的用户,则直接调用谷歌地图。这样实现国内国外均能访问。

顺便说一下,谷歌地图的瓦片图加载方案,也是大同小异的。

这里另外有一个需要注意注意的点就是,怎么判断当前用户是在国内还是国外。可以通过经纬度判断,可以通过IP判断。我建议通过经纬度,更加直观明了且准确。但是中国的版图区域,是一个很复杂的多边形,因此,需要一些较为复杂的计算。这个点现在就不展开,后面有机会再细说。


​复杂的多图瓦片

四、手绘地图覆盖到平台的关键技术点

通过上文可知,手绘地图要覆盖到平台,最关键的点就在于瓦片图的层级、x/y坐标。

而这一点,其本质又是手绘地图所在区域的经纬度所决定的。

因此,在《景区手绘地图(电子地图、智慧导览系统)如何制作》以及《景区手绘地图的绘制流程》都有所提及或强调,手绘地图底图的获取,可以基于系统直接下载。然后设计师在设计手绘地图的时候,需要特别注意,不能随意修改手绘地图的像素尺寸。因为修改之后,就会导致区域发生变化,然后经纬度就会出现误差。

在下载地图底图的时候,系统已知地图的经纬度,因此系统在对完成的手绘地图进行切片的时候,就会从默认记忆的经纬度开始计算,通过地球的经纬度、墨卡托坐标、像素偏移三者的转换关系,计算出当前手绘图层的第一张瓦片的偏移位置。找到此关键之后,后面切图就非常简单,只需要以256像素为依据,进行累加切图即可。

这里需要注意的是,实际上256像素的瓦片,在手机上的显示效果并不佳,看上去不是很清晰。这里涉及手机屏幕和PC屏幕硬件及显示像素的差异原理。解决此问题不难,这里便不展开说明了。


​瓦片图准确覆盖

五、更好的图层加载体验

因为手绘地图打开之后,要加载的瓦片图很多,即便是只加载可视区域内的瓦片图,数量也是不少。因此,为了用户在使用手绘地图时有更好的放大、缩小查看的体验,我们可以使用预加载瓦片图方案。

具体的方案就是,系统在完成用户当前所在层级的瓦片图加载之后,便可以对相邻的大一级和小一级的瓦片图进行加载。因为知道当前层级的经纬度,因此也能计算出相邻的两个级别的经纬度,进而对相应的瓦片图进行加载。加载之后,当用户放大或缩小手绘地图层级时,便可快速显示瓦片图而不需要再临时加载,获得“网络更快”的体验。

实现预加载的核心代码:

for (x = xMin; x <= xMax; x ++) {
    for (y = yMin; y <= yMax; y ++) {
        if (preloadPics[x + '_' + y]) {
            continue;
        }
        preloadPics[x + '_' + y] = true;
        var picUrl = tileHost + '/uploades/map/'+ zoom +'/tile'+ x +'_'+ y +'amap.png';
        (new Image()).src = picUrl;
    }
}

​预加载的两个相邻层级的瓦片图

六、结尾

手绘地图覆盖到底图的方式,其实还有更多,但根据我有限的经验,觉得其他方式都没有TileLayer.Flexible接口灵活和实用,因此不再过多赘述。

另外说一点,这个接口的复杂效果,个人经验是高德地图支持最完善。因此一直以来我都首推高德地图作为手绘地图平台。

在本文最后,衷心感谢各大地图开放平台。他们提供了大量可免费使用的资源,使得无数企业作为服务商可以为更多各行各业的需求方提供各种各样的解决方案,创造了难以估量的经济价值,体现了大企业的责任和担当。

最后,列一些开放地图平台文档:

高德地图:概述-地图 JS API | 高德地图API

百度地图:地图 JS API | 百度地图API SDK

腾讯地图:JavaScript API | 腾讯位置服务

谷歌地图://developers.google.com/maps/documentation/javascript