esri-leaflet入门教程(5)- 动态绘制图形
by 李远祥
在上一章节中已经说明了esr-leaflet是如何加载ArcGIS Server提供的各种服务,这些都是服务本身来决定的,API脚本只是非常简单的调用。但如果要做一列的地图交互操作或者动态渲染等,那就必须使用地图区域跳转、查询结果渲染、动态添加图形等多种交互手段。而这些交互手段基本上离不开一些非服务类型的数据加载,我们可以将其成为动态要素。动态要素一般是在页面端进行动态绘制的。
动态要素这一说法并不是ArcGIS 或者leaflet的说法,而是笔者想了很久之后才编出来的一个名词,为的就是要使ArcGIS JavaScript API 体系与esri-leaflet能找到一个比较好的对应关系,便于ArcGIS的开发人员能够快速的切换过来。esri-leaflet 本身就是基于leaflet去做的扩展,因此,很多情况下都不能摆脱leaflet的限制,文雅点来说就是必须遵循leaflet的定义的接口规范。对于多年ArcGIS开发人员来说,刚开始的时候是有点不习惯的,因为很多时候某些功能和接口不能很好的映射回ArcGIS JavaScript API中。所以,要搞清楚esri-leaflet的使用,那就必须从leaflet本身入手。
在传统的ArcGIS JavaScript API中,要加载一些动态的要素(非直接引用服务的数据),必须使用graphic或者是graphicLayer(其实也是graphic的数组)。graphic在ArcGIS JS里面是由四个部分组成的,分别是geometry(图形)、symbol(符号)、attributes(属性)、infoTemplate(弹窗),如下图
虽然是四个参数,但并不一定要全部使用才能构建,一般来说最基本是需要一个geometry参数就行了,系统会自动给与graphic一个默认的symbol,这样构成一个最简单的graphic,就可以加载到地图上去了。总的来看,ArcGIS的API中是遵循ArcGIS数据的理念,图元(暂且这么说吧,真不知道中文怎么区分graphic和geometry)的显示是读取了要素的图形和属性,并且可以修改其符号(symbol),在地图交互时还可以绑定一些特定的弹出信息。
但在esri-leaflet中,从它提供的基础类来看,根本没有graphic和symbol,就连geometry都没有!这很让人抓狂。为了搞清楚这个关键问题,笔者特意去翻一遍leaflet的接口。终于在leaflet的基础类型中找到了关于图形的一系列接口 http://leafletjs.com/reference-1.0.3.html#layer,其分组也是相当的奇怪,名叫 Vector Layers ,乍一看还以为是一种矢量地图服务,如下图
这个里面就包含了所有的图形定义格式,传统的线、面、圆等都有了,但没有点,因为点是单独的在UI Layers 里面的Marker 。当然了没有arcgis定义的多,但基本也能满足了。再来看其定义是怎样的,点击polyline去查看其构建例子:
// create a red polyline from an array of LatLng points
var latlngs = [
[45.51, -122.68],
[37.77, -122.43],
[34.04, -118.2]
];
var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
// zoom the map to the polyline
map.fitBounds(polyline.getBounds());
通过上述代码可以发现几个问题,polyline的构建方式是需要传入点的数组,在构建的时候在其option参数中可以设置其样式,例子中就是使用了color参数,最后图形是可以直接作为Layer加载到地图中,可以与这个图形交互,通过fitBounds 方法跳转到polyline的图形位置。
也有人奇怪,点进polyline和polygon等的这些接口,居然没看到有类似的options可以实现color或者是宽度等设置。那是因为这些接口都在Path中实现,其他的如polyline、polygon、circle等都是实现了path的接口。可以查看下Path的options,如下图
可以看到传统的颜色、线宽、透明度等都可以在options里面设置。
从上面的资料基本上可以看到leaflet与ArcGIS的对应关系了。leaflet中的没有所谓的单独的图元一说,在动态要素来说,全部都是属于Layer,都实现了Layer的接口,其相当于ArcGIS体系中将graphicLayer和graphic 融合在一起,leaflet所谓的图元就是ArcGIS的 graphicLayer+graphics。这感觉还是有点乱,但基本上可以了解leaflet和ArcGIS JS体系的差异。
那么问题来了,由于esri-leaflet 中是没有实现类似ArcGIS JavaScript API中的graphic、geometry、symbol等接口,所有的操作都必须在leaflet的基础体系下完成,那这个代码该怎么编写?
我们可以通过查看esri-leaflet的例子,看看它是如何实现前端加载的。笔者查看的例子在 http://esri.github.io/esri-leaflet/examples/styling-feature-layer-polylines.html 可以看看其截图效果,对道路进行了动态的渲染。
再来看关键代码实现部分
var map = L.map('map').setView([45.5275, -122.6717], 14);
L.esri.basemapLayer('Streets').addTo(map);
var bikePaths = L.esri.featureLayer({
url: 'https://services.arcgis.com/uCXeTVveQzP4IIcx/ArcGIS/rest/services/Bike_Routes/FeatureServer/0',
style: function (feature) {
var c,o = 0.75;
switch (feature.properties.BIKEMODE) {
case 'Low traffic through street':
c = '#007D7D';
break;
case 'Bike boulevard':
c = '#00FF3C';
break;
case 'Caution area':
c = '#FF0000';
break;
case 'Local multi-use path':
c = '#00BEFF';
break;
case 'Regional multi-use path':
c = '#b1a9d0';
break;
case 'Moderate traffic through street':
c = '#FFEB00';
break;
case 'Planned multi-use path':
c = '#000000';
break;
case 'Bike lane':
c = '#328000';
o = '0.70';
break;
case 'High traffic through street':
c = '#FFA500';
break;
case 'Planned bike lane':
c = '#000000';
o = '1.0';
break;
default:
c = '#C0C0C0';
}
return {color: c, opacity: o, weight: 5};
}
}).addTo(map);
可以关键部分是获取了ArcGIS 的Featureservice 的要素,针对要素进行了style的设置,做了一个分等级和颜色的渲染。关键部分是使用了L.esri.featureLayer 接口。从这里可以看到端倪,对于动态的要素的加载,esri也是遵循了leaflet的规则,特意搞什么graphiclayer之类的特殊图层,而是想leaflet一样,直接在Layer中实现,当然这个Layer就是自家的featurelayer了。那传统的graphic前端绘制怎么办?esri-leaflet没有给出答案,笔者看到所有的基于后台服务查询的结果最终都是以featurelayer的形式展示出来,esri-leaflet中根本就没有在客户端绘制图形的接口。其实这部分答案很明确,就是使用leaflet本身的Vector Layers ,开发人员可以直接将其等同于graphiclayer就行了。
为了搞清楚真相,笔者又专门查找了 L.esri.featureLayer 的相关说明,发现其options 的属性里面的style一项居然是实现了leaflet本身的ILayer接口,如下图所示
前面我们看到的Vector Layers 部分其实也是实现了ILayer,也就是说,不管是采用什么样的方式去做动态要素的加载和交互操作,最终还是会落到ILayer和Vector Layers 中去,从ArcGIS的实现方法来看,可见一斑了。
原理清楚之后,所以的问题都可以迎刃而解了,关于代码实现部分就变得异常的简单了。接下来就可以用自己的数据按照例子进行制作一遍。例如笔者现在要按照道路的等级对道路进行实际的渲染,分别采用不同的颜色和粗细来渲染。代码很简单,跟例子差不多,不过就是替换成自己的数据。
function addFeatures() {
var featlayer = L.esri.featureLayer({
url: 'http://localhost:6080/arcgis/rest/services/dongguan/FeatureServer/2',
style: function(feature) {
var c, w, o = 0.75;
switch(feature.properties.type) {
case '44000':
c = '#007D7D';
w = 3;
break;
case '45000':
c = '#00FF3C';
w = 2;
break;
case '51000':
c = '#FFA500';
w = 3;
break;
default:
c = '#C0C0C0';
w = 1;
}
return {
color: c,
opacity: o
};
}
});
map.addLayer(featlayer);
}
这里要注意几点,首先要留意的是esri-leaflet是如何获取要素的属性的,代码中可以看到参数中使用的是 feature.properties.type 其中feature.properties是获取属性的方式,后面加点,接着是字段名(笔者的服务里面使用的是type字段分类)。可以通过arcgis server 的服务路径查看服务的字段情况,例如 http://localhost:6080/arcgis/rest/services/dongguan/FeatureServer/2 道路的要素图层信息如下图所示:
这里还要注意一点就是,arcgis server发布的服务,无论原始数据的字段是否大小写,都会一律转为小写字母,所以要特别注意。还有数据中千万别使用中文作为字段名称,这个大家懂的^_^ 。那么可以看看接下来的效果了,如下图所示
接下来就是自定义的图形加载地图上了。前面已经提及过,这类型的加载方式必须使用leaflet的方式进行。例如我们可以通过以下代码去加载一个面图形,并将其加到地图上面。
function addPolygon(){
var latlngs = [[23.17, 113.45],[23.15, 113.46],[23.19, 113.46],[23.19, 113.45]];
var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
}
其实际效果如下:
这里值得注意的有几点,一是面图形的构建是一个二维数组,数组元素记录的是xy坐标,但不是[经度,纬度],而是恰好反过来,[纬度,经度]。另外,如果要清除掉动态绘制的图形,那就必须使用ILayer的接口,就是在构建面的时候,其实声明的就是一个Layer,可以利用Layer的remove方法进行清除,在该代码实例中要清除前端的图形,那就是使用 polygon.remove();
由于leaflet对点的绘制做了特别的关照,将它放在UI Layer 而非Vector Layers 中,属于非常个性的部分,如果要加载点的数据,也是可以使用leaflet的方法来构建。但是arcgis server 中的点要素服务,如果要直接动态加载,那L.esri.featureLayer 接口中也有特殊的做法。例如如下核心代码
L.esri.basemapLayer('Streets').addTo(map);
L.esri.featureLayer({
url: 'https://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Trimet_Transit_Stops/FeatureServer/0',
pointToLayer: function (geojson, latlng) {
return L.marker(latlng, {
icon: icons[geojson.properties.direction.toLowerCase()]
});
},
}).addTo(map);
可以看到,在featurelayer中使用了一个方法是pointToLayer ,强行将esri格式转为L.marker 。说到底还是使用了L.marker接口,只不过是再封装了一层转换而已。具体的代码可在 http://esri.github.io/esri-leaflet/examples/styling-feature-layer-points.html 进行查看。
上面的很多代码都是基于arcgis 的featureservice 是制作的,但也有人会说,featureservice的发布需要后台有ArcSDE支撑,如果没有ArcSDE的数据源,那岂不是连基本的动态要素绘制都不能做?其实不然,这个问题Esri的工程师应该早就想到。看接口名称叫 L.esri.featureLayer ,不代表它只能支持featureservice,在上述的动态要素加载的代码中,将featureservice改为使用mapservice,同样可以使可运行的,这个只是命名的名称让大家产生误会而已。笔者也特意尝试了一把,将原来的路径替换为 http://localhost:6080/arcgis/rest/services/dongguan/MapServer/2 ,还是能出来原来的结果,因此无需虚惊一场。
总结:总的来说,esri-leaflet在动态要素加载方面还是坚持的不错,尽管大部分的操作都是使用leaflet原有的接口实现。对于从来没有过ArcGIS开发经验的人来说,毫无历史负担,即学即用。对于ArcGIS老鸟来说,还是需要一些时间去琢磨,毕竟一些使用习惯和叫法发生了一定改变。