首页 > 代码库 > esri-leaflet入门教程(3)-自定义底图

esri-leaflet入门教程(3)-自定义底图

esri-leaflet入门教程(3)-自定义底图

by 李远祥

      在前面的篇章我们已经了解到了怎么样使用FlatUI、leaflet和esri-leaflet 去搭建简单的地图应用界面了。如果有着手写代码的朋友可能已经发现第一个Hello World 程序,使用的就是esri 的 Topographic 样式底图。底图这东西从来都是个好东西,放在互联网领域就是一个免费而好用的资源,而在专业GIS领域,它就是一个权威和标准,所有的地图和数据资源都必须叠加在底图之上。

      那么,问题来了。esri-leaflet提供的是自家的底图,其服务器放在美国,而且是英文样式,在中国区域的数据也不全,基本上无法使用。而大部分的国内用户要么考虑国内的免费地图,如天地图、高德等或者是内网用户使用自家发布的底图服务,那就无法直接通过 L.esri.basemapLayer 方法进行加载了,因为底图使用的是关键字的方式进行代入,而且是唯一替换的。也有人说可以变通一下,不使用底图,而是使用其 L.esri.tiledMapLayer 加载ArcGIS的切片服务。但这种方式比较麻烦,因为每次切换都必须重新控制图层的顺序,尤其是在底图之前叠加了数据之后,会让程序变得尤其复杂。

      leaf本身就是一个非常优秀的地图框架,其优美的接口设计以及良好的扩展性,确实值得称赞。仔细想一下,esri都能在其基础上定义自己的basemap,为什么我们不自己去动手做一下呢,只要动手,就会扩展底图其实并不困难。在esri-leaflet的介绍章节中,我们可以找到其源码下载的地址,并下载api的源码。笔者试用的是 esri-leaflet-2.0.7 版本,解压后可以看到其目录结构如下图

技术分享

      其中笔者直接使用的是js脚本就是在dist中分发出来的 esri-leaflet.js 文件,经过了混淆之后,这个文件非常小。不过这不是我们研究的对象,要看清楚esri是怎么扩展leaflet的底图,就必须参考 src 目录下的源码,其中一个就是Layers目录,里面的js都是对leaflet的图层接口进行适配,如下图,找到 BasemapLayer.js 文件,用文本编辑器打开,就可以窥探其内部。

技术分享

技术分享

      通过这个代码片段,可以看到esri的BasemapLayer 其实就是扩展自leaflet的 L.TileLayer ,并且遵循其编写规范。以其“Streests”底图为例子,可以看到其底图定义的结构

Streets: {
        urlTemplate: tileProtocol + ‘//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}‘,
        options: {
          minZoom: 1,
          maxZoom: 19,
          subdomains: [‘server‘, ‘services‘],
          attribution: ‘USGS, NOAA‘,
          attributionUrl: ‘https://static.arcgis.com/attribution/World_Street_Map‘
        }
      }

      其中Streets 为底图的名称,这对应的就是底图选择接口中的key值,urlTemplate 对应的就是地图服务路径,当然了,这个路径不完全是ArcGIS Server发布的切片服务地址,需要在原来完整的路径后面补上  /tile/{z}/{y}/{x}  其基本写法就是 “【ArcGIS切片服务路径】/tile/{z}/{y}/{x} “ ;而接下来的 options 参数配置就是使用底图时的一些配置参数,例如指定最大和最小的比例尺,子服务器选择等,这些都是可以定义的。如果想了解得更加什么,那就直接到leaflet官网上去看 其 tileLayer 接口,附上参考网址 http://leafletjs.com/reference-1.0.3.html#tilelayer  可以看到相关的说明,跟esri扩展的BasemapLayer是一样的。

      了解完原理之后,那接下来就非常好办了,那就直接进入动手环节。如果想自己也像esri一样去适配自己的底图,可以参考它的写法。当然了,如果技术到家,可以自行编写一个适配的js ,例如前面说到在leaflet官网上有一个名叫“Tao Huang” 的人就自行写了一个适配中国天地图、高德地图以及geoq地图的扩展。如果需要考虑到程序的优美和可读,建议自行编写js。但如果想简单粗暴的实现,笔者也有一个非常快捷的方式,就是直接修改混淆过的 esri-leaflet.js 文件,在其基础上加入自定义的底图服务。

      在IDE中打开esri-leaflet.js ,笔者使用的就是HBuilder ,因为它里面有一个功能可以帮助我们重新排版代码,如下图

技术分享

整理之后原本那些混淆的代码就会以一种程序员可以阅读的格式出现了。其他的一些JS IDE 也是可以实现的,主要是看使用习惯。整理后可以看到代码已经变得比较整齐了。通过查找关键字定位到前面所说的BasemapLayer扩展的位置,如下图

技术分享

      esri-leaflet 的所有底图服务都在这里重新定义了一遍。所以,接下来只需要依葫芦画瓢,将 Streets代码块拷贝多次,并在这个基础上进行修改就行了。为了适配国内免费的底图服务,可以直接在此追加上笔者以下的代码

      Streets: {
            urlTemplate: yt + "//{s}.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: ["server", "services"],
              attribution: "USGS, NOAA",
              attributionUrl: "https://static.arcgis.com/attribution/World_Street_Map"
            }
          },
          //自定义中国灰色地图(ArcGIS Server切片服务,来自arcgisonline.cn)
          chinagray: {
            urlTemplate: "http://cache1.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineStreetGray/MapServer/tile/{z}/{y}/{x}",
            options: {
              minZoom: 1,
              maxZoom: 19,
            }
          },
          //自定义中国彩色地图(ArcGIS Server切片服务,来自arcgisonline.cn)
          chinacolor: {
            urlTemplate: "http://cache1.arcgisonline.cn/ArcGIS/rest/services/ChinaOnlineStreetColor/MapServer/tile/{z}/{y}/{x}",
            options: {
              minZoom: 1,
              maxZoom: 19
            }
          },
          //天地图矢量地图服务
          TianDiTuVec: {
            urlTemplate: "http://t{s}.tianditu.cn/DataServer?T=vec_w&X={x}&Y={y}&L={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘]
            }
          },
          //天地图矢量标注服务
          TianDiTuVec_A: {
            urlTemplate: "http://t{s}.tianditu.cn/DataServer?T=cva_w&X={x}&Y={y}&L={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘]
            }
          },
          //天地图卫星底图服务
          TianDiTuSat: {
            urlTemplate: "http://t{s}.tianditu.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘]
            }
          },
          //天地图卫星影像标注
          TianDiTuSat_A: {
            urlTemplate: "http://t{s}.tianditu.cn/DataServer?T=cia_w&X={x}&Y={y}&L={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘]
            }
          },
          
          //高德矢量地图服务
          GaodeVec: {
            urlTemplate: "http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘]
            }
          },
          //高德矢量地图服务
          GaodeVec_A: {
            urlTemplate: "http://webst0{s}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}",                          
            options: {
              minZoom: 1,
              maxZoom: 19,
              subdomains: [‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘]
            }
          },

      这个代码都是比较好阅读的了,如果还有自己发布的ArcGIS Server切片服务作为底图,可以按照这个方式追加进去。追加完之后,唯一要记住的就是这些底图的名称(底图的名称也是可以自定义,所以,一定要定义一个比较好记的名称,方便自己也方便别人)。

      接下来就看怎么调用了。首先底图的调用还是使用esri-leaflet的方式进行调用,毕竟我们是直接在其js上面追加的,所以规范还是要保持一致。

map = L.map("mapDiv").setView([23.1, 113.2], 9);//定位在广州
customBaselayer = L.esri.basemapLayer(‘chinacolor‘).addTo(map);

      代码中的chinacolor就是之前定义的底图的名称。这样在加载的时候就变成了彩色版的中国地图了。如下图

技术分享

      下一步马上就是实现底图的切换功能了。按照esri-leaflet 和leaflet上的例子,都是使用L.control 来实现,于界面上来看非常的丑陋。所以,我们采用bootstrap 或 FlatUI 的菜单来实现。这样会比较美观。先来看看以下定义的这个切换方法

//切换基础底图
      var map;
      var customBaselayer; //底图图层
      var layerLabels;     //底图标注
      function setBaseMap(idName){
        var mapid= idName.id;
        if(customBaselayer){
          map.removeLayer(customBaselayer);
        }
        if(layerLabels){
          map.removeLayer(layerLabels);
        }                
        if(mapid=="chinagray"){
          customBaselayer= L.esri.basemapLayer("chinagray");
          map.addLayer(customBaselayer);
        
        }else if(mapid=="chinacolor"){
          customBaselayer= L.esri.basemapLayer("chinacolor");
          map.addLayer(customBaselayer);  
        }else if(mapid=="TianDiTuVec")
        {          
          customBaselayer= L.esri.basemapLayer("TianDiTuVec");
          map.addLayer(customBaselayer);  
          layerLabels = L.esri.basemapLayer("TianDiTuVec_A");
          map.addLayer(layerLabels);  
        }else if(mapid=="TianDiTuSat")
        {          
          customBaselayer= L.esri.basemapLayer("TianDiTuSat");
          map.addLayer(customBaselayer);  
          layerLabels = L.esri.basemapLayer("TianDiTuSat_A");
          map.addLayer(layerLabels);  
        }else if(mapid=="GaodeVec")
        {          
          customBaselayer= L.esri.basemapLayer("GaodeVec");
          map.addLayer(customBaselayer);  
          layerLabels = L.esri.basemapLayer("GaodeVec_A");
          map.addLayer(layerLabels);  
        }
      }      

      这段代码是在全局设置了map和custombaselayer,这样方便我们在其他地方调用。setBaseMap 方法里面带的参数就是dom对象,只要触发了点击,就传触发的dom给该方法,该方法通过dom的id来判断。所以,无论是菜单还是按钮或者是标签等,都必须给一个与底图名称一样的id名称。先来判断一下是否有底图,如果有就先移除后添加。还有就是天地图和高德地图都是讲切片和标注分开的,因此,要对这类切片和标注分开的地图服务进行处理,在切换的时候同时加上其标注服务。

      接下来再看在HTML页面代码中的菜单,在其标签上加入调用代码,如下

<li class="dropdown">
         <a href="#" class="dropdown-toggle" data-toggle="dropdown">底图切换 <b class="caret"></b></a>
         <ul class="dropdown-menu">
          <li>
           <a id="chinacolor" href=‘JavaScript:void(0)‘  onclick=‘setBaseMap(this)‘>中国地图彩色版</a>
          </li>
          <li>
           <a id="chinagray" href=‘JavaScript:void(0)‘  onclick=‘setBaseMap(this)‘>中国地图灰色版</a>
          </li>
          <li>
           <a id="TianDiTuVec" href=‘JavaScript:void(0)‘  onclick=‘setBaseMap(this)‘>天地图矢量地图</a>
          </li>
          <li>
           <a id="TianDiTuSat" href=‘JavaScript:void(0)‘  onclick=‘setBaseMap(this)‘>天地图影像地图</a>
          </li>
          <li>
           <a id="GaodeVec" href=‘JavaScript:void(0)‘  onclick=‘setBaseMap(this)‘>高德地图</a>
          </li>
         </ul>
        </li>

     简单的修改,就可以使用esri-leaflet来实现arcgisonline.cn 、天地图、高德地图的切换了。效果如下图所示

技术分享

      总结:leaflet是一个非常优秀的框架,扩展性非常好。如果想好好的使用,可以通过参考它官网上的plugins ,通过参考别人的代码去进一步完善其功能。而笔者在本章中使用的方式也就是直接参考esri在GitHub上上传的源码,并修改其发布的代码版本,也算是一种投机取巧的方法吧。

esri-leaflet入门教程(3)-自定义底图