Merge branch 'check' of https://github.com/vershwal/iD into vershwal-check

This commit is contained in:
Bryan Housel
2018-07-09 09:32:15 -04:00
12 changed files with 454 additions and 9 deletions

View File

@@ -318,3 +318,32 @@ text.gpxlabel-halo {
stroke-miterlimit: 1;
}
/* MVT Paths */
.layer-mvt {
pointer-events: none;
}
path.mvt {
stroke: #ff26d4;
stroke-width: 2;
fill: none;
}
text.mvtlabel-halo,
text.mvtlabel {
font-size: 10px;
font-weight: bold;
dominant-baseline: middle;
}
text.mvtlabel {
fill: #ff26d4;
}
text.mvtlabel-halo {
opacity: 0.7;
stroke: #000;
stroke-width: 5px;
stroke-miterlimit: 1;
}

View File

@@ -2521,6 +2521,10 @@ div.full-screen > button:hover {
transform: rotateY(180deg);
}
[dir='rtl'] .list-item-mvt-browse svg {
transform: rotateY(180deg);
}
/* make sure tooltip fits in map-control panel */
/* if too wide, placement will be wrong the first time it displays */
.layer-list li.best .tooltip-inner {

View File

@@ -585,10 +585,15 @@ en:
cannot_zoom: "Cannot zoom out further in current mode."
full_screen: Toggle Full Screen
gpx:
local_layer: "Local file"
local_layer: "Add a GPX"
drag_drop: "Drag and drop a .gpx, .geojson or .kml file on the page, or click the button to the right to browse"
zoom: "Zoom to layer"
browse: "Browse for a file"
mvt:
local_layer: "Add a MVT"
drag_drop: "Drag and drop a .mvt or .pbf file on the page, or click the button to the right to browse"
zoom: "Zoom to layer"
browse: "Browse for a file"
streetside:
tooltip: "Streetside photos from Microsoft"
title: "Photo Overlay (Bing Streetside)"

View File

@@ -710,7 +710,7 @@
"cannot_zoom": "Cannot zoom out further in current mode.",
"full_screen": "Toggle Full Screen",
"gpx": {
"local_layer": "Local file",
"local_layer": "Add a GPX",
"drag_drop": "Drag and drop a .gpx, .geojson or .kml file on the page, or click the button to the right to browse",
"zoom": "Zoom to layer",
"browse": "Browse for a file"
@@ -721,6 +721,12 @@
"report": "Report a privacy concern with this image",
"hires": "High resolution"
},
"mvt": {
"local_layer": "Add a MVT",
"drag_drop": "Drag and drop a .mvt or .pbf file on the page, or click the button to the right to browse",
"zoom": "Zoom to layer",
"browse": "Browse for a file"
},
"mapillary_images": {
"tooltip": "Street-level photos from Mapillary",
"title": "Photo Overlay (Mapillary)"

View File

@@ -181,6 +181,14 @@ export function rendererBackground(context) {
imageryUsed.push('Bing Streetside');
}
var mvt = context.layers().layer('mvt');
if (mvt && mvt.enabled() && mvt.hasMvt()) {
// Include a string like '.mvt data file' or '.geojson data file'
var matchmvt = mvt.getSrc().match(/(pbf|mvt|(?:geo)?json)$/i);
var extensionmvt = matchmvt ? ('.' + matchmvt[0].toLowerCase() + ' ') : '';
imageryUsed.push(extensionmvt + 'data file');
}
var mapillary_images = context.layers().layer('mapillary-images');
if (mapillary_images && mapillary_images.enabled()) {
imageryUsed.push('Mapillary Images');
@@ -429,6 +437,13 @@ export function rendererBackground(context) {
}
}
if (q.mvt) {
var mvt = context.layers().layer('mvt');
if (mvt) {
mvt.url(q.mvt);
}
}
if (q.offset) {
var offset = q.offset.replace(/;/g, ',').split(',').map(function(n) {
return !isNaN(n) && n;

View File

@@ -2,6 +2,7 @@ export { svgAreas } from './areas.js';
export { svgDebug } from './debug.js';
export { svgDefs } from './defs.js';
export { svgGpx } from './gpx.js';
export { svgMvt } from './mvt.js';
export { svgIcon } from './icon.js';
export { svgLabels } from './labels.js';
export { svgLayers } from './layers.js';

View File

@@ -1,3 +1,4 @@
import _difference from 'lodash-es/difference';
import _find from 'lodash-es/find';
import _map from 'lodash-es/map';
@@ -9,6 +10,7 @@ import { select as d3_select } from 'd3-selection';
import { svgDebug } from './debug';
import { svgGpx } from './gpx';
import { svgStreetside } from './streetside';
import { svgMvt } from './mvt';
import { svgMapillaryImages } from './mapillary_images';
import { svgMapillarySigns } from './mapillary_signs';
import { svgOpenstreetcamImages } from './openstreetcam_images';
@@ -23,6 +25,7 @@ export function svgLayers(projection, context) {
var layers = [
{ id: 'osm', layer: svgOsm(projection, context, dispatch) },
{ id: 'gpx', layer: svgGpx(projection, context, dispatch) },
{ id: 'mvt', layer: svgMvt(projection, context, dispatch) },
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
{ id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) },
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },

294
modules/svg/mvt.js Normal file
View File

@@ -0,0 +1,294 @@
import _flatten from 'lodash-es/flatten';
import _isEmpty from 'lodash-es/isEmpty';
import _isUndefined from 'lodash-es/isUndefined';
import _reduce from 'lodash-es/reduce';
import _union from 'lodash-es/union';
import { geoBounds as d3_geoBounds } from 'd3-geo';
import { text as d3_text } from 'd3-request';
import { buffer } from 'd3-fetch';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
import { svgPath } from './index';
import { utilDetect } from '../util/detect';
import vt from '@mapbox/vector-tile';
import Protobuf from 'pbf';
var _initialized = false;
var _enabled = false;
var _geojson;
export function svgMvt(projection, context, dispatch) {
var _showLabels = true;
var detected = utilDetect();
var layer;
var _src;
function init() {
if (_initialized) return; // run once
_geojson = {};
_enabled = true;
function over() {
d3_event.stopPropagation();
d3_event.preventDefault();
d3_event.dataTransfer.dropEffect = 'copy';
}
d3_select('body')
.attr('dropzone', 'copy')
.on('drop.localmvt', function() {
d3_event.stopPropagation();
d3_event.preventDefault();
if (!detected.filedrop) return;
drawMvt.files(d3_event.dataTransfer.files);
})
.on('dragenter.localmvt', over)
.on('dragexit.localmvt', over)
.on('dragover.localmvt', over);
_initialized = true;
}
function drawMvt(selection) {
var getPath = svgPath(projection).geojson;
layer = selection.selectAll('.layer-mvt')
.data(_enabled ? [0] : []);
layer.exit()
.remove();
layer = layer.enter()
.append('g')
.attr('class', 'layer-mvt')
.merge(layer);
var paths = layer
.selectAll('path')
.data([_geojson]);
paths.exit()
.remove();
paths = paths.enter()
.append('path')
.attr('class', 'mvt')
.merge(paths);
paths
.attr('d', getPath);
var labelData = _showLabels && _geojson.features ? _geojson.features : [];
labelData = labelData.filter(getPath);
layer
.call(drawLabels, 'mvtlabel-halo', labelData)
.call(drawLabels, 'mvtlabel', labelData);
function drawLabels(selection, textClass, data) {
var labels = selection.selectAll('text.' + textClass)
.data(data);
// exit
labels.exit()
.remove();
// enter/update
labels = labels.enter()
.append('text')
.attr('class', textClass)
.merge(labels)
.text(function(d) {
if (d.properties) {
return d.properties.desc || d.properties.name;
}
return null;
})
.attr('x', function(d) {
var centroid = getPath.centroid(d);
return centroid[0] + 11;
})
.attr('y', function(d) {
var centroid = getPath.centroid(d);
return centroid[1];
});
}
}
function vtToGeoJson(bufferdata) {
var tile = new vt.VectorTile(new Protobuf(bufferdata.data));
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 getExtension(fileName) {
if (_isUndefined(fileName)) {
return '';
}
var lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) {
return '';
}
return fileName.substr(lastDotIndex);
}
function parseSaveAndZoom(extension, bufferdata) {
switch (extension) {
default:
drawMvt.geojson(JSON.parse(bufferdata.data)).fitZoom();
break;
case '.pbf':
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
break;
case '.mvt':
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
break;
}
}
drawMvt.showLabels = function(_) {
if (!arguments.length) return _showLabels;
_showLabels = _;
return this;
};
drawMvt.enabled = function(_) {
if (!arguments.length) return _enabled;
_enabled = _;
dispatch.call('change');
return this;
};
drawMvt.hasMvt = function() {
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
};
drawMvt.geojson = function(gj) {
if (!arguments.length) return _geojson;
if (_isEmpty(gj) || _isEmpty(gj.features)) return this;
_geojson = gj;
dispatch.call('change');
return this;
};
drawMvt.url = function(url) {
buffer(url).then(function(data) {
_src = url;
var match = url.match(/(pbf|mvt|(?:geo)?json)/i);
var extension = match ? ('.' + match[0].toLowerCase()) : '';
var zxy = url.match(/\/(\d+)\/(\d+)\/(\d+)/);
var bufferdata = {
data : data,
zxy : zxy
};
parseSaveAndZoom(extension, bufferdata);
});
return this;
};
drawMvt.files = function(fileList) {
if (!fileList.length) return this;
var f = fileList[0],
reader = new FileReader();
reader.onload = (function(file) {
_src = file.name;
var extension = getExtension(file.name);
var bufferdata = {
data,
zxy //to-do find x,y,z
};
return function (e) {
bufferdata.data = e.target.result;
parseSaveAndZoom(extension, bufferdata);
};
})(f);
reader.readAsArrayBuffer(f);
return this;
};
drawMvt.getSrc = function () {
return _src;
};
drawMvt.fitZoom = function() {
if (!this.hasMvt()) return this;
var map = context.map();
var viewport = map.trimmedExtent().polygon();
var coords = _reduce(_geojson.features, function(coords, feature) {
var c = feature.geometry.coordinates;
/* eslint-disable no-fallthrough */
switch (feature.geometry.type) {
case 'Point':
c = [c];
case 'MultiPoint':
case 'LineString':
break;
case 'MultiPolygon':
c = _flatten(c);
case 'Polygon':
case 'MultiLineString':
c = _flatten(c);
break;
}
/* eslint-enable no-fallthrough */
return _union(coords, c);
}, []);
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
}
return this;
};
init();
return drawMvt;
}

View File

@@ -277,6 +277,87 @@ export function uiMapData(context) {
.property('checked', showsGpx);
}
function drawMvtItem(selection) {
var mvt = layers.layer('mvt'),
hasMvt = mvt && mvt.hasMvt(),
showsMvt = hasMvt && mvt.enabled();
var ul = selection
.selectAll('.layer-list-mvt')
.data(mvt ? [0] : []);
// Exit
ul.exit()
.remove();
// Enter
var ulEnter = ul.enter()
.append('ul')
.attr('class', 'layer-list layer-list-mvt');
var liEnter = ulEnter
.append('li')
.attr('class', 'list-item-mvt');
liEnter
.append('button')
.attr('class', 'list-item-mvt-extent')
.call(tooltip()
.title(t('mvt.zoom'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
mvt.fitZoom();
})
.call(svgIcon('#icon-search'));
liEnter
.append('button')
.attr('class', 'list-item-mvt-browse')
.call(tooltip()
.title(t('mvt.browse'))
.placement((textDirection === 'rtl') ? 'right' : 'left')
)
.on('click', function() {
d3_select(document.createElement('input'))
.attr('type', 'file')
.on('change', function() {
mvt.files(d3_event.target.files);
})
.node().click();
})
.call(svgIcon('#icon-geolocate'));
var labelEnter = liEnter
.append('label')
.call(tooltip()
.title(t('mvt.drag_drop'))
.placement('top')
);
labelEnter
.append('input')
.attr('type', 'checkbox')
.on('change', function() { toggleLayer('mvt'); });
labelEnter
.append('span')
.text(t('mvt.local_layer'));
// Update
ul = ul
.merge(ulEnter);
ul.selectAll('.list-item-mvt')
.classed('active', showsMvt)
.selectAll('label')
.classed('deemphasize', !hasMvt)
.selectAll('input')
.property('disabled', !hasMvt)
.property('checked', showsMvt);
}
function drawListItems(selection, data, type, name, change, active) {
var items = selection.selectAll('li')
@@ -369,7 +450,8 @@ export function uiMapData(context) {
_dataLayerContainer
.call(drawOsmItem)
.call(drawPhotoItems)
.call(drawGpxItem);
.call(drawGpxItem)
.call(drawMvtItem);
_fillList
.call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill);

View File

@@ -101,6 +101,9 @@ export function utilStringQs(str) {
if (parts.length === 2) {
obj[parts[0]] = (null === parts[1]) ? '' : decodeURIComponent(parts[1]);
}
if (parts[0] === 'mvt') {
obj[parts[0]] = (parts[2] != undefined) ? (decodeURIComponent(parts[1]) + '=' + decodeURIComponent(parts[2])) : (decodeURIComponent(parts[1]));
}
return obj;
}, {});
}

View File

@@ -33,6 +33,8 @@
"dependencies": {
"@mapbox/sexagesimal": "1.1.0",
"@mapbox/togeojson": "0.16.0",
"@mapbox/vector-tile": "^1.3.1",
"d3-fetch": "^1.1.0",
"diacritics": "1.3.0",
"lodash-es": "4.17.10",
"marked": "0.4.0",

View File

@@ -26,14 +26,15 @@ describe('iD.svgLayers', function () {
it('creates default data layers', function () {
container.call(iD.svgLayers(projection, context));
var nodes = container.selectAll('svg .data-layer').nodes();
expect(nodes.length).to.eql(7);
expect(nodes.length).to.eql(8);
expect(d3.select(nodes[0]).classed('data-layer-osm')).to.be.true;
expect(d3.select(nodes[1]).classed('data-layer-gpx')).to.be.true;
expect(d3.select(nodes[2]).classed('data-layer-streetside')).to.be.true;
expect(d3.select(nodes[3]).classed('data-layer-mapillary-images')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-mapillary-signs')).to.be.true;
expect(d3.select(nodes[5]).classed('data-layer-openstreetcam-images')).to.be.true;
expect(d3.select(nodes[6]).classed('data-layer-debug')).to.be.true;
expect(d3.select(nodes[2]).classed('data-layer-mvt')).to.be.true;
expect(d3.select(nodes[3]).classed('data-layer-streetside')).to.be.true;
expect(d3.select(nodes[4]).classed('data-layer-mapillary-images')).to.be.true;
expect(d3.select(nodes[5]).classed('data-layer-mapillary-signs')).to.be.true;
expect(d3.select(nodes[6]).classed('data-layer-openstreetcam-images')).to.be.true;
expect(d3.select(nodes[7]).classed('data-layer-debug')).to.be.true;
});
});