mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 21:48:20 +02:00
Now supports fetching data from vector tile url
This commit is contained in:
+4
-4
@@ -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?"
|
||||
|
||||
Vendored
+4
-4
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user