Add a vector tile service

This commit is contained in:
Bryan Housel
2018-08-20 21:54:00 -04:00
parent 0840b8dff9
commit 9009d55fd1
4 changed files with 151 additions and 2 deletions
+1 -1
View File
@@ -646,7 +646,7 @@
},
"custom_data": {
"tooltip": "Edit custom data layer",
"header": "Custom Data Settings",
"header": "Custom Map Data Settings",
"file": {
"instructions": "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson/.json, .pbf, .mvt",
"label": "Browse files"
-1
View File
@@ -1,5 +1,4 @@
import _find from 'lodash-es/find';
import _omit from 'lodash-es/omit';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { interpolateNumber as d3_interpolateNumber } from 'd3-interpolate';
+3
View File
@@ -4,6 +4,7 @@ import serviceOpenstreetcam from './openstreetcam';
import serviceOsm from './osm';
import serviceStreetside from './streetside';
import serviceTaginfo from './taginfo';
import serviceVectorTile from './vector_tile';
import serviceWikidata from './wikidata';
import serviceWikipedia from './wikipedia';
@@ -14,6 +15,7 @@ export var services = {
osm: serviceOsm,
streetside: serviceStreetside,
taginfo: serviceTaginfo,
vectorTile: serviceVectorTile,
wikidata: serviceWikidata,
wikipedia: serviceWikipedia
};
@@ -25,6 +27,7 @@ export {
serviceOsm,
serviceStreetside,
serviceTaginfo,
serviceVectorTile,
serviceWikidata,
serviceWikipedia
};
+147
View File
@@ -0,0 +1,147 @@
import _find from 'lodash-es/find';
import _forEach from 'lodash-es/forEach';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import Protobuf from 'pbf';
import vt from '@mapbox/vector-tile';
import { utilRebind, utilTiler } from '../util';
var tiler = utilTiler().tileSize(512);
var dispatch = d3_dispatch('loadedData');
var _vtCache;
function abortRequest(i) {
i.abort();
}
function vtToGeoJSON(bufferdata) {
var tile = new vt.VectorTile(new Protobuf(bufferdata.data.response));
var layers = Object.keys(tile.layers);
if (!Array.isArray(layers)) { layers = [layers]; }
var collection = { type: 'FeatureCollection', features: [] };
layers.forEach(function (layerID) {
var layer = tile.layers[layerID];
if (layer) {
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i).toGeoJSON(bufferdata.zxy[2], bufferdata.zxy[3], bufferdata.zxy[1]);
if (layers.length > 1) feature.properties.vt_layer = layerID;
collection.features.push(feature);
}
}
});
return collection;
}
function loadTile(source, tile) {
if (source.loaded[tile.id] || source.inflight[tile.id]) return;
var url = source.template
.replace('{x}', tile.xyz[0])
.replace('{y}', tile.xyz[1])
// TMS-flipped y coordinate
.replace(/\{[t-]y\}/, Math.pow(2, tile.xyz[2]) - tile.xyz[1] - 1)
.replace(/\{z(oom)?\}/, tile.xyz[2])
.replace(/\{switch:([^}]+)\}/, function(s, r) {
var subdomains = r.split(',');
return subdomains[(tile.xyz[0] + tile.xyz[1]) % subdomains.length];
});
source.inflight[tile.id] = d3_request(url)
.responseType('arraybuffer')
.get(function(err, data) {
source.loaded[tile.id] = true;
delete source.inflight[tile.id];
if (err || !data) return;
source.loaded[tile.id] = {
bufferdata: data,
geojson: vtToGeoJSON(data)
};
dispatch.call('loadedData');
});
}
export default {
init: function() {
if (!_vtCache) {
this.reset();
}
this.event = utilRebind(this, dispatch, 'on');
},
reset: function() {
for (var sourceID in _vtCache) {
var source = _vtCache[sourceID];
if (source && source.inflight) {
_forEach(source.inflight, abortRequest);
}
}
_vtCache = {};
},
addSource: function(sourceID, template) {
_vtCache[sourceID] = { template: template, inflight: {}, loaded: {} };
return _vtCache[sourceID];
},
data: function(sourceID, projection) {
var source = _vtCache[sourceID];
if (!source) return [];
// for now, return the FeatureCollection for each tile
var tiles = tiler.getTiles(projection);
return tiles.map(function(tile) {
var loaded = source.loaded[tile.id];
return loaded && loaded.geojson;
}).filter(Boolean);
},
loadTiles: function(sourceID, template, projection) {
var source = _vtCache[sourceID];
if (!source) {
source = this.addSource(sourceID, template);
}
var tiles = tiler.getTiles(projection);
// abort inflight requests that are no longer needed
_forEach(source.inflight, function(v, k) {
var wanted = _find(tiles, function(tile) { return k === tile.id; });
if (!wanted) {
abortRequest(v);
delete source.inflight[k];
}
});
tiles.forEach(function(tile) {
loadTile(source, tile);
});
},
cache: function() {
return _vtCache;
}
};