geotrellis使用(三十四)矢量瓦片技术研究——矢栅一体化
前言
本文所涉及技术与Geotrellis并无太大关系,仅是矢量瓦片前端渲染和加载技术,但是其实我这是在为Geotrellis的矢量瓦片做铺垫。很多人可能会说,Geotrellis为什么要搞矢量瓦片,这不就是前端展示吗。其实不然,首先Geotrellis可以用分布式技术进行快速矢量瓦片切割,当然这不是主要的,因为单台服务器基本也能很快处理矢量瓦片的切割,重要的是Geotrellis可以使用矢量瓦片进行空间计算,这样可以矢栅一体化,矢量瓦片和栅格瓦片同时进行计算,这个东西就厉害了,将大大的提高空间数据分析的可能性。当然这只是我个人的看法,有待后续研究,并且Geotrellis的矢量瓦片还并在测试当中。本文仅介绍前端矢量瓦片技术。
一、什么是矢量瓦片
目前高德、百度等互联网地图基本都使用了矢量瓦片技术。先来看一下Wiki中的介绍:
Vector tiles, tiled vectors or vectiles are packets of geographic data, packaged into pre-defined roughly-square shaped "tiles" for transfer over the web. This is an emerging method for delivering styled web maps, combining certain benefits of pre-rendered raster map tiles with vector map data. As with the widely used raster tiled web maps, map data is requested by a client as a set of "tiles" corresponding to square areas of land of a pre-defined size and location. Unlike raster tiled web maps, however, the server returns vector map data, which has been clipped to the boundaries of each tile, instead of a pre-rendered map image.
There are several major advantages of this hybrid approach. Compared to an un-tiled vector map, the data transfer is reduced,because only data within the current viewport, and at the current zoom level needs to be transferred. The GIS clipping operations can all be performed in advance, as the tile boundaries are pre-defined. This in turn means that tiled vector data can be packaged up and distributed, without needing any kind of GIS system available to serve data.
Compared to a tiled raster map, data transfer is also greatly reduced, as vector data is typically much smaller than a rendered bitmap. Also, styling can be applied later in the process, or even in the browser itself, allowing much greater flexibility in how data is presented. It is also easy to provide interactivity with map features, as their vector representation already exists within the client. Yet another benefit is that less centralised server processing power is required, since rasterisation can be performed directly in the client. This has been described as making "rendering ... a last-mile problem, with fast, high-quality GPU[s] in everyone’s pocket".
简单的说就是将矢量直接切割成如栅格瓦片一样大小的块,这种切割同样是按照空间来进行的。优势就是在于继承了栅格瓦片的所有优点后,还不需要事先定义样式进行矢量数据栅格化,能够在用户浏览器随意配置显示样式,减轻服务器端计算压力,缩小服务端存储空间(栅格图片占用大量存储空间),并且可以实现用户交互。
这些就是矢量瓦片的优势,当然不是说矢量瓦片绝对是个好东西,任何事情都要辩证的区看待,对待任何问题都要深入研究,找出最优解。如栅格数据(遥感影像等)永远需要使用栅格瓦片,某些不需要交互、不怎么变化等情况的矢量数据也可以使用栅格瓦片。
二、前端显示技术
矢量瓦片的生成还未研究,本文只是调用OSM公开发布的矢量瓦片进行前端展示试验。
目前开源中矢量瓦片做的比较好的是Mapbox,各种渲染技术也基本以Mapbox定义的矢量瓦片标准为标准。Leaflet有多款插件支持矢量瓦片,Leaftlet是一款开源的前端地图渲染引擎,主要支持的是栅格瓦片。综合分析之后我选用了Leaflet.VectorGrid插件进行矢量瓦片的渲染,Github地址https://github.com/IvanSanchez/Leaflet.VectorGrid。
2.1 添加插件
除了正常的Leftlet所需的js以及css文件外(具体请自行搜索),还需添加一下语句引入vectorgrid的js文件。
<script src="https://unpkg.com/leaflet.vectorgrid@1.2.0"></script>
当然你可以直接将此文件下载到本地引入。在Github中也有相应的示例可以参考。
2.2 添加OSM矢量瓦片
OSM有一套可以直接调用的矢量瓦片,在这里我们以此数据为演示,将其添加到地图中,并实现交互。
var map = L.map('map');
var openmaptilesUrl = "https://free-{s}.tilehosting.com/data/v3/{z}/{x}/{y}.pbf.pict?key={key}";
var openmaptilesVectorTileOptions = {
rendererFactory: L.canvas.tile,
attribution: '<a href="https://openmaptiles.org/">© OpenMapTiles</a>, <a href="http://www.openstreetmap.org/copyright">© OpenStreetMap</a> contributors',
vectorTileLayerStyles: osm_poi_style,
subdomains: '0123',
interactive: true, // Make sure that this VectorGrid fires mouse/pointer events
key: '5iCgspbpUIw5lEYGLbGj',
maxZoom: 16
};
var openmaptilesPbfLayer = L.vectorGrid.protobuf(openmaptilesUrl, openmaptilesVectorTileOptions).addTo(map)
.on('click', function(e) { // The .on method attaches an event handler
L.popup()
.setContent((e.layer.properties.name || e.layer.properties.type) + "<br/>" + e.layer.properties.class)
.setLatLng(e.latlng)
.openOn(map);
L.DomEvent.stop(e);
});
openmaptilesUrl为OSM矢量瓦片请求地址,openmaptilesVectorTileOptions为矢量瓦片的相应配置,其中最重要的就是vectorTileLayerStyles,其表示矢量瓦片的渲染规则,矢量瓦片传送的只是矢量数,那么渲染就要由前端完成,这个变量定义的就是渲染规则,如点线面显示成什么颜色以及不同的要素渲染成什么形状颜色以及如何交互等,均在此变量中设置。osm_poi_style定义如下:
var osm_poi_style= {
poi: {icon: new L.Icon.Default()},
water: {
fill: true,
weight: 1,
fillColor: '#06cccc',
color: '#06cccc',
fillOpacity: 0.2,
opacity: 0.4,
},
admin: {
weight: 1,
fillColor: 'pink',
color: 'pink',
fillOpacity: 0.2,
opacity: 0.4
},
waterway: {
weight: 1,
fillColor: '#2375e0',
color: '#2375e0',
fillOpacity: 0.2,
opacity: 0.4
},
landcover: {
fill: true,
weight: 1,
fillColor: '#53e033',
color: '#53e033',
fillOpacity: 0.2,
opacity: 0.4,
},
landuse: {
fill: true,
weight: 1,
fillColor: '#e5b404',
color: '#e5b404',
fillOpacity: 0.2,
opacity: 0.4
},
park: {
fill: true,
weight: 1,
fillColor: '#84ea5b',
color: '#84ea5b',
fillOpacity: 0.2,
opacity: 0.4
},
boundary: {
weight: 1,
fillColor: '#c545d3',
color: '#054b96',
fillOpacity: 0.2,
opacity: 0.4
},
aeroway: {
weight: 1,
fillColor: '#51aeb5',
color: '#51aeb5',
fillOpacity: 0.2,
opacity: 0.4
},
road: { // mapbox & mapzen only
weight: 1,
fillColor: '#f2b648',
color: '#f2b648',
fillOpacity: 0.2,
opacity: 0.4
},
tunnel: { // mapbox only
weight: 0.5,
fillColor: '#f2b648',
color: '#f2b648',
fillOpacity: 0.2,
opacity: 0.4,
// dashArray: [4, 4]
},
bridge: { // mapbox only
weight: 0.5,
fillColor: '#f2b648',
color: '#f2b648',
fillOpacity: 0.2,
opacity: 0.4,
// dashArray: [4, 4]
},
transportation: { // openmaptiles only
weight: 0.5,
fillColor: '#f2b648',
color: '#f2b648',
fillOpacity: 0.2,
opacity: 0.4,
// dashArray: [4, 4]
},
transit: { // mapzen only
weight: 0.5,
fillColor: '#f2b648',
color: '#f2b648',
fillOpacity: 0.2,
opacity: 0.4,
// dashArray: [4, 4]
},
building: {
fill: true,
weight: 1,
fillColor: '#2b2b2b',
color: '#2b2b2b',
fillOpacity: 0.2,
opacity: 0.4
},
water_name: {
weight: 1,
fillColor: '#022c5b',
color: '#022c5b',
fillOpacity: 0.2,
opacity: 0.4
},
transportation_name: {
weight: 1,
fillColor: '#bc6b38',
color: '#bc6b38',
fillOpacity: 0.2,
opacity: 0.4
},
place: {
weight: 1,
fillColor: '#f20e93',
color: '#f20e93',
fillOpacity: 0.2,
opacity: 0.4
},
housenumber: {
weight: 1,
fillColor: '#ef4c8b',
color: '#ef4c8b',
fillOpacity: 0.2,
opacity: 0.4
},
poi: {
weight: 1,
fillColor: '#3bb50a',
color: '#3bb50a',
fillOpacity: 0.2,
opacity: 0.4
},
earth: { // mapzen only
fill: true,
weight: 1,
fillColor: '#c0c0c0',
color: '#c0c0c0',
fillOpacity: 0.2,
opacity: 0.4
},
// Do not symbolize some stuff for mapbox
country_label: [],
marine_label: [],
state_label: [],
place_label: [],
waterway_label: [],
poi_label: [],
road_label: [],
housenum_label: [],
// Do not symbolize some stuff for openmaptiles
country_name: [],
marine_name: [],
state_name: [],
place_name: [],
waterway_name: [],
poi_name: [],
road_name: [],
housenum_name: []
};
其中不同的对象有不同的渲染规则,而第一行的poi: {icon: new L.Icon.Default()}表示对poi这个属性进行特别渲染,渲染成一个Icon图标,当用户点击此图标的时候即可根据上面定义的on方法中的内容来进行交互。再来看一下on方法中的内容:
L.popup()
.setContent((e.layer.properties.name || e.layer.properties.type) + "<br/>" + e.layer.properties.class)
.setLatLng(e.latlng)
.openOn(map);
L.DomEvent.stop(e);
L.popup表示弹出一个提示框,setContent表示提示框中的内容,这个根据矢量瓦片中的数据内容和自己的业务需求具体修改。setLatLng表示提示框显示的位置,此处表示当前点的位置,也可以修改。当然其实我们也完全可以在on函数中实现更复杂的逻辑,如查询数据库获取更多信息进行显示等,具体根据自己的业务而定。来看一下显示的具体效果。


可以看到交互的图标以及交互信息,当然后面的数据也都是矢量瓦片在前端时时渲染的。矢量瓦片显示很流畅,交互也都很顺利。总之此插件效果不错。
三、矢量瓦片解析
我们知道了如何在前端进行矢量瓦片渲染,下面来看一下矢量瓦片的具体内容,当我们下载一幅矢量瓦片时可以看到其中都是二进制数据,这是为了减小传输压力进行的压缩,也有一些开源的软件可以进行解压缩,如https://github.com/bertt/mapbox-vector-tile-cs。
解析后的部分数据内容如下(只取出了属性等数据):
water
----Polygon
--------class lake
----Polygon
--------class lake
----Polygon
--------class lake
----Polygon
--------class lake
waterway
----LineString
--------class stream
--------name Molly Ann Brook
--------name:latin Molly Ann Brook
--------name_de Molly Ann Brook
--------name_en Molly Ann Brook
--------name_int Molly Ann Brook
landcover
----MultiPolygon
--------class wood
--------subclass wood
mountain_peak
----Point
--------ele 268
--------ele_ft 879
--------name High Mountain
--------name:latin High Mountain
--------name_de High Mountain
--------name_en High Mountain
--------name_int High Mountain
--------osm_id 357723234
--------rank 1
boundary
----LineString
--------admin_level 8
--------disputed 0
--------maritime 0
place
----Point
--------class village
--------name North Haledon
--------name:latin North Haledon
--------name_de North Haledon
--------name_en North Haledon
--------name_int North Haledon
--------rank 11
housenumber
----Point
--------housenumber 558
----Point
--------housenumber 65
poi
----Point
--------class school
--------name Memorial Elementary School
--------name:latin Memorial Elementary School
--------name_de Memorial Elementary School
--------name_en Memorial Elementary School
--------name_int Memorial Elementary School
--------rank 1
--------subclass school
----Point
--------class grocery
--------name Super Foodtown
--------name:latin Super Foodtown
--------name_de Super Foodtown
--------name_en Super Foodtown
--------name_int Super Foodtown
--------rank 1
--------subclass supermarket
----Point
--------class place_of_worship
--------name Temple Emanuel of North Jersey
--------name:latin Temple Emanuel of North Jersey
--------name_de Temple Emanuel of North Jersey
--------name_en Temple Emanuel of North Jersey
--------name_int Temple Emanuel of North Jersey
--------rank 2
--------subclass place_of_worship
可以看出其中确实包含了多种数据类型,water、boundary、poi等,各种类型下面有空间属性也有一些class、name等属性。主要来看一下poi,可以看出下面有多个点,每个点有分类以及name等,刚刚我在提示框中显示的正是class和name信息。
四、总结
本文简单讲述了矢量瓦片技术,期待Geotrellis的矢量瓦片早日上线,这样就能验证我矢栅一体化的猜想,真正的统合所有空间数据,进行统一基准下的空间运算。
Geotrellis系列文章链接地址http://www.cnblogs.com/shoufengwei/p/5619419.html
geotrellis使用(三十四)矢量瓦片技术研究——矢栅一体化的更多相关文章
- spring boot 常见三十四问
Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家. 问题一 Spring Boot.Spring MVC 和 Spring 有什么区别 ...
- FreeSql (三十四)CodeFirst 迁移说明
FreeSql 支持 CodeFirst 迁移结构至数据库,这应该是(O/RM)必须标配的一个功能. 与其他(O/RM)不同FreeSql支持更多的数据库特性,而不只是支持基础的数据类型,这既是优点也 ...
- 剑指Offer(三十四):第一个只出现一次的字符
剑指Offer(三十四):第一个只出现一次的字符 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net ...
- COJ966 WZJ的数据结构(负三十四)
WZJ的数据结构(负三十四) 难度级别:C: 运行时间限制:20000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 给一棵n个节点的树,请对于形如"u ...
- NeHe OpenGL教程 第三十四课:地形
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫
JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...
- Java进阶(三十四)Integer与int的种种比较你知道多少?
Java进阶(三十四)Integer与int的种种比较你知道多少? 前言 如果面试官问Integer与int的区别:估计大多数人只会说到两点:Ingeter是int的包装类,注意是一个类:int的初值 ...
- Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- SQL注入之Sqli-labs系列第三十四关(基于宽字符逃逸POST注入)和三十五关
开始挑战第三十四关和第三十五关(Bypass add addslashes) 0x1查看源码 本关是post型的注入漏洞,同样的也是将post过来的内容进行了 ' \ 的处理. if(isset($_ ...
随机推荐
- 【Hadoop】集群配置要点
1.SSH免密码登录 1.1生成公钥,一直enter,直到完成 dream361@master:~$ ssh-keygen -t rsa 1.2传送公钥 dream361@master:~$ scp ...
- java的引用数据类型,你知道吗???
有些人很清楚java的八种基本数据类型,但说到java的引用数据类型就不清楚了. Java的数据类型分为两大类,即基本数据类型和引用数据类型,在基本数据类型中有8种 基本数据类型(逻辑型-boolea ...
- php sql uuid 32位
最近表中id需要用到此值来作为唯一主键 其含义是通用唯一识别码.具体好处及应用可百度百科,链接给你https://baike.baidu.com/item/UUID/5921266?fr=aladdi ...
- Pyinstaller(python打包为exe文件)
需求分析: python脚本如果在没有安装python的机器上不能运行,所以将脚本打包成exe文件,降低脚本对环境的依赖性,同时运行更加迅速. 当然打包的脚本似乎不是在所有的win平台下都能使用, ...
- Jmeter常用功能详解
嘻嘻,忙碌的一周,马上就到周四了~明天就是周五了,可以去嗨了! 这几天正式成立了一个微信订阅号,旨在免费帮助需要入门软件测试的小白! 各位走过路过的亲,欢迎订阅哦:扫描二维码即可订阅
- solr-geohsah 按照距离搜索分组
通过solr的domain-import,将mysql的数据通过查询,导入到solr中.java通过使用solrj,链接solr,调用domaininport,并将分页参数设置到domain-impo ...
- Centos 6修复/boot目录及fstab等系统文件
author:JevonWei 版权声明:原创作品 错误界面 系统修复过程中,若需要修复fatab挂载文件,磁盘分区为lvm逻辑卷格式,则默认在修复模式下处于不可活动状态,需使用vgchage -ay ...
- Java 类的热替换 —— 概念、设计与实现
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp71 Java 类的热替换 -- 概念.设计与实现 构建基于 Java ...
- js获取地址栏参数数据
// 获取指定地址栏数据//name:参数名 function GetQueryString(name, url) { var reg = new RegExp("(^|&)&quo ...
- ant安装以及环境变量配置、验证
(一)安装 ant 下载地址: http://ant.apache.org/ 根据自己电脑下载对应版本 下载完成以后,可自行解压到自己常用的盘中,但是要记住解压到哪里了,以便后续的环境变量配置 ...