diff --git a/Makefile b/Makefile index f99342a98..4ba02a7b0 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ data/data.js: $(DATA_FILES) js/lib/d3.v3.js \ js/lib/d3.combobox.js \ js/lib/d3.geo.tile.js \ + js/lib/d3.jsonp.js \ js/lib/d3.keybinding.js \ js/lib/d3.one.js \ js/lib/d3.size.js \ diff --git a/css/app.css b/css/app.css index 3039c6b97..1d193d849 100644 --- a/css/app.css +++ b/css/app.css @@ -1668,6 +1668,11 @@ div.combobox { color:#fff; } +.source-image { + height:20px; + vertical-align:top; +} + .user-list a:not(:last-child):after { content: ', '; } diff --git a/img/bing.png b/img/bing.png deleted file mode 100644 index a5a5c38dd..000000000 Binary files a/img/bing.png and /dev/null differ diff --git a/img/bing_maps.png b/img/bing_maps.png new file mode 100644 index 000000000..bbc4b1dfa Binary files /dev/null and b/img/bing_maps.png differ diff --git a/index.html b/index.html index 0103bb5ee..51ceae68d 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index d6696ca02..8048175e9 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -102,7 +102,13 @@ window.iD = function () { context.zoomOut = map.zoomOut; /* Background */ - var backgroundSources = iD.data.imagery.map(iD.BackgroundSource.template); + var backgroundSources = iD.data.imagery.map(function(source) { + if (source.sourcetag === 'Bing') { + return iD.BackgroundSource.Bing(source, context.background().dispatch); + } else { + return iD.BackgroundSource.template(source); + } + }); backgroundSources.push(iD.BackgroundSource.Custom); context.backgroundSources = function() { diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 9fe8a7378..83ffc31a8 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -168,14 +168,17 @@ iD.Background = function() { } } + background.dispatch = d3.dispatch('change'); + background.source = function(_) { if (!arguments.length) return source; source = _; cache = {}; tile.scaleExtent((source.data && source.data.scaleExtent) || [1, 20]); setHash(source); + background.dispatch.change(); return background; }; - return background; + return d3.rebind(background, background.dispatch, 'on'); }; diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js index 3ddc76477..46c93821f 100644 --- a/js/id/renderer/background_source.js +++ b/js/id/renderer/background_source.js @@ -29,12 +29,55 @@ iD.BackgroundSource.template = function(data) { } generator.data = data; + generator.copyrightNotices = function() {}; return generator; }; +iD.BackgroundSource.Bing = function(data, dispatch) { + // http://msdn.microsoft.com/en-us/library/ff701716.aspx + // http://msdn.microsoft.com/en-us/library/ff701701.aspx + + var bing = iD.BackgroundSource.template(data), + key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM + url = 'http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + + key + '&jsonp={callback}', + providers = []; + + d3.jsonp(url, function(json) { + providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) { + return { + attribution: provider.attribution, + areas: provider.coverageAreas.map(function(area) { + return { + zoom: [area.zoomMin, area.zoomMax], + extent: iD.geo.Extent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) + }; + }) + }; + }); + dispatch.change(); + }); + + bing.copyrightNotices = function(zoom, extent) { + zoom = Math.min(zoom, 21); + return providers.filter(function(provider) { + return _.any(provider.areas, function(area) { + return extent.intersects(area.extent) && + area.zoom[0] <= zoom && + area.zoom[1] >= zoom; + }); + }).map(function(provider) { + return provider.attribution; + }).join(', '); + }; + + return bing; +}; + iD.BackgroundSource.Custom = function() { - var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); + var template = window.prompt('Enter a tile template. ' + + 'Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); if (!template) return null; return iD.BackgroundSource.template({ template: template, diff --git a/js/id/ui.js b/js/id/ui.js index 71394c34c..9b671a115 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -108,7 +108,6 @@ iD.ui = function(context) { linkList.append('li') .attr('class', 'attribution') .attr('tabindex', -1) - .data([context.background().source()]) .call(iD.ui.Attribution(context)); linkList.append('li') diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js index ec98a84b8..f06b04972 100644 --- a/js/id/ui/attribution.js +++ b/js/id/ui/attribution.js @@ -1,6 +1,8 @@ iD.ui.Attribution = function(context) { - return function attribution(selection) { - var d = selection.data()[0]; + var selection; + + function update() { + var d = context.background().source(); var provided_by = selection .html('') @@ -9,18 +11,37 @@ iD.ui.Attribution = function(context) { if (!d) return; - var desc = t('imagery.provided_by', { - source: (d.data.sourcetag || d.data.name) - }); + var source = d.data.sourcetag || d.data.name; + if (d.data.logo) { + source = ''; + } + var desc = t('imagery.provided_by', {source: source}); if (d.data.terms_url) { provided_by.append('a') - .attr('href', (d.data.terms_url || '')) + .attr('href', d.data.terms_url) .attr('target', '_blank') - .classed('disabled', !d.data.terms_url) - .text(desc); + .html(desc); } else { provided_by.text(desc); } + + var copyright = d.copyrightNotices(context.map().zoom(), context.map().extent()); + if (copyright) { + provided_by.append('span') + .text(copyright); + } + } + + return function(select) { + selection = select; + + context.background() + .on('change.attribution', update); + + context.map() + .on('move.attribution', _.throttle(update, 400)); + + update(); }; }; diff --git a/js/id/ui/background.js b/js/id/ui/background.js index 7e23e94e5..0595875c2 100644 --- a/js/id/ui/background.js +++ b/js/id/ui/background.js @@ -1,6 +1,5 @@ iD.ui.Background = function(context) { - var event = d3.dispatch('cancel', 'save'), - key = 'b', + var key = 'b', opacities = [1, 0.5, 0], directions = [ ['left', [1, 0]], @@ -57,16 +56,11 @@ iD.ui.Background = function(context) { } } - function selectLayer(d) { + function selectLayer() { content.selectAll('a.layer') .classed('selected', function(d) { return d.data.name === context.background().source().data.name; }); - - context.container() - .select('.attribution') - .data([d]) - .call(iD.ui.Attribution(context)); } function clickSetSource(d) { @@ -85,7 +79,7 @@ iD.ui.Background = function(context) { .imagery_used(d.data.sourcetag || d.data.name); } context.redraw(); - selectLayer(d); + selectLayer(); } function clickGpx(d) { @@ -139,7 +133,7 @@ iD.ui.Background = function(context) { layerLinks.exit() .remove(); - selectLayer(context.background().source()); + selectLayer(); } function clickNudge(d) { @@ -289,5 +283,5 @@ iD.ui.Background = function(context) { .call(keybinding); } - return d3.rebind(background, event, 'on'); + return background; }; diff --git a/js/lib/d3.jsonp.js b/js/lib/d3.jsonp.js new file mode 100644 index 000000000..e0cd4a80e --- /dev/null +++ b/js/lib/d3.jsonp.js @@ -0,0 +1,25 @@ +d3.jsonp = function (url, callback) { + function rand() { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + c = '', i = -1; + while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52)); + return c; + } + + function create(url) { + var e = url.match(/callback=d3.jsonp.(\w+)/), + c = e ? e[1] : rand(); + d3.jsonp[c] = function(data) { + callback(data); + delete d3.jsonp[c]; + script.remove(); + }; + return 'd3.jsonp.' + c; + } + + var cb = create(url), + script = d3.select('head') + .append('script') + .attr('type', 'text/javascript') + .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb)); +}; diff --git a/test/index.html b/test/index.html index 770d33e21..173e22592 100644 --- a/test/index.html +++ b/test/index.html @@ -26,6 +26,7 @@ +