说明:本月的主要工作都是围绕制作矢量切片这一个核心问题进行的,所以2月的主题就以这个问题为主,目前分支出来的一些内容主要包括了TMS(Tile map service),OpenLayers3中的Projection和Resolution以及proj4js在OpenLayers3中的应用,这些在这篇文章之后会继续展开,作为本月的番外内容。
一、GIS数据与OGC标准地图服务
本节主要是介绍一些基础的数据概念以及基本的WebGIS地图服务,如对这些内容已经熟知,可直接跳过本节。
1)GIS中的矢量与栅格数据
熟悉GIS的人应该都知道,在GIS中的数据分类有很多种方式,其中最常用的一种是根据数据组织结构方式的不同而分类成矢量数据和栅格数据的两种类型。其中栅格数据以二维矩阵的形式来表示地理空间信息的数据结构,其中数据的最小存在单元是以像素的形式存在,可以理解为和图片的组织结构类似,以分辨率等特征作为精度的定义标准。
栅格数据 矢量数据
而矢量数据则是试图利用点、线、面等几何要素来表现这个世界,其数据结构紧凑精准,数据图形质量好,有利于地理信息检索与网络传输等。其中矢量数据的最小单元是以点的形式存在,点构成线,线组成面,面构造出体。所以,我个人看来矢量数据应该更贴近于信息的精准分析与计算,而栅格数据则偏重于信息的表达(主要受制于当前图像处理技术的瓶颈)。
2)OGC地图服务
在WebGIS中,访问数据是通过访问服务器端的数据库来获取数据,鉴于GIS数据的特殊性,在这一套标准的请求响应模型中引入了一套基于OGC标准的地图服务标准,在这里我只介绍几个与本文有关的服务(如果你需要了解一个完整的内容,在这里可以找到很多资料:http://blog.csdn.net/wildboy2001/article/details/7743350):
- WMS(Web Map Service)
Web地图服务,利用地理空间信息的数据输出地图,地图本身只是一张图片,其中包括了图片的宽高、坐标系统、图片格式以及渲染方式,也正是因为本身的简洁性,读取传输速度都比较快,要高于WFS
//WMS请求实例
http://localhost:8080/geoserver/szdata/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image/png&TRANSPARENT=true &LAYERS=szdata:DLZXX_2011_PL_10000_3857&SRS=EPSG:4326&STYLES=&WIDTH=1347&HEIGHT=336 &BBOX=113.68754425048829,22.5346435546875,114.61245574951172,22.7653564453125
- WFS(Web Feature Service)
Web要素服务,请求获取要素,最小单元是以要素的形式存在的,用户可以通过与——请求获得的矢量数据——在前端渲染绘制的几何图形进行交互,从而达到对矢量要素的控制。
//WFS请求实例
http://localhost:8080/geoserver/wfs?service=WFS&request=GetFeature&version=1.1.0&outputFormat=application/json &typename=szdata:DLZXX_2011_PL_10000_new3857
- TMS(Tile Map Service)
(详细内容见番外篇)
所以,为了带来更好更快的用户体验,目前许多主流WebGIS应用都采用了栅格切片技术,通过缓存切片的形式使得地图数据的浏览体验更顺畅。打开你的浏览器,F12出控制台,进入任意一家地图供应商提供的地图应用,你会发现大部分的地图数据都是以切片的形式请求获得的,我在这里就不举例了。栅格地图的切片应用是很广泛,可在我们的日常工作中遇到的需求往往要比这些功能需求较浅的商业性地图复杂,有的时候用户甚至会提出需要地图配色的编辑修改功能这样的需求,这是商业主流地图所达不到的,因为栅格切片在完成切图之后,你所能控制的最小单位是一张图片,失去了对图片上地理信息的交互能力。
总结来看,栅格切片存在以下的几个缺点:
- 地图数据一次渲染,无法修改
- 无交互能力
那可否用WFS来替代呢?直接用WFS请求获取矢量数据,这样不就获得了交互能力吗?当然,如果在你的应用中矢量数据量不大的情况下,这样做也是可行的,但是当一旦数据量大了起来,前端对于数据的请求和响应处理渲染会提高客户端的硬件门槛,而频繁的交互操作也会对服务器产生压力。
直接加载的矢量数据与对栅格地图进行切片这两种方式看起来好像有些互补,如果能将这二者结合起来的话应该会很美好: 矢量+切片=矢量切片。
二、矢量切片
1)什么是矢量切片?
和栅格切片一样的思路,以金字塔的方式切割矢量数据,只不过切割的不是栅格图片,而是矢量数据的描述性文件,目前矢量切片主要有以下三种格式:GeoJSON,TopoJSON和MapbBox Vector Tile(MVT)。
栅格切图后文件存储形式
矢量切图后文件存储形式
切片中的数据结构
从上面的两张图可以看出,其实思路是一致的,因此,矢量切图结合了矢量数据与栅格切图的优势互补:
- 前端缓存切片,提高地图使用的体验
- 粒度上来看,矢量切图继承了矢量数据的特性,以要素为单位进行管理,加强了细节上的把控能力
- 在保证体验的前提下,为用户提供地图数据样式动态修改的功能,加强了地图定制化的程度
- 数据的实时性
2)如何生成矢量切片?
目前就博主所知道的矢量切片生成方式共有以下几种:1)ArcGIS 系列产品:利用ArcGIS Pro生成矢量切片,然后发布在ArcGIS Online上;2)Mapbox,目前已经提出了一套开放的矢量切片标准,并被多个开源团队所接受,但具体的产品使用我并不熟悉,所以还请自行搜索资料,如果后期有接触到这方面,我会继续补充;3)GeoServer,在2.11beta版中出现了对矢量切片的支持,主要依赖于开源插件geoserver-2.11-SNAPSHOT-vectortiles-plugin以及内嵌的GeoWebcahce完成切片工作。4)自己编写切片工具,这主要针对于对这一套体系非常熟悉的GIS编程人员,我本人也属于入门级别,所以在此也没法提供更多详细的内容。本文主要采用的切片方式就是利用第3种方法,利用GeoServer作为GIS应用服务器生成矢量切片:
2-1)切片前需要确认的环境配置:
a) JAVA环境:GeoServer是一套基于JAVA环境下的开源项目,因此需要配置好JAVA环境,博主采用的是JAVA1.8,因为最新版的GeoServer文档中提出需要JAVA_8,低配版本(JAVA7)我也试过,但没有成功,所以为了稳妥起见,我推荐你使用JAVA8,特别要注意的是,GeoServer当前不支持JAVA9。
b) Tomcat:Web容器,没有限制,按自己的技术方向选择。
c) GeoServer 2.11以及其插件geoserver-2.11-SNAPSHOT-vectortiles-plugin
d) 矢量数据:我在此使用的是 深圳道路【EPSG:4326】的矢量数据,请特别注意括号中的坐标系,在后面我会具体讲到相关的问题。
2-2)切片过程
a) 首先,我们将下载好的GeoServer2.11的war包直接放入/%TomcatHome%/WebAPP的文件夹中,启动Tomcat完毕后,访问以下链接: http://localhost:8080/geoserver, 如果出现以下页面则说明部署成功。
b) 然后,我们将下载好的插件geoserver-2.11-SNAPSHOT-vectortiles-plugin 进行解压,然后将其直接copy到tomcat中部署的GeoServer文件夹的WEB-INF的lib文件夹下,重启tomcat;
c) 启动完成后,访问GeoServer主页中的数据菜单中的数据存储,在页面中选择添加新的数据存储,这些步骤和发布普通数据的步骤一致,在此就不赘述了。
但是在发布过程中有一个编辑图层的步骤需要注意,在进入编辑图层的页面之后,记得点击选中Tile caching选项卡,你会发现Tile Image Foramt属性中多出了几种数据格式,这就说明你的插件生效了。
d) 按照你的需求选中需要切片的格式,如果第一次切的话,我建议可以用GeoJSON作为入门,因为GeoJSON格式的数据可读性较强,能够给我们一个比较直观的认识。
e) 从左边菜单栏点击进入Tile caching的Tile Layer子项,在Tile Layer列表中你可以看到已经被你发布成功的Tile Layer,通过preview的选择下拉框中的选项你可以发现,之前发布时被选中的格式都出现了,这就说明你的切片数据已经发布成功了!
3)调用矢量切片
调用矢量切片的方式有很多种,OpenLayers3,Leaflet等等都可以达到目的(不过听说好像LeafLet目前仅支持WGS84投影坐标系的矢量切片),因此在这里使用的调用请求工具是OpenLayers3,具体代码如下:
var projection4326 = new ol.proj.Projection({ code: 'EPSG:4326', units: 'degrees', }); var defaultView = new ol.View({ projection: projection4326, center: [114.15, 22.65], //new ol.proj.fromLonLat([114.15, 22.65]), zoom: 11 }); function loadVectorTile(){
//参数设置:图层名称 / 投影坐标系 / 初始化样式 var layerName = 'szdata:DLZXX_2011_PL_10000_4326'; var layerProjection = '4326'; var initStyle = new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgb(163,204,25)', width: 5 }) }); //矢量切片图层 var vectorTile = new ol.layer.VectorTile({ title:"深圳道路-VectorTile", style: initStyle, projection: projection4326, //矢量切片数据 source: new ol.source.VectorTile({ projeciton: projection4326, format: new ol.format.GeoJSON(), tileGrid: ol.tilegrid.createXYZ({ extent: ol.proj.get('EPSG:4326').getExtent(), maxZoom: 22 }), tilePixelRatio:1, //发出获取切片的请求 tileUrlFunction: function(tileCoord){
return '/geoserver/gwc/service/tms/1.0.0/' +layerName+'@EPSG%3A'+layerProjection+'@geojson/'+(tileCoord[0]-1) + '/'+tileCoord[1] + '/' + (Math.pow(2,tileCoord[0]-1)+tileCoord[2]) + '.geojson'; } }) });
//构造Map对象
//你需要在页面中提供一个id='map'的div
map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM();
}),
],
view:defaultView,
controls:[
new ol.control.ScaleLine(),
new ol.control.ZoomSlider(),
new ol.control.LayerSwitcher(),
new ol.control.OverviewMap(),
new ol.control.Zoom()
],
});
map.addLayer(vectorTile);
}
//千万别忘记这是一个独立的JS文件,你需要将这一段JS代码以Onload的方式加载到页面中
上述代码中,在vectorSource的属性tileUrlFunction中,我对coordinate(一个存储了XYZ参数的数组)进行了一些简单的处理,从而使EPSG:4326请求下的XYZ能够与切图结果相匹配,有一些拼凑的嫌疑,导致了这段代码并不具有普适性,所以,请读者根据自己的情况进行调整,但整个大体思路基本一致,主要是切片行列号的匹配方面需要额外注意。
最后切片页面的效果如下(关于样式的问题,在OL3中你可以通过设置Layer的Style属性进行修改样式,和WFS请求获取的矢量数据处理方式一致,这也就实现了在完成切片缓存的前提下,得到了地图要素样式的控制权。):
矢量切片效果图
控制台请求响应过程
4)遇到的一些问题
4-1)我想检查一下我的切片数据,在哪里能够找到呢?
首先,你需要了解的是,完成切片工作的其实是GeoWebCache,而GeoWebCache的数据存储文件的默认路径一般为:
C:\Users\%YOUR-PC-NAME%\AppData\Local\Temp\geowebcache
你可以在这里面看到各个图层的切片数据,当你打开你的目标图层文件夹时,如果你发现是空的,千万不要着急,因为GeoWebCache中切片的生成本来就是一个动态的过程,
若实在是不放心,你可以通过进入GeoServer的管理页面-->左侧菜单栏中的Tile caching-->子菜单Tile Layers-->图层列表中选择一个Preview的方式进行预览,然后在预览界面进行缩放,你会发现刚才的文件夹里多了很多数据文件,这下你就可以放心的进行下面的调用工作了。但有一点需要提到的是:GeoServer目前还不提供矢量切片的预览,你通过查看预览页面的源码就能发现,当前预览功能中的代码还是采用的OpenLayers2的老代码,所以当你选择预览vector tile时,只能看到一个个的红叉。
4-2)我的切片数据调用不出来怎么办?
在确认了你的数据是没有问题之后,采用OpenLayers3代码进行调用,但仍有可能会出现资源地址不存在(即404错误)或者请求响应都成功了可是矢量切片的图层仍未加载到地图上。这些问题都和一个很关键的属性有关系,就是我上面提到的Projection——投影坐标系。在OpenLayers3 中有这么一些类是需要你提供投影参数的:
ol.View / ol.layer.Vector / ol.source.Vector
a)404资源不存在:最有可能的原因就是,你在在创建vectorTileSource对象时,url中设置的请求参数Projection与你数据视图的Projection不一致,举个例子,如果你发出的请求是以900913(Web-mercator)为切图投影,而你发布的数据所采用的投影是4326(WGS 84):
'http://localhost:8080/geoserver/gwc/service/tms/1.0.0/shenzhenDL_4326@EPSG:900913@geojson/{z}/{x}/{-y}.geojson'
导致了切出来的矢量切片的行列号是在EPSG:900913下产生的,由于EPSG:4326和EPSG:900913之间的不匹配,导致了你用4326的行列号参数XY去请求实际生成的900913的行列号切片,导致了无法准确的访问资源。当然还有一个需要提到的小细节:在创建vectorTileSource对象时,有一个非常重要的属性——tileGrid【ol.tilegrid】,这是用来创建切片的,如果你所切的图层资源的投影坐标系不是默认的EPSG:3857(也就是EPSG:900913),你需要额外的为你的tilegrid限制一个范围Extent,如上述代码中所写道的一样。因为在OpenLayers3中的源码中可以看出(ol.source.XYZ),所有的默认的投影坐标都是EPSG:3857,所以当你使用其他坐标系的数据时,需要将默认的参数全部按照你的投影坐标系进行重新声明,而OL3中也提供了一系列的Projection的转换方法。
b)请求响应都没有问题,但是图层并未加载出来:其实这个问题我暂时还没有完全解决,我的初步设想是因为我在GeoServer上发布数据时,原始数据的Projection并未被识别出来,导致了最终图层的无法加载,这可能与投影坐标的转换有关系,在后续我将继续完成这个问题的探索。
4-3) 如果我发布的数据采用的坐标系既不是EPSG:4326(WGS84),又不是EPSG:900913(Web Mercator),而是其他坐标系该怎么办呢?
之所以把这个问题列出来是因为在GeoServer中,默认的Tile grid切片格网只定义了4326和900913这两种类型,如果你需要更多其他投影坐标系下的切片,你需要在Geoserver的Tile Caching选项卡下的子选项tile grid中定义属于自己的切片格网,当然,OL3中也没有太多的投影可供你使用,与之相对应的,你需要利用Proj4js去定义你所需要的投影坐标系,然后在你的OL3代码中调用即可,具体关于Proj4js的内容,我会单列一篇文章进行介绍:矢量切片(Vector tile)番外一:Proj4js
5)总结
在这篇博客中,主要是简单地介绍了关于矢量切片的制作与使用方法,没有深入的去探索矢量切片的实现原理与算法,如果以后有时间,我会接着这篇博客继续学习关于切片算法以及实现过程的相关内容。
其实第一次接触矢量切片的时候,我并没有太多的直观认识,而网络上的资料也十分有限,能够找到的博客也是寥寥无几,一来是因为GIS开发较少,二来矢量切片的技术还处于一个初步发展的阶段,基本上都是在开源上使用,ArcGIS系列产品虽有涉及,但也处于一个探索的过程中,所以我写这篇博客既是为了总结之前的学习,也是为了让更多的人能够少走一些弯路。
以下附上我在学习过程中找到的比较有价值的资料:
矢量切片的制作过程:http://blog.csdn.net/qingyafan/article/details/53367204,这个博客主要生产的内容是关于WebGIS这一块的,应该是为数不多的GIS技术博主,并保持着一定的更新频率,若你对WebGIS有兴趣的话可以follow一下。
切片数据的初步研究: http://www.cnblogs.com/naaoveGIS/p/4982549.html,这个博客比较全面,GIS算法、数据库、中间件、Web以及开源都有涉及,风格也比较接地气,不过有些内容比较深入,不仅仅是停留在应用层面,所以读这个博客需要认真细致的去思考。