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/">&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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM