mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-30 15:38:27 +02:00
Don't bundle the imagery anymore, fetch from dist/data at runtime
(re: #4994)
This commit is contained in:
+84315
-84276
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,12 @@
|
||||
export { dataLocales } from './locales.json';
|
||||
export { en as dataEn } from '../dist/locales/en.json';
|
||||
|
||||
import { dataImagery } from './imagery.json';
|
||||
import { presets } from './presets/presets.json';
|
||||
import { defaults } from './presets/defaults.json';
|
||||
import { categories } from './presets/categories.json';
|
||||
import { fields } from './presets/fields.json';
|
||||
|
||||
export let data = {
|
||||
imagery: dataImagery, //legacy
|
||||
presets: {
|
||||
presets: presets,
|
||||
defaults: defaults,
|
||||
|
||||
Vendored
+1
-1
@@ -10075,7 +10075,7 @@
|
||||
},
|
||||
"MAPNIK": {
|
||||
"attribution": {
|
||||
"text": "© OpenStreetMap contributors, CC-BY-SA"
|
||||
"text": "© OpenStreetMap contributors, CC-BY-SA 2.0"
|
||||
},
|
||||
"description": "The default OpenStreetMap layer.",
|
||||
"name": "OpenStreetMap (Standard)"
|
||||
|
||||
File diff suppressed because one or more lines are too long
+463
-457
@@ -4,7 +4,6 @@ import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import whichPolygon from 'which-polygon';
|
||||
|
||||
import { data } from '../../data';
|
||||
import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo';
|
||||
import { rendererBackgroundSource } from './background_source';
|
||||
import { rendererTileLayer } from './tile_layer';
|
||||
@@ -13,495 +12,502 @@ import { utilDetect } from '../util/detect';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
let _imageryIndex = null;
|
||||
|
||||
export function rendererBackground(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var detected = utilDetect();
|
||||
var baseLayer = rendererTileLayer(context).projection(context.projection);
|
||||
var _isValid = true;
|
||||
var _overlayLayers = [];
|
||||
var _backgroundSources = [];
|
||||
var _brightness = 1;
|
||||
var _contrast = 1;
|
||||
var _saturation = 1;
|
||||
var _sharpness = 1;
|
||||
const dispatch = d3_dispatch('change');
|
||||
const detected = utilDetect();
|
||||
const baseLayer = rendererTileLayer(context).projection(context.projection);
|
||||
let _isValid = true;
|
||||
let _overlayLayers = [];
|
||||
let _brightness = 1;
|
||||
let _contrast = 1;
|
||||
let _saturation = 1;
|
||||
let _sharpness = 1;
|
||||
|
||||
|
||||
function background(selection) {
|
||||
// If we are displaying an Esri basemap at high zoom,
|
||||
// check its tilemap to see how high the zoom can go
|
||||
if (context.map().zoom() > 18) {
|
||||
var basemap = baseLayer.source();
|
||||
if (basemap && /^EsriWorldImagery/.test(basemap.id)) {
|
||||
var center = context.map().center();
|
||||
basemap.fetchTilemap(center);
|
||||
}
|
||||
}
|
||||
function ensureImageryIndex() {
|
||||
return context.data().get('imagery')
|
||||
.then(sources => {
|
||||
if (_imageryIndex) return _imageryIndex;
|
||||
|
||||
// Is the imagery valid here? - #4827
|
||||
var sources = background.sources(context.map().extent());
|
||||
var wasValid = _isValid;
|
||||
_isValid = !!sources
|
||||
.filter(function(d) { return d === baseLayer.source(); }).length;
|
||||
|
||||
if (wasValid !== _isValid) { // change in valid status
|
||||
background.updateImagery();
|
||||
}
|
||||
|
||||
|
||||
var baseFilter = '';
|
||||
if (detected.cssfilters) {
|
||||
if (_brightness !== 1) {
|
||||
baseFilter += 'brightness(' + _brightness + ')';
|
||||
}
|
||||
if (_contrast !== 1) {
|
||||
baseFilter += 'contrast(' + _contrast + ')';
|
||||
}
|
||||
if (_saturation !== 1) {
|
||||
baseFilter += 'saturate(' + _saturation + ')';
|
||||
}
|
||||
if (_sharpness < 1) { // gaussian blur
|
||||
var blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness);
|
||||
baseFilter += 'blur(' + blur + 'px)';
|
||||
}
|
||||
}
|
||||
|
||||
var base = selection.selectAll('.layer-background')
|
||||
.data([0]);
|
||||
|
||||
base = base.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-background')
|
||||
.merge(base);
|
||||
|
||||
if (detected.cssfilters) {
|
||||
base.style('filter', baseFilter || null);
|
||||
} else {
|
||||
base.style('opacity', _brightness);
|
||||
}
|
||||
|
||||
|
||||
var imagery = base.selectAll('.layer-imagery')
|
||||
.data([0]);
|
||||
|
||||
imagery.enter()
|
||||
.append('div')
|
||||
.attr('class', 'layer layer-imagery')
|
||||
.merge(imagery)
|
||||
.call(baseLayer);
|
||||
|
||||
|
||||
var maskFilter = '';
|
||||
var mixBlendMode = '';
|
||||
if (detected.cssfilters && _sharpness > 1) { // apply unsharp mask
|
||||
mixBlendMode = 'overlay';
|
||||
maskFilter = 'saturate(0) blur(3px) invert(1)';
|
||||
|
||||
var contrast = _sharpness - 1;
|
||||
maskFilter += ' contrast(' + contrast + ')';
|
||||
|
||||
var brightness = d3_interpolateNumber(1, 0.85)(_sharpness - 1);
|
||||
maskFilter += ' brightness(' + brightness + ')';
|
||||
}
|
||||
|
||||
var mask = base.selectAll('.layer-unsharp-mask')
|
||||
.data(detected.cssfilters && _sharpness > 1 ? [0] : []);
|
||||
|
||||
mask.exit()
|
||||
.remove();
|
||||
|
||||
mask.enter()
|
||||
.append('div')
|
||||
.attr('class', 'layer layer-mask layer-unsharp-mask')
|
||||
.merge(mask)
|
||||
.call(baseLayer)
|
||||
.style('filter', maskFilter || null)
|
||||
.style('mix-blend-mode', mixBlendMode || null);
|
||||
|
||||
|
||||
var overlays = selection.selectAll('.layer-overlay')
|
||||
.data(_overlayLayers, function(d) { return d.source().name(); });
|
||||
|
||||
overlays.exit()
|
||||
.remove();
|
||||
|
||||
overlays.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-overlay')
|
||||
.merge(overlays)
|
||||
.each(function(layer) { d3_select(this).call(layer); });
|
||||
}
|
||||
|
||||
|
||||
background.updateImagery = function() {
|
||||
var b = baseLayer.source();
|
||||
if (context.inIntro() || !b) return;
|
||||
|
||||
var o = _overlayLayers
|
||||
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
|
||||
.map(function (d) { return d.source().id; })
|
||||
.join(',');
|
||||
|
||||
var meters = geoOffsetToMeters(b.offset());
|
||||
var epsilon = 0.01;
|
||||
var x = +meters[0].toFixed(2);
|
||||
var y = +meters[1].toFixed(2);
|
||||
var q = utilStringQs(window.location.hash.substring(1));
|
||||
|
||||
var id = b.id;
|
||||
if (id === 'custom') {
|
||||
id = 'custom:' + b.template();
|
||||
}
|
||||
|
||||
if (id) {
|
||||
q.background = id;
|
||||
} else {
|
||||
delete q.background;
|
||||
}
|
||||
|
||||
if (o) {
|
||||
q.overlays = o;
|
||||
} else {
|
||||
delete q.overlays;
|
||||
}
|
||||
|
||||
if (Math.abs(x) > epsilon || Math.abs(y) > epsilon) {
|
||||
q.offset = x + ',' + y;
|
||||
} else {
|
||||
delete q.offset;
|
||||
}
|
||||
|
||||
if (!window.mocha) {
|
||||
window.location.replace('#' + utilQsString(q, true));
|
||||
}
|
||||
|
||||
var imageryUsed = [];
|
||||
var photoOverlaysUsed = [];
|
||||
|
||||
var current = b.imageryUsed();
|
||||
if (current && _isValid) {
|
||||
imageryUsed.push(current);
|
||||
}
|
||||
|
||||
_overlayLayers
|
||||
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
|
||||
.forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); });
|
||||
|
||||
var data = context.layers().layer('data');
|
||||
if (data && data.enabled() && data.hasData()) {
|
||||
imageryUsed.push(data.getSrc());
|
||||
}
|
||||
|
||||
var photoOverlayLayers = {
|
||||
streetside: 'Bing Streetside',
|
||||
mapillary: 'Mapillary Images',
|
||||
'mapillary-map-features': 'Mapillary Map Features',
|
||||
'mapillary-signs': 'Mapillary Signs',
|
||||
openstreetcam: 'OpenStreetCam Images'
|
||||
_imageryIndex = {
|
||||
imagery: sources,
|
||||
features: {}
|
||||
};
|
||||
|
||||
for (var layerID in photoOverlayLayers) {
|
||||
var layer = context.layers().layer(layerID);
|
||||
if (layer && layer.enabled()) {
|
||||
photoOverlaysUsed.push(layerID);
|
||||
imageryUsed.push(photoOverlayLayers[layerID]);
|
||||
}
|
||||
}
|
||||
// use which-polygon to support efficient index and querying for imagery
|
||||
const features = sources.map(source => {
|
||||
if (!source.polygon) return null;
|
||||
// workaround for editor-layer-index weirdness..
|
||||
// Add an extra array nest to each element in `source.polygon`
|
||||
// so the rings are not treated as a bunch of holes:
|
||||
// what we have: [ [[outer],[hole],[hole]] ]
|
||||
// what we want: [ [[outer]],[[outer]],[[outer]] ]
|
||||
const rings = source.polygon.map(ring => [ring]);
|
||||
|
||||
context.history().photoOverlaysUsed(photoOverlaysUsed);
|
||||
context.history().imageryUsed(imageryUsed);
|
||||
};
|
||||
const feature = {
|
||||
type: 'Feature',
|
||||
properties: { id: source.id },
|
||||
geometry: { type: 'MultiPolygon', coordinates: rings }
|
||||
};
|
||||
|
||||
|
||||
background.sources = function(extent, zoom, alwaysIncludeSelected) {
|
||||
if (!data.imagery || !data.imagery.query) return []; // called before init()?
|
||||
|
||||
var matchIDs = {};
|
||||
var matchImagery = data.imagery.query.bbox(extent.rectangle(), true) || [];
|
||||
matchImagery.forEach(function(d) { matchIDs[d.id] = true; });
|
||||
|
||||
var currentSource = baseLayer.source();
|
||||
|
||||
return _backgroundSources.filter(function(source) {
|
||||
// optionally always include the selected source
|
||||
if (alwaysIncludeSelected && currentSource === source) return true;
|
||||
|
||||
// always show sources with worldwide coverage
|
||||
if (!source.polygon) return true;
|
||||
|
||||
// optionally don't include non-worldwide sources at low zooms
|
||||
if (zoom && zoom < 6) return false;
|
||||
|
||||
// don't include sources outside the extent
|
||||
return matchIDs[source.id];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
background.dimensions = function(d) {
|
||||
if (!d) return;
|
||||
baseLayer.dimensions(d);
|
||||
|
||||
_overlayLayers.forEach(function(layer) {
|
||||
layer.dimensions(d);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
background.baseLayerSource = function(d) {
|
||||
if (!arguments.length) return baseLayer.source();
|
||||
|
||||
// test source against OSM imagery blacklists..
|
||||
var osm = context.connection();
|
||||
if (!osm) return background;
|
||||
|
||||
var blacklists = context.connection().imageryBlacklists();
|
||||
var template = d.template();
|
||||
var fail = false;
|
||||
var tested = 0;
|
||||
var regex;
|
||||
|
||||
for (var i = 0; i < blacklists.length; i++) {
|
||||
try {
|
||||
regex = new RegExp(blacklists[i]);
|
||||
fail = regex.test(template);
|
||||
tested++;
|
||||
if (fail) break;
|
||||
} catch (e) {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ensure at least one test was run.
|
||||
if (!tested) {
|
||||
regex = new RegExp('.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*');
|
||||
fail = regex.test(template);
|
||||
}
|
||||
|
||||
baseLayer.source(!fail ? d : background.findSource('none'));
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.findSource = function(id) {
|
||||
return _backgroundSources.find(function(d) {
|
||||
return d.id && d.id === id;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
background.bing = function() {
|
||||
background.baseLayerSource(background.findSource('Bing'));
|
||||
};
|
||||
|
||||
|
||||
background.showsLayer = function(d) {
|
||||
var baseSource = baseLayer.source();
|
||||
if (!d || !baseSource) return false;
|
||||
return d.id === baseSource.id ||
|
||||
_overlayLayers.some(function(layer) { return d.id === layer.source().id; });
|
||||
};
|
||||
|
||||
|
||||
background.overlayLayerSources = function() {
|
||||
return _overlayLayers.map(function (l) { return l.source(); });
|
||||
};
|
||||
|
||||
|
||||
background.toggleOverlayLayer = function(d) {
|
||||
var layer;
|
||||
for (var i = 0; i < _overlayLayers.length; i++) {
|
||||
layer = _overlayLayers[i];
|
||||
if (layer.source() === d) {
|
||||
_overlayLayers.splice(i, 1);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
layer = rendererTileLayer(context)
|
||||
.source(d)
|
||||
.projection(context.projection)
|
||||
.dimensions(baseLayer.dimensions()
|
||||
);
|
||||
|
||||
_overlayLayers.push(layer);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
};
|
||||
|
||||
|
||||
background.nudge = function(d, zoom) {
|
||||
baseLayer.source().nudge(d, zoom);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.offset = function(d) {
|
||||
if (!arguments.length) return baseLayer.source().offset();
|
||||
baseLayer.source().offset(d);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.brightness = function(d) {
|
||||
if (!arguments.length) return _brightness;
|
||||
_brightness = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.contrast = function(d) {
|
||||
if (!arguments.length) return _contrast;
|
||||
_contrast = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.saturation = function(d) {
|
||||
if (!arguments.length) return _saturation;
|
||||
_saturation = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.sharpness = function(d) {
|
||||
if (!arguments.length) return _sharpness;
|
||||
_sharpness = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.init = function() {
|
||||
function parseMap(qmap) {
|
||||
if (!qmap) return false;
|
||||
var args = qmap.split('/').map(Number);
|
||||
if (args.length < 3 || args.some(isNaN)) return false;
|
||||
return geoExtent([args[2], args[1]]);
|
||||
}
|
||||
|
||||
var q = utilStringQs(window.location.hash.substring(1));
|
||||
var requested = q.background || q.layer;
|
||||
var extent = parseMap(q.map);
|
||||
var first;
|
||||
var best;
|
||||
|
||||
|
||||
data.imagery = data.imagery || [];
|
||||
data.imagery.features = {};
|
||||
|
||||
// build efficient index and querying for data.imagery
|
||||
var features = data.imagery.map(function(source) {
|
||||
if (!source.polygon) return null;
|
||||
|
||||
// Add an extra array nest to each element in `source.polygon`
|
||||
// so the rings are not treated as a bunch of holes:
|
||||
// what we have: [ [[outer],[hole],[hole]] ]
|
||||
// what we want: [ [[outer]],[[outer]],[[outer]] ]
|
||||
var rings = source.polygon.map(function(ring) { return [ring]; });
|
||||
|
||||
var feature = {
|
||||
type: 'Feature',
|
||||
properties: { id: source.id },
|
||||
geometry: { type: 'MultiPolygon', coordinates: rings }
|
||||
};
|
||||
|
||||
data.imagery.features[source.id] = feature;
|
||||
return feature;
|
||||
_imageryIndex.features[source.id] = feature;
|
||||
return feature;
|
||||
|
||||
}).filter(Boolean);
|
||||
|
||||
data.imagery.query = whichPolygon({
|
||||
type: 'FeatureCollection',
|
||||
features: features
|
||||
_imageryIndex.query = whichPolygon({ type: 'FeatureCollection', features: features });
|
||||
|
||||
|
||||
// Instantiate `rendererBackgroundSource` objects for each source
|
||||
_imageryIndex.backgrounds = sources.map(source => {
|
||||
if (source.type === 'bing') {
|
||||
return rendererBackgroundSource.Bing(source, dispatch);
|
||||
} else if (/^EsriWorldImagery/.test(source.id)) {
|
||||
return rendererBackgroundSource.Esri(source);
|
||||
} else {
|
||||
return rendererBackgroundSource(source);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add all the available imagery sources
|
||||
_backgroundSources = data.imagery.map(function(source) {
|
||||
if (source.type === 'bing') {
|
||||
return rendererBackgroundSource.Bing(source, dispatch);
|
||||
} else if (/^EsriWorldImagery/.test(source.id)) {
|
||||
return rendererBackgroundSource.Esri(source);
|
||||
} else {
|
||||
return rendererBackgroundSource(source);
|
||||
}
|
||||
});
|
||||
|
||||
first = _backgroundSources.length && _backgroundSources[0];
|
||||
|
||||
// Add 'None'
|
||||
_backgroundSources.unshift(rendererBackgroundSource.None());
|
||||
_imageryIndex.backgrounds.unshift(rendererBackgroundSource.None());
|
||||
|
||||
// Add 'Custom'
|
||||
var template = context.storage('background-custom-template') || '';
|
||||
var custom = rendererBackgroundSource.Custom(template);
|
||||
_backgroundSources.unshift(custom);
|
||||
let template = context.storage('background-custom-template') || '';
|
||||
const custom = rendererBackgroundSource.Custom(template);
|
||||
_imageryIndex.backgrounds.unshift(custom);
|
||||
|
||||
return _imageryIndex;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function background(selection) {
|
||||
const currSource = baseLayer.source();
|
||||
|
||||
// If we are displaying an Esri basemap at high zoom,
|
||||
// check its tilemap to see how high the zoom can go
|
||||
if (context.map().zoom() > 18) {
|
||||
if (currSource && /^EsriWorldImagery/.test(currSource.id)) {
|
||||
const center = context.map().center();
|
||||
currSource.fetchTilemap(center);
|
||||
}
|
||||
}
|
||||
|
||||
// Is the imagery valid here? - #4827
|
||||
const sources = background.sources(context.map().extent());
|
||||
const wasValid = _isValid;
|
||||
_isValid = !!sources.filter(d => d === currSource).length;
|
||||
|
||||
if (wasValid !== _isValid) { // change in valid status
|
||||
background.updateImagery();
|
||||
}
|
||||
|
||||
|
||||
let baseFilter = '';
|
||||
if (detected.cssfilters) {
|
||||
if (_brightness !== 1) {
|
||||
baseFilter += ` brightness(${_brightness})`;
|
||||
}
|
||||
if (_contrast !== 1) {
|
||||
baseFilter += ` contrast(${_contrast})`;
|
||||
}
|
||||
if (_saturation !== 1) {
|
||||
baseFilter += ` saturate(${_saturation})`;
|
||||
}
|
||||
if (_sharpness < 1) { // gaussian blur
|
||||
const blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness);
|
||||
baseFilter += ` blur(${blur}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
let base = selection.selectAll('.layer-background')
|
||||
.data([0]);
|
||||
|
||||
base = base.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-background')
|
||||
.merge(base);
|
||||
|
||||
if (detected.cssfilters) {
|
||||
base.style('filter', baseFilter || null);
|
||||
} else {
|
||||
base.style('opacity', _brightness);
|
||||
}
|
||||
|
||||
|
||||
let imagery = base.selectAll('.layer-imagery')
|
||||
.data([0]);
|
||||
|
||||
imagery.enter()
|
||||
.append('div')
|
||||
.attr('class', 'layer layer-imagery')
|
||||
.merge(imagery)
|
||||
.call(baseLayer);
|
||||
|
||||
|
||||
let maskFilter = '';
|
||||
let mixBlendMode = '';
|
||||
if (detected.cssfilters && _sharpness > 1) { // apply unsharp mask
|
||||
mixBlendMode = 'overlay';
|
||||
maskFilter = 'saturate(0) blur(3px) invert(1)';
|
||||
|
||||
let contrast = _sharpness - 1;
|
||||
maskFilter += ` contrast(${contrast})`;
|
||||
|
||||
let brightness = d3_interpolateNumber(1, 0.85)(_sharpness - 1);
|
||||
maskFilter += ` brightness(${brightness})`;
|
||||
}
|
||||
|
||||
let mask = base.selectAll('.layer-unsharp-mask')
|
||||
.data(detected.cssfilters && _sharpness > 1 ? [0] : []);
|
||||
|
||||
mask.exit()
|
||||
.remove();
|
||||
|
||||
mask.enter()
|
||||
.append('div')
|
||||
.attr('class', 'layer layer-mask layer-unsharp-mask')
|
||||
.merge(mask)
|
||||
.call(baseLayer)
|
||||
.style('filter', maskFilter || null)
|
||||
.style('mix-blend-mode', mixBlendMode || null);
|
||||
|
||||
|
||||
let overlays = selection.selectAll('.layer-overlay')
|
||||
.data(_overlayLayers, d => d.source().name());
|
||||
|
||||
overlays.exit()
|
||||
.remove();
|
||||
|
||||
overlays.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-overlay')
|
||||
.merge(overlays)
|
||||
.each((layer, i, nodes) => d3_select(nodes[i]).call(layer));
|
||||
}
|
||||
|
||||
|
||||
background.updateImagery = function() {
|
||||
let currSource = baseLayer.source();
|
||||
if (context.inIntro() || !currSource) return;
|
||||
|
||||
let o = _overlayLayers
|
||||
.filter(d => !d.source().isLocatorOverlay() && !d.source().isHidden())
|
||||
.map(d => d.source().id)
|
||||
.join(',');
|
||||
|
||||
const meters = geoOffsetToMeters(currSource.offset());
|
||||
const EPSILON = 0.01;
|
||||
const x = +meters[0].toFixed(2);
|
||||
const y = +meters[1].toFixed(2);
|
||||
let q = utilStringQs(window.location.hash.substring(1));
|
||||
|
||||
let id = currSource.id;
|
||||
if (id === 'custom') {
|
||||
id = `custom:${currSource.template()}`;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
q.background = id;
|
||||
} else {
|
||||
delete q.background;
|
||||
}
|
||||
|
||||
if (o) {
|
||||
q.overlays = o;
|
||||
} else {
|
||||
delete q.overlays;
|
||||
}
|
||||
|
||||
if (Math.abs(x) > EPSILON || Math.abs(y) > EPSILON) {
|
||||
q.offset = `${x},${y}`;
|
||||
} else {
|
||||
delete q.offset;
|
||||
}
|
||||
|
||||
if (!window.mocha) {
|
||||
window.location.replace('#' + utilQsString(q, true));
|
||||
}
|
||||
|
||||
let imageryUsed = [];
|
||||
let photoOverlaysUsed = [];
|
||||
|
||||
const currUsed = currSource.imageryUsed();
|
||||
if (currUsed && _isValid) {
|
||||
imageryUsed.push(currUsed);
|
||||
}
|
||||
|
||||
_overlayLayers
|
||||
.filter(d => !d.source().isLocatorOverlay() && !d.source().isHidden())
|
||||
.forEach(d => imageryUsed.push(d.source().imageryUsed()));
|
||||
|
||||
const dataLayer = context.layers().layer('data');
|
||||
if (dataLayer && dataLayer.enabled() && dataLayer.hasData()) {
|
||||
imageryUsed.push(dataLayer.getSrc());
|
||||
}
|
||||
|
||||
const photoOverlayLayers = {
|
||||
streetside: 'Bing Streetside',
|
||||
mapillary: 'Mapillary Images',
|
||||
'mapillary-map-features': 'Mapillary Map Features',
|
||||
'mapillary-signs': 'Mapillary Signs',
|
||||
openstreetcam: 'OpenStreetCam Images'
|
||||
};
|
||||
|
||||
for (let layerID in photoOverlayLayers) {
|
||||
const layer = context.layers().layer(layerID);
|
||||
if (layer && layer.enabled()) {
|
||||
photoOverlaysUsed.push(layerID);
|
||||
imageryUsed.push(photoOverlayLayers[layerID]);
|
||||
}
|
||||
}
|
||||
|
||||
context.history().imageryUsed(imageryUsed);
|
||||
context.history().photoOverlaysUsed(photoOverlaysUsed);
|
||||
};
|
||||
|
||||
|
||||
background.sources = (extent, zoom, includeCurrent) => {
|
||||
if (!_imageryIndex) return []; // called before init()?
|
||||
|
||||
let visible = {};
|
||||
(_imageryIndex.query.bbox(extent.rectangle(), true) || [])
|
||||
.forEach(d => visible[d.id] = true);
|
||||
|
||||
const currSource = baseLayer.source();
|
||||
|
||||
return _imageryIndex.backgrounds.filter(source => {
|
||||
if (!source.polygon) return true; // always include imagery with worldwide coverage
|
||||
if (includeCurrent && currSource === source) return true; // optionally include the current imagery
|
||||
if (zoom && zoom < 6) return false; // optionally exclude local imagery at low zooms
|
||||
return visible[source.id]; // include imagery visible in given extent
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
background.dimensions = (val) => {
|
||||
if (!val) return;
|
||||
baseLayer.dimensions(val);
|
||||
_overlayLayers.forEach(layer => layer.dimensions(val));
|
||||
};
|
||||
|
||||
|
||||
background.baseLayerSource = function(d) {
|
||||
if (!arguments.length) return baseLayer.source();
|
||||
|
||||
// test source against OSM imagery blacklists..
|
||||
const osm = context.connection();
|
||||
if (!osm) return background;
|
||||
|
||||
const blacklists = osm.imageryBlacklists();
|
||||
const template = d.template();
|
||||
let fail = false;
|
||||
let tested = 0;
|
||||
let regex;
|
||||
|
||||
for (let i = 0; i < blacklists.length; i++) {
|
||||
try {
|
||||
regex = new RegExp(blacklists[i]);
|
||||
fail = regex.test(template);
|
||||
tested++;
|
||||
if (fail) break;
|
||||
} catch (e) {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ensure at least one test was run.
|
||||
if (!tested) {
|
||||
regex = new RegExp('.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*');
|
||||
fail = regex.test(template);
|
||||
}
|
||||
|
||||
baseLayer.source(!fail ? d : background.findSource('none'));
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.findSource = (id) => {
|
||||
if (!id || !_imageryIndex) return null; // called before init()?
|
||||
return _imageryIndex.backgrounds.find(d => d.id && d.id === id);
|
||||
};
|
||||
|
||||
|
||||
background.bing = () => {
|
||||
background.baseLayerSource(background.findSource('Bing'));
|
||||
};
|
||||
|
||||
|
||||
background.showsLayer = (d) => {
|
||||
const currSource = baseLayer.source();
|
||||
if (!d || !currSource) return false;
|
||||
return d.id === currSource.id || _overlayLayers.some(layer => d.id === layer.source().id);
|
||||
};
|
||||
|
||||
|
||||
background.overlayLayerSources = () => {
|
||||
return _overlayLayers.map(layer => layer.source());
|
||||
};
|
||||
|
||||
|
||||
background.toggleOverlayLayer = (d) => {
|
||||
let layer;
|
||||
for (let i = 0; i < _overlayLayers.length; i++) {
|
||||
layer = _overlayLayers[i];
|
||||
if (layer.source() === d) {
|
||||
_overlayLayers.splice(i, 1);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
layer = rendererTileLayer(context)
|
||||
.source(d)
|
||||
.projection(context.projection)
|
||||
.dimensions(baseLayer.dimensions()
|
||||
);
|
||||
|
||||
_overlayLayers.push(layer);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
};
|
||||
|
||||
|
||||
background.nudge = (d, zoom) => {
|
||||
const currSource = baseLayer.source();
|
||||
if (currSource) {
|
||||
currSource.nudge(d, zoom);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
}
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.offset = function(d) {
|
||||
const currSource = baseLayer.source();
|
||||
if (!arguments.length) {
|
||||
return (currSource && currSource.offset()) || [0, 0];
|
||||
}
|
||||
if (currSource) {
|
||||
currSource.offset(d);
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
}
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.brightness = function(d) {
|
||||
if (!arguments.length) return _brightness;
|
||||
_brightness = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.contrast = function(d) {
|
||||
if (!arguments.length) return _contrast;
|
||||
_contrast = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.saturation = function(d) {
|
||||
if (!arguments.length) return _saturation;
|
||||
_saturation = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.sharpness = function(d) {
|
||||
if (!arguments.length) return _sharpness;
|
||||
_sharpness = d;
|
||||
if (context.mode()) dispatch.call('change');
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
background.init = () => {
|
||||
function parseMapParams(qmap) {
|
||||
if (!qmap) return false;
|
||||
const params = qmap.split('/').map(Number);
|
||||
if (params.length < 3 || params.some(isNaN)) return false;
|
||||
return geoExtent([params[2], params[1]]); // lon,lat
|
||||
}
|
||||
|
||||
const q = utilStringQs(window.location.hash.substring(1));
|
||||
const requested = q.background || q.layer;
|
||||
let extent = parseMapParams(q.map);
|
||||
|
||||
ensureImageryIndex()
|
||||
.then(imageryIndex => {
|
||||
const first = imageryIndex.backgrounds.length && imageryIndex.backgrounds[0];
|
||||
|
||||
let best;
|
||||
if (!requested && extent) {
|
||||
best = background.sources(extent).find(s => s.best());
|
||||
}
|
||||
|
||||
// Decide which background layer to display
|
||||
if (!requested && extent) {
|
||||
best = this.sources(extent).find(function(s) { return s.best(); });
|
||||
}
|
||||
if (requested && requested.indexOf('custom:') === 0) {
|
||||
template = requested.replace(/^custom:/, '');
|
||||
background.baseLayerSource(custom.template(template));
|
||||
context.storage('background-custom-template', template);
|
||||
const template = requested.replace(/^custom:/, '');
|
||||
const custom = background.findSource('custom');
|
||||
background.baseLayerSource(custom.template(template));
|
||||
context.storage('background-custom-template', template);
|
||||
} else {
|
||||
background.baseLayerSource(
|
||||
background.findSource(requested) ||
|
||||
best ||
|
||||
background.findSource(context.storage('background-last-used')) ||
|
||||
background.findSource('Bing') ||
|
||||
first ||
|
||||
background.findSource('none')
|
||||
);
|
||||
background.baseLayerSource(
|
||||
background.findSource(requested) ||
|
||||
best ||
|
||||
background.findSource(context.storage('background-last-used')) ||
|
||||
background.findSource('Bing') ||
|
||||
first ||
|
||||
background.findSource('none')
|
||||
);
|
||||
}
|
||||
|
||||
var locator = _backgroundSources.find(function(d) {
|
||||
return d.overlay && d.default;
|
||||
});
|
||||
|
||||
const locator = imageryIndex.backgrounds.find(d => d.overlay && d.default);
|
||||
if (locator) {
|
||||
background.toggleOverlayLayer(locator);
|
||||
background.toggleOverlayLayer(locator);
|
||||
}
|
||||
|
||||
var overlays = (q.overlays || '').split(',');
|
||||
overlays.forEach(function(overlay) {
|
||||
overlay = background.findSource(overlay);
|
||||
if (overlay) {
|
||||
background.toggleOverlayLayer(overlay);
|
||||
}
|
||||
const overlays = (q.overlays || '').split(',');
|
||||
overlays.forEach(overlay => {
|
||||
overlay = background.findSource(overlay);
|
||||
if (overlay) {
|
||||
background.toggleOverlayLayer(overlay);
|
||||
}
|
||||
});
|
||||
|
||||
if (q.gpx) {
|
||||
var gpx = context.layers().layer('data');
|
||||
if (gpx) {
|
||||
gpx.url(q.gpx, '.gpx');
|
||||
}
|
||||
if (q.gpx) { // todo: move elsewhere - this doesn't belong in background
|
||||
const gpx = context.layers().layer('data');
|
||||
if (gpx) {
|
||||
gpx.url(q.gpx, '.gpx');
|
||||
}
|
||||
}
|
||||
|
||||
if (q.offset) {
|
||||
var offset = q.offset.replace(/;/g, ',').split(',').map(function(n) {
|
||||
return !isNaN(n) && n;
|
||||
});
|
||||
const offset = q.offset
|
||||
.replace(/;/g, ',')
|
||||
.split(',')
|
||||
.map(n => !isNaN(n) && n);
|
||||
|
||||
if (offset.length === 2) {
|
||||
background.offset(geoMetersToOffset(offset));
|
||||
}
|
||||
if (offset.length === 2) {
|
||||
background.offset(geoMetersToOffset(offset));
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.catch(() => { /* ignore */ });
|
||||
};
|
||||
|
||||
|
||||
return utilRebind(background, dispatch, 'on');
|
||||
return utilRebind(background, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ export function rendererBackgroundSource(data) {
|
||||
source.zoomExtent = data.zoomExtent || [0, 22];
|
||||
source.overzoom = data.overzoom !== false;
|
||||
|
||||
source.offset = function(_) {
|
||||
source.offset = function(val) {
|
||||
if (!arguments.length) return offset;
|
||||
offset = _;
|
||||
offset = val;
|
||||
return source;
|
||||
};
|
||||
|
||||
|
||||
+13
-11
@@ -1,6 +1,5 @@
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { data } from '../../data';
|
||||
import { svgPath } from './helpers';
|
||||
|
||||
|
||||
@@ -70,19 +69,22 @@ export function svgDebug(projection, context) {
|
||||
|
||||
// imagery
|
||||
const extent = context.map().extent();
|
||||
const matchImagery = (showImagery && data.imagery.query.bbox(extent.rectangle(), true)) || [];
|
||||
const features = matchImagery.map(d => data.imagery.features[d.id]);
|
||||
context.data().get('imagery')
|
||||
.then(d => {
|
||||
const hits = (showImagery && d.query.bbox(extent.rectangle(), true)) || [];
|
||||
const features = hits.map(d => d.features[d.id]);
|
||||
|
||||
let imagery = layer.selectAll('path.debug-imagery')
|
||||
.data(features);
|
||||
let imagery = layer.selectAll('path.debug-imagery')
|
||||
.data(features);
|
||||
|
||||
imagery.exit()
|
||||
.remove();
|
||||
|
||||
imagery.enter()
|
||||
.append('path')
|
||||
.attr('class', 'debug-imagery debug orange');
|
||||
imagery.exit()
|
||||
.remove();
|
||||
|
||||
imagery.enter()
|
||||
.append('path')
|
||||
.attr('class', 'debug-imagery debug orange');
|
||||
})
|
||||
.catch(() => { /* ignore */ });
|
||||
|
||||
// downloaded
|
||||
const osm = context.connection();
|
||||
|
||||
+79
-78
@@ -4,102 +4,103 @@ import { t } from '../util/locale';
|
||||
|
||||
|
||||
export function uiAttribution(context) {
|
||||
var selection;
|
||||
let _selection = d3_select(null);
|
||||
|
||||
|
||||
function attribution(data, klass) {
|
||||
var div = selection.selectAll('.' + klass)
|
||||
.data([0]);
|
||||
function render(selection, data, klass) {
|
||||
let div = selection.selectAll(`.${klass}`)
|
||||
.data([0]);
|
||||
|
||||
div = div.enter()
|
||||
.append('div')
|
||||
.attr('class', klass)
|
||||
.merge(div);
|
||||
div = div.enter()
|
||||
.append('div')
|
||||
.attr('class', klass)
|
||||
.merge(div);
|
||||
|
||||
|
||||
var background = div.selectAll('.attribution')
|
||||
.data(data, function(d) { return d.name(); });
|
||||
let attributions = div.selectAll('.attribution')
|
||||
.data(data, d => d.id);
|
||||
|
||||
background.exit()
|
||||
.remove();
|
||||
attributions.exit()
|
||||
.remove();
|
||||
|
||||
background = background.enter()
|
||||
.append('span')
|
||||
.attr('class', 'attribution')
|
||||
.each(function(d) {
|
||||
if (d.terms_html) {
|
||||
d3_select(this)
|
||||
.html(d.terms_html);
|
||||
return;
|
||||
}
|
||||
attributions = attributions.enter()
|
||||
.append('span')
|
||||
.attr('class', 'attribution')
|
||||
.each((d, i, nodes) => {
|
||||
let attribution = d3_select(nodes[i]);
|
||||
|
||||
var selection;
|
||||
if (d.terms_url) {
|
||||
selection = d3_select(this)
|
||||
.append('a')
|
||||
.attr('href', d.terms_url)
|
||||
.attr('target', '_blank');
|
||||
} else {
|
||||
selection = d3_select(this);
|
||||
}
|
||||
if (d.terms_html) {
|
||||
attribution.html(d.terms_html);
|
||||
return;
|
||||
}
|
||||
|
||||
if (d.terms_url) {
|
||||
attribution = attribution
|
||||
.append('a')
|
||||
.attr('href', d.terms_url)
|
||||
.attr('target', '_blank');
|
||||
}
|
||||
|
||||
const sourceID = d.id.replace(/\./g, '<TX_DOT>');
|
||||
const terms_text = t(`imagery.${sourceID}.attribution.text`,
|
||||
{ default: d.terms_text || d.id || d.name() }
|
||||
);
|
||||
|
||||
if (d.icon && !d.overlay) {
|
||||
attribution
|
||||
.append('img')
|
||||
.attr('class', 'source-image')
|
||||
.attr('src', d.icon);
|
||||
}
|
||||
|
||||
attribution
|
||||
.append('span')
|
||||
.attr('class', 'attribution-text')
|
||||
.text(terms_text);
|
||||
})
|
||||
.merge(attributions);
|
||||
|
||||
|
||||
var id_safe = d.id.replace(/\./g, '<TX_DOT>');
|
||||
var terms_text = t('imagery.' + id_safe + '.attribution.text',
|
||||
{ default: d.terms_text || d.id || d.name() }
|
||||
);
|
||||
let copyright = attributions.selectAll('.copyright-notice')
|
||||
.data(d => {
|
||||
let notice = d.copyrightNotices(context.map().zoom(), context.map().extent());
|
||||
return notice ? [notice] : [];
|
||||
});
|
||||
|
||||
if (d.icon && !d.overlay) {
|
||||
selection
|
||||
.append('img')
|
||||
.attr('class', 'source-image')
|
||||
.attr('src', d.icon);
|
||||
}
|
||||
copyright.exit()
|
||||
.remove();
|
||||
|
||||
selection
|
||||
.append('span')
|
||||
.attr('class', 'attribution-text')
|
||||
.text(terms_text);
|
||||
})
|
||||
.merge(background);
|
||||
copyright = copyright.enter()
|
||||
.append('span')
|
||||
.attr('class', 'copyright-notice')
|
||||
.merge(copyright);
|
||||
|
||||
copyright
|
||||
.text(String);
|
||||
}
|
||||
|
||||
|
||||
var copyright = background.selectAll('.copyright-notice')
|
||||
.data(function(d) {
|
||||
var notice = d.copyrightNotices(context.map().zoom(), context.map().extent());
|
||||
return notice ? [notice] : [];
|
||||
});
|
||||
function update() {
|
||||
let baselayer = context.background().baseLayerSource();
|
||||
_selection
|
||||
.call(render, (baselayer ? [baselayer] : []), 'base-layer-attribution');
|
||||
|
||||
copyright.exit()
|
||||
.remove();
|
||||
|
||||
copyright = copyright.enter()
|
||||
.append('span')
|
||||
.attr('class', 'copyright-notice')
|
||||
.merge(copyright);
|
||||
|
||||
copyright
|
||||
.text(String);
|
||||
}
|
||||
const z = context.map().zoom();
|
||||
let overlays = context.background().overlayLayerSources() || [];
|
||||
_selection
|
||||
.call(render, overlays.filter(s => s.validZoom(z)), 'overlay-layer-attribution');
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
attribution([context.background().baseLayerSource()], 'base-layer-attribution');
|
||||
attribution(context.background().overlayLayerSources().filter(function (s) {
|
||||
return s.validZoom(context.map().zoom());
|
||||
}), 'overlay-layer-attribution');
|
||||
}
|
||||
return function(selection) {
|
||||
_selection = selection;
|
||||
|
||||
context.background()
|
||||
.on('change.attribution', update);
|
||||
|
||||
return function(select) {
|
||||
selection = select;
|
||||
context.map()
|
||||
.on('move.attribution', _throttle(update, 400, { leading: false }));
|
||||
|
||||
context.background()
|
||||
.on('change.attribution', update);
|
||||
|
||||
context.map()
|
||||
.on('move.attribution', _throttle(update, 400, {leading: false}));
|
||||
|
||||
update();
|
||||
};
|
||||
update();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export function uiPanelBackground(context) {
|
||||
|
||||
function redraw(selection) {
|
||||
var source = background.baseLayerSource();
|
||||
if (!source) return;
|
||||
|
||||
var isDG = (source.id.match(/^DigitalGlobe/i) !== null);
|
||||
|
||||
if (currSourceName !== source.name()) {
|
||||
|
||||
@@ -168,4 +168,5 @@ sources.concat(keep).forEach(source => {
|
||||
|
||||
imagery.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
fs.writeFileSync('data/imagery.json', prettyStringify({ dataImagery: imagery }));
|
||||
fs.writeFileSync('data/imagery.json', prettyStringify(imagery));
|
||||
fs.writeFileSync('dist/data/imagery.min.json', JSON.stringify(imagery));
|
||||
|
||||
Reference in New Issue
Block a user