mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
Merge branch 'check' of https://github.com/vershwal/iD into vershwal-check
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)"
|
||||
|
||||
8
dist/locales/en.json
vendored
8
dist/locales/en.json
vendored
@@ -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)"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
294
modules/svg/mvt.js
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}, {});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user