前言

本文所涉及技术与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/">&copy; OpenMapTiles</a>, <a href="http://www.openstreetmap.org/copyright">&copy; 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使用(三十四)矢量瓦片技术研究——矢栅一体化的更多相关文章

  1. spring boot 常见三十四问

    Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家. 问题一 Spring Boot.Spring MVC 和 Spring 有什么区别 ...

  2. FreeSql (三十四)CodeFirst 迁移说明

    FreeSql 支持 CodeFirst 迁移结构至数据库,这应该是(O/RM)必须标配的一个功能. 与其他(O/RM)不同FreeSql支持更多的数据库特性,而不只是支持基础的数据类型,这既是优点也 ...

  3. 剑指Offer(三十四):第一个只出现一次的字符

    剑指Offer(三十四):第一个只出现一次的字符 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net ...

  4. COJ966 WZJ的数据结构(负三十四)

    WZJ的数据结构(负三十四) 难度级别:C: 运行时间限制:20000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 给一棵n个节点的树,请对于形如"u  ...

  5. NeHe OpenGL教程 第三十四课:地形

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  6. JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

    JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...

  7. Java进阶(三十四)Integer与int的种种比较你知道多少?

    Java进阶(三十四)Integer与int的种种比较你知道多少? 前言 如果面试官问Integer与int的区别:估计大多数人只会说到两点:Ingeter是int的包装类,注意是一个类:int的初值 ...

  8. Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  9. SQL注入之Sqli-labs系列第三十四关(基于宽字符逃逸POST注入)和三十五关

    开始挑战第三十四关和第三十五关(Bypass add addslashes) 0x1查看源码 本关是post型的注入漏洞,同样的也是将post过来的内容进行了 ' \ 的处理. if(isset($_ ...

随机推荐

  1. 创建Windows服务(C++)

    这次我们来创建一个windows本地服务,需要有以下功能: 安装服务. 卸载服务. 手动启动服务. 开机自动启动服务. 控制服务(停止.暂停.恢复.启动). 服务概念及介绍 看下图,一切尽在不言中了( ...

  2. spring boot / cloud (五) 自签SSL证书以及HTTPS

    spring boot / cloud (五) 自签SSL证书以及HTTPS 前言 什么是HTTPS? HTTPS(全称:Hyper Text Transfer Protocol over Secur ...

  3. BotVS趋势交易策略-RSI

    BotVS趋势交易策略-RSI, 基于Python实现. RSI简单买卖测试, 默认 70-100卖出,0-30买入 参数 代码 import math def adjustFloat(v): ret ...

  4. Nodejs进阶:使用DiffieHellman密钥交换算法

    ## 简介 Diffie-Hellman(简称DH)是密钥交换算法之一,它的作用是保证通信双方在非安全的信道中安全地交换密钥.目前DH最重要的应用场景之一,就是在HTTPS的握手阶段,客户端.服务端利 ...

  5. css中滚动条样式的设置

    参数说明: 1.overflow-y : 设置当对象的内容超过其指定高度时如何管理内容:overflow-x : 设置当对象的内容超过其指定宽度时如何管理内容. 参数: visible:扩大面积以显示 ...

  6. Java入门(5)——类和对象还有构造方法

    类     类和对象的概念: 类是对一群具有相同属性.行为的事物的统称.        类是抽象的.       人以类聚 物以群分 对象:      对象是现实生活中的1个具体存在.      看得 ...

  7. TCP协议三次握手与四次挥手通俗解析

    TCP/IP协议三次握手与四次握手流程解析 一.TCP报文格式 TCP/IP协议的详细信息参看<TCP/IP协议详解>三卷本.下面是TCP报文格式图: 图1 TCP报文格式 上图中有几个字 ...

  8. [读书笔记] 四、SpringBoot中使用JPA 进行快速CRUD操作

    通过Spring提供的JPA Hibernate实现,进行快速CRUD操作的一个栗子~. 视图用到了SpringBoot推荐的thymeleaf来解析,数据库使用的Mysql,代码详细我会贴在下面文章 ...

  9. [2015-10-11]常用git命令

    从vs2013开始,vs已经对git的操作提供了很好的支持,但是重度使用时还是会遇到很多抽风的时候,在此记录一些常用命令. 分支操作 查看所有远程分支 git branch -r 查看本地分支 git ...

  10. Java8 Stream简介

    Stream是Java 8新增的重要特性, 它提供函数式编程支持并允许以管道方式操作集合. 流操作会遍历数据源, 使用管道式操作处理数据后生成结果集合, 这个过程通常不会对数据源造成影响. lambd ...