diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 01b7dff90..611086449 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -12,6 +12,17 @@ function localeDateString(s) { return d.toLocaleDateString(); } +function vintageRange(vintage) { + var s; + if (vintage.start || vintage.end) { + s = (vintage.start || '?'); + if (vintage.start !== vintage.end) { + s += ' - ' + (vintage.end || '?'); + } + } + return s; +} + export function rendererBackgroundSource(data) { var source = _.clone(data), @@ -114,11 +125,13 @@ export function rendererBackgroundSource(data) { source.copyrightNotices = function() {}; - source.getVintage = function(center, zoom, callback) { - callback(null, { + source.getVintage = function(center, tileCoord, callback) { + var vintage = { start: localeDateString(source.startDate), end: localeDateString(source.endDate) - }); + }; + vintage.range = vintageRange(vintage); + callback(null, vintage); }; @@ -136,6 +149,7 @@ rendererBackgroundSource.Bing = function(data, dispatch) { key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key + '&jsonp={callback}', + cache = {}, providers = []; jsonpRequest(url, function(json) { @@ -168,22 +182,32 @@ rendererBackgroundSource.Bing = function(data, dispatch) { }; - bing.getVintage = function(center, zoom, callback) { - zoom = Math.min(zoom, 21); - - var centerPoint = center[1] + ',' + center[0], + bing.getVintage = function(center, tileCoord, callback) { + var tileId = tileCoord.slice(0, 3).join('/'), + zoom = Math.min(tileCoord[2], 21), + centerPoint = center[1] + ',' + center[0], // lat,lng url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + '?zl=' + zoom + '&key=' + key + '&jsonp={callback}'; + if (!cache[tileId]) { + cache[tileId] = {}; + } + if (cache[tileId] && cache[tileId].vintage) { + return callback(null, cache[tileId].vintage); + } + jsonpRequest(url, function(result) { var err = (!result && 'Unknown Error') || result.errorDetails; if (err) { return callback(err); } else { - return callback(null, { + var vintage = { start: localeDateString(result.resourceSets[0].resources[0].vintageStart), end: localeDateString(result.resourceSets[0].resources[0].vintageEnd) - }); + }; + vintage.range = vintageRange(vintage); + cache[tileId].vintage = vintage; + return callback(null, vintage); } }); }; diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index 0c141a28e..292391d6d 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { utilPrefixCSSProperty } from '../util/index'; +import { geoEuclideanDistance } from '../geo'; +import { utilPrefixCSSProperty } from '../util'; import { rendererBackgroundSource } from './background_source.js'; @@ -98,7 +99,7 @@ export function rendererTileLayer(context) { tile().forEach(function(d) { addSource(d); if (d[3] === '') return; - if (typeof d[3] !== 'string') return; // Workaround for chrome crash https://github.com/openstreetmap/iD/issues/2295 + if (typeof d[3] !== 'string') return; // Workaround for #2295 requests.push(d); if (cache[d[3]] === false && lookUp(d)) { requests.push(addSource(lookUp(d))); @@ -119,6 +120,7 @@ export function rendererTileLayer(context) { source.offset()[1] * Math.pow(2, z) ]; + function load(d) { cache[d[3]] = true; d3.select(this) @@ -146,9 +148,11 @@ export function rendererTileLayer(context) { 'scale(' + scale + ',' + scale + ')'; } - function debugCoordinates(d) { + function tileCenter(d) { var _ts = tileSize * Math.pow(2, z - d[2]); var scale = tileSizeAtZoom(d, z); + // FIXME: this scale * tileSize/number stuff is hacky, and more for displaying the debug text. + // It's not really the center of the tile, but it is guaranteed to be somewhere in the tile. return [ ((d[0] * _ts) - tileOrigin[0] + pixelOffset[0] + scale * (tileSize / 4)), ((d[1] * _ts) - tileOrigin[1] + pixelOffset[1] + scale * (tileSize / 2)) @@ -156,16 +160,35 @@ export function rendererTileLayer(context) { } function debugTransform(d) { - var coord = debugCoordinates(d); + var coord = tileCenter(d); return 'translate(' + coord[0] + 'px,' + coord[1] + 'px)'; } + + // Pick a representative tile near the center of the viewport + // (This is useful for sampling the imagery vintage) + var dims = tile.size(), + mapCenter = [dims[0] / 2, dims[1] / 2], + minDist = Math.max(dims[0], dims[1]), + nearCenter; + + requests.forEach(function(d) { + var c = tileCenter(d); + var dist = geoEuclideanDistance(c, mapCenter); + if (dist < minDist) { + minDist = dist; + nearCenter = d; + } + }); + + var image = selection.selectAll('img') .data(requests, function(d) { return d[3]; }); image.exit() .style(transformProp, imageTransform) .classed('tile-removing', true) + .classed('tile-center', false) .each(function() { var tile = d3.select(this); window.setTimeout(function() { @@ -184,7 +207,9 @@ export function rendererTileLayer(context) { .merge(image) .style(transformProp, imageTransform) .classed('tile-debug', showDebug) - .classed('tile-removing', false); + .classed('tile-removing', false) + .classed('tile-center', function(d) { return d === nearCenter; }); + var debug = selection.selectAll('.tile-label-debug') @@ -219,20 +244,11 @@ export function rendererTileLayer(context) { .selectAll('.tile-label-debug-vintage') .each(function(d) { var span = d3.select(this); - var center = context.projection.invert(debugCoordinates(d)); - source.getVintage(center, d[2], function(err, result) { - var vintage = ''; - if (result) { - if (result.start || result.end) { - vintage = (result.start || '?'); - if (result.start !== result.end) { - vintage += ' - ' + (result.end || '?'); - } - } - } - - span - .text(vintage || t('infobox.imagery.vintage') + ': ' + t('infobox.imagery.unknown')); + var center = context.projection.invert(tileCenter(d)); + source.getVintage(center, d, function(err, result) { + span.text((result && result.range) || + t('infobox.imagery.vintage') + ': ' + t('infobox.imagery.unknown') + ); }); }); } diff --git a/modules/ui/panels/imagery.js b/modules/ui/panels/imagery.js index 3d57fa65f..7e22d09d0 100644 --- a/modules/ui/panels/imagery.js +++ b/modules/ui/panels/imagery.js @@ -64,31 +64,20 @@ export function uiPanelImagery(context) { var debouncedGetVintage = _.debounce(getVintage, 250); function getVintage(selection) { - var tile = d3.select('.layer-background img'); + var tile = d3.select('.layer-background img.tile-center'); // tile near viewport center if (tile.empty()) return; - var tiledata = tile.datum(), - zoom = tiledata[2] || Math.floor(context.map().zoom()), + var d = tile.datum(), + zoom = (d && d.length >= 3 && d[2]) || Math.floor(context.map().zoom()), center = context.map().center(); currZoom = String(zoom); selection.selectAll('.zoom') .text(currZoom); - background.baseLayerSource().getVintage(center, currZoom, function(err, result) { - if (!result) { - currVintage = t('infobox.imagery.unknown'); - } else { - if (result.start || result.end) { - currVintage = (result.start || '?'); - if (result.start !== result.end) { - currVintage += ' - ' + (result.end || '?'); - } - } else { - currVintage = t('infobox.imagery.unknown'); - } - } - + if (!d || !d.length >= 3) return; + background.baseLayerSource().getVintage(center, d, function(err, result) { + currVintage = (result && result.range) || t('infobox.imagery.unknown'); selection.selectAll('.vintage') .text(currVintage); });