Now supports fetching data from vector tile url

This commit is contained in:
Bryan Housel
2018-08-21 18:44:05 -04:00
parent 575c98ab28
commit 3eb4d91987
6 changed files with 104 additions and 124 deletions
+4 -4
View File
@@ -527,12 +527,12 @@ en:
tooltip: Edit custom data layer
header: Custom Map Data Settings
file:
instructions: "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson/.json, .pbf, .mvt"
instructions: "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson, .json"
label: "Browse files"
or: "Or"
template:
instructions: "Enter a vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme"
placeholder: Enter a url template
url:
instructions: "Enter a data file URL or vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme"
placeholder: Enter a url
restore:
heading: You have unsaved changes
description: "Do you wish to restore unsaved changes from a previous editing session?"
+4 -4
View File
@@ -648,13 +648,13 @@
"tooltip": "Edit custom data layer",
"header": "Custom Map Data Settings",
"file": {
"instructions": "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson/.json, .pbf, .mvt",
"instructions": "Choose a local data file. Supported types are:\n .gpx, .kml, .geojson, .json",
"label": "Browse files"
},
"or": "Or",
"template": {
"instructions": "Enter a vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme",
"placeholder": "Enter a url template"
"url": {
"instructions": "Enter a data file URL or vector tile URL template. Valid tokens are:\n {zoom} or {z}, {x}, {y} for Z/X/Y tile scheme",
"placeholder": "Enter a url"
}
}
},
+8 -3
View File
@@ -31,7 +31,7 @@ function vtToGeoJSON(bufferdata) {
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]);
var feature = layer.feature(i).toGeoJSON(bufferdata.xyz[0], bufferdata.xyz[1], bufferdata.xyz[2]);
if (layers.length > 1) feature.properties.vt_layer = layerID;
collection.features.push(feature);
}
@@ -64,9 +64,14 @@ function loadTile(source, tile) {
delete source.inflight[tile.id];
if (err || !data) return;
var bufferdata = {
data: data,
xyz: tile.xyz
};
source.loaded[tile.id] = {
bufferdata: data,
geojson: vtToGeoJSON(data)
bufferdata: bufferdata,
geojson: vtToGeoJSON(bufferdata)
};
dispatch.call('loadedData');
+70 -95
View File
@@ -5,11 +5,7 @@ import _union from 'lodash-es/union';
import _throttle from 'lodash-es/throttle';
import { geoBounds as d3_geoBounds } from 'd3-geo';
import {
request as d3_request,
text as d3_text
} from 'd3-request';
import { text as d3_text } from 'd3-request';
import {
event as d3_event,
@@ -17,8 +13,6 @@ import {
} from 'd3-selection';
import toGeoJSON from '@mapbox/togeojson';
import vt from '@mapbox/vector-tile';
import Protobuf from 'pbf';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import { services } from '../services';
@@ -38,7 +32,7 @@ export function svgData(projection, context, dispatch) {
var layer = d3_select(null);
var _vtService;
var _fileList;
var _template; // todo, if template is set, use vectorTile service
var _template;
var _src;
@@ -117,6 +111,7 @@ export function svgData(projection, context, dispatch) {
function drawData(selection) {
var vtService = getService();
var getPath = svgPath(projection).geojson;
var hasData = drawData.hasData();
@@ -132,9 +127,18 @@ export function svgData(projection, context, dispatch) {
.merge(layer);
var geoData;
if (_template && vtService) { // fetch data from vector tile service
var sourceID = _template;
vtService.loadTiles(sourceID, _template, projection);
geoData = vtService.data(sourceID, projection);
} else {
geoData = _geojson ? [_geojson] : [];
}
var paths = layer
.selectAll('path')
.data(hasData ? [_geojson] : []);
.data(geoData);
paths.exit()
.remove();
@@ -148,8 +152,17 @@ export function svgData(projection, context, dispatch) {
.attr('d', getPath);
var labelData = (_showLabels && hasData && _geojson.features) || [];
labelData = labelData.filter(getPath);
var labelData = [];
if (_showLabels) {
geoData.forEach(function(f) {
if (f.type === 'FeatureCollection') {
labelData = labelData.concat(f.features);
} else {
labelData.push(f);
}
});
labelData = labelData.filter(getPath);
}
layer
.call(drawLabels, 'label-halo', labelData)
@@ -188,60 +201,33 @@ export function svgData(projection, context, dispatch) {
function getExtension(fileName) {
if (fileName === undefined) {
return '';
}
if (!fileName) return;
var lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) {
return '';
}
if (lastDotIndex < 0) return;
return fileName.substr(lastDotIndex);
}
function toDom(textdata) {
function xmlToDom(textdata) {
return (new DOMParser()).parseFromString(textdata, 'text/xml');
}
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;
}
drawData.setFile = function(extension, data, src) {
_template = null;
_fileList = null;
_geojson = null;
_src = null;
var gj;
switch (extension) {
case '.gpx':
gj = toGeoJSON.gpx(toDom(data));
gj = toGeoJSON.gpx(xmlToDom(data));
break;
case '.kml':
gj = toGeoJSON.kml(toDom(data));
break;
case '.pbf':
gj = vtToGeoJSON(data);
break;
case '.mvt':
gj = vtToGeoJSON(data);
gj = toGeoJSON.kml(xmlToDom(data));
break;
case '.geojson':
case '.json':
@@ -249,17 +235,19 @@ export function svgData(projection, context, dispatch) {
break;
}
if (!gj || _isEmpty(gj) || _isEmpty(gj.features)) return;
_geojson = gj;
_src = src || 'unknown.geojson';
if (!_isEmpty(gj)) {
_geojson = gj;
_src = src || 'unknown.geojson';
return this.fitZoom();
}
dispatch.call('change');
return this.fitZoom();
};
drawData.showLabels = function(val) {
if (!arguments.length) return _showLabels;
_showLabels = val;
return this;
};
@@ -267,6 +255,7 @@ export function svgData(projection, context, dispatch) {
drawData.enabled = function(val) {
if (!arguments.length) return _enabled;
_enabled = val;
if (_enabled) {
showLayer();
@@ -280,7 +269,7 @@ export function svgData(projection, context, dispatch) {
drawData.hasData = function() {
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
return !!(_template || _geojson);
};
@@ -302,8 +291,13 @@ export function svgData(projection, context, dispatch) {
_template = null;
_fileList = null;
_geojson = gj;
_src = src || 'unknown.geojson';
_geojson = null;
_src = null;
if (!_isEmpty(gj)) {
_geojson = gj;
_src = src || 'unknown.geojson';
}
dispatch.call('change');
return this;
@@ -320,58 +314,38 @@ export function svgData(projection, context, dispatch) {
if (!fileList || !fileList.length) return this;
var f = fileList[0];
var reader = new FileReader();
var extension = getExtension(f.name);
var reader = new FileReader();
reader.onload = (function(file) {
return function(e) {
drawData.setFile(extension, e.target.result, file.name);
};
})(f);
if (extension === 'mvt' || extension === 'pbf') {
reader.onload = (function(file) {
return; // todo find x,y,z
var data = [];
var zxy = [0,0,0];
var bufferdata = { data: data, zxy: zxy };
return function (e) {
bufferdata.data = e.target.result;
drawData.setFile(extension, bufferdata, file.name);
};
})(f);
reader.readAsArrayBuffer(f);
} else {
reader.onload = (function(file) {
return function (e) {
drawData.setFile(extension, e.target.result, file.name);
};
})(f);
reader.readAsText(f);
}
reader.readAsText(f);
return this;
};
drawData.url = function(url) {
_template = null;
_fileList = null;
_geojson = null;
_src = null;
var extension = getExtension(url);
if (extension === 'mvt' || extension === 'pbf') {
d3_request(url)
.responseType('arraybuffer')
.get(function(err, data) {
if (err || !data) return;
_src = url;
var match = url.match(/(pbf|mvt)/i);
var extension = match ? ('.' + match[0].toLowerCase()) : '';
var zxy = url.match(/\/(\d+)\/(\d+)\/(\d+)/);
var bufferdata = { data : data, zxy : zxy };
drawData.setFile(extension, bufferdata, url);
});
} else {
var re = /\.(gpx|kml|(geo)?json)$/i;
if (re.test(extension)) {
_template = null;
d3_text(url, function(err, data) {
if (!err) {
drawData.setFile(extension, data, url);
}
});
} else {
drawData.template(url);
}
return this;
@@ -379,12 +353,13 @@ export function svgData(projection, context, dispatch) {
drawData.getSrc = function() {
return _src;
return _src || '';
};
drawData.fitZoom = function() {
if (!this.hasData()) return this;
// note: only works on a FeatureCollection
if (_isEmpty(_geojson) || _isEmpty(_geojson.features)) return;
var map = context.map();
var viewport = map.trimmedExtent().polygon();
+2 -2
View File
@@ -295,8 +295,8 @@ export function uiMapData(context) {
function customChanged(d) {
var dataLayer = layers.layer('data');
if (d && d.template) {
dataLayer.template(d.template);
if (d && d.url) {
dataLayer.url(d.url);
} else if (d && d.fileList) {
dataLayer.fileList(d.fileList);
}
+16 -16
View File
@@ -15,7 +15,7 @@ export function uiSettingsCustomData(context) {
var dataLayer = context.layers().layer('data');
var _origSettings = {
fileList: (dataLayer && dataLayer.fileList()) || null,
template: context.storage('settings-custom-data-template')
url: context.storage('settings-custom-data-url')
};
var _currSettings = _cloneDeep(_origSettings);
@@ -45,8 +45,8 @@ export function uiSettingsCustomData(context) {
.on('change', function() {
var files = d3_event.target.files;
if (files && files.length) {
_currSettings.template = '';
textSection.select('.field-template').property('value', '');
_currSettings.url = '';
textSection.select('.field-url').property('value', '');
_currSettings.fileList = files;
} else {
_currSettings.fileList = null;
@@ -59,15 +59,15 @@ export function uiSettingsCustomData(context) {
textSection
.append('pre')
.attr('class', 'instructions-template')
.text(t('settings.custom_data.template.instructions'));
.attr('class', 'instructions-url')
.text(t('settings.custom_data.url.instructions'));
textSection
.append('textarea')
.attr('class', 'field-template')
.attr('placeholder', t('settings.custom_data.template.placeholder'))
.attr('class', 'field-url')
.attr('placeholder', t('settings.custom_data.url.placeholder'))
.call(utilNoAuto)
.property('value', _currSettings.template);
.property('value', _currSettings.url);
// insert a cancel button, and adjust the button widths
@@ -94,23 +94,23 @@ export function uiSettingsCustomData(context) {
}
// restore the original template
// restore the original url
function clickCancel() {
textSection.select('.field-template').property('value', _origSettings.template);
context.storage('settings-custom-data-template', _origSettings.template);
textSection.select('.field-url').property('value', _origSettings.url);
context.storage('settings-custom-data-url', _origSettings.url);
this.blur();
modal.close();
}
// accept the current template
// accept the current url
function clickSave() {
_currSettings.template = textSection.select('.field-template').property('value').trim();
_currSettings.url = textSection.select('.field-url').property('value').trim();
// one or the other but not both
if (_currSettings.template) { _currSettings.fileList = null; }
if (_currSettings.fileList) { _currSettings.template = ''; }
if (_currSettings.url) { _currSettings.fileList = null; }
if (_currSettings.fileList) { _currSettings.url = ''; }
context.storage('settings-custom-data-template', _currSettings.template);
context.storage('settings-custom-data-url', _currSettings.url);
this.blur();
modal.close();
dispatch.call('change', this, _currSettings);