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 @@
+