diff --git a/data/core.yaml b/data/core.yaml
index 25fe9264b..56815412f 100644
--- a/data/core.yaml
+++ b/data/core.yaml
@@ -195,6 +195,8 @@ en:
title: Background
description: Background settings
percent_brightness: "{opacity}% brightness"
+ custom: Custom
+ custom_prompt: "Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme."
fix_misalignment: Fix misalignment
reset: reset
restore:
diff --git a/dist/locales/en.json b/dist/locales/en.json
index c612b9aa5..9cdfbd3b7 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -241,6 +241,8 @@
"title": "Background",
"description": "Background settings",
"percent_brightness": "{opacity}% brightness",
+ "custom": "Custom",
+ "custom_prompt": "Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.",
"fix_misalignment": "Fix misalignment",
"reset": "reset"
},
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index 479931ca3..72c3afca9 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -10,21 +10,19 @@ iD.Background = function(context) {
if (source.sourcetag === 'Bing') {
return iD.BackgroundSource.Bing(source, dispatch);
} else {
- return iD.BackgroundSource.template(source);
+ return iD.BackgroundSource(source);
}
});
- backgroundSources.push(iD.BackgroundSource.Custom);
-
function findSource(sourcetag) {
return _.find(backgroundSources, function(d) {
- return d.data.sourcetag && d.data.sourcetag === sourcetag;
+ return d.sourcetag && d.sourcetag === sourcetag;
});
}
function updateImagery() {
- var b = background.baseLayerSource().data,
- o = overlayLayers.map(function (d) { return d.source().data.sourcetag; }).join(','),
+ var b = background.baseLayerSource(),
+ o = overlayLayers.map(function (d) { return d.source().sourcetag; }).join(','),
q = iD.util.stringQs(location.hash.substring(1));
var tag = b.sourcetag;
@@ -54,7 +52,7 @@ iD.Background = function(context) {
}
overlayLayers.forEach(function (d) {
- imageryUsed.push(d.source().data.sourcetag || d.source().data.name);
+ imageryUsed.push(d.source().sourcetag || d.source().name);
});
if (background.showsGpxLayer()) {
@@ -82,7 +80,7 @@ iD.Background = function(context) {
gpx.call(gpxLayer);
var overlays = selection.selectAll('.overlay-layer')
- .data(overlayLayers, function(d) { return d.source().data.name });
+ .data(overlayLayers, function(d) { return d.source().name });
overlays.enter().insert('div', '.layer-data')
.attr('class', 'layer-layer overlay-layer');
@@ -96,11 +94,8 @@ iD.Background = function(context) {
}
background.sources = function(extent) {
- return backgroundSources.filter(function(layer) {
- return !layer.data.extents ||
- layer.data.extents.some(function(layerExtent) {
- return iD.geo.Extent(layerExtent).intersects(extent);
- });
+ return backgroundSources.filter(function(source) {
+ return source.intersects(extent);
});
};
@@ -149,7 +144,7 @@ iD.Background = function(context) {
background.showsLayer = function(d) {
return d === baseLayer.source() ||
- (d.data.name === 'Custom' && baseLayer.source().data.name === 'Custom') ||
+ (d.name === 'Custom' && baseLayer.source().name === 'Custom') ||
overlayLayers.some(function(l) { return l.source() === d; });
};
@@ -177,14 +172,14 @@ iD.Background = function(context) {
};
background.nudge = function(d, zoom) {
- baseLayer.nudge(d, zoom);
+ baseLayer.source().nudge(d, zoom);
dispatch.change();
return background;
};
background.offset = function(d) {
- if (!arguments.length) return baseLayer.offset();
- baseLayer.offset(d);
+ if (!arguments.length) return baseLayer.source().offset();
+ baseLayer.source().offset(d);
dispatch.change();
return background;
};
@@ -193,7 +188,7 @@ iD.Background = function(context) {
chosen = q.background || q.layer;
if (chosen && chosen.indexOf('custom:') === 0) {
- background.baseLayerSource(iD.BackgroundSource.template({
+ background.baseLayerSource(iD.BackgroundSource({
template: chosen.replace(/^custom:/, ''),
name: 'Custom'
}));
diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js
index 778e0c5cc..0c3a5cf84 100644
--- a/js/id/renderer/background_source.js
+++ b/js/id/renderer/background_source.js
@@ -1,9 +1,22 @@
-iD.BackgroundSource = {};
+iD.BackgroundSource = function(data) {
+ var source = _.clone(data),
+ offset = [0, 0];
-// derive the url of a 'quadkey' style tile from a coordinate object
-iD.BackgroundSource.template = function(data) {
+ source.scaleExtent = data.scaleExtent || [0, 20];
- function generator(coord) {
+ source.offset = function(_) {
+ if (!arguments.length) return offset;
+ offset = _;
+ return source;
+ };
+
+ source.nudge = function(_, zoomlevel) {
+ offset[0] += _[0] / Math.pow(2, zoomlevel);
+ offset[1] += _[1] / Math.pow(2, zoomlevel);
+ return source;
+ };
+
+ source.url = function(coord) {
var u = '';
for (var zoom = coord[2]; zoom > 0; zoom--) {
var b = 0;
@@ -28,19 +41,24 @@ iD.BackgroundSource.template = function(data) {
var subdomains = r.split(':')[1].split(',');
return subdomains[coord[2] % subdomains.length];
});
- }
+ };
- generator.data = data;
- generator.copyrightNotices = function() {};
+ source.intersects = function(extent) {
+ return !data.extents || data.extents.some(function(ex) {
+ return iD.geo.Extent(ex).intersects(extent);
+ });
+ };
- return generator;
+ source.copyrightNotices = function() {};
+
+ return source;
};
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),
+ var bing = iD.BackgroundSource(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}',
@@ -76,15 +94,3 @@ iD.BackgroundSource.Bing = function(data, dispatch) {
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.');
- if (!template) return null;
- return iD.BackgroundSource.template({
- template: template,
- name: 'Custom'
- });
-};
-
-iD.BackgroundSource.Custom.data = { 'name': 'Custom' };
diff --git a/js/id/renderer/tile_layer.js b/js/id/renderer/tile_layer.js
index 7f7ee6c0b..1f0ca76d6 100644
--- a/js/id/renderer/tile_layer.js
+++ b/js/id/renderer/tile_layer.js
@@ -3,8 +3,6 @@ iD.TileLayer = function() {
tile = d3.geo.tile(),
projection,
cache = {},
- offset = [0, 0],
- offsets = {},
tileOrigin,
z,
transformProp = iD.util.prefixCSSProperty('Transform'),
@@ -25,7 +23,7 @@ iD.TileLayer = function() {
function lookUp(d) {
for (var up = -1; up > -d[2]; up--) {
var tile = atZoom(d, up);
- if (cache[source(tile)] !== false) {
+ if (cache[source.url(tile)] !== false) {
return tile;
}
}
@@ -43,7 +41,7 @@ iD.TileLayer = function() {
}
function addSource(d) {
- d.push(source(d));
+ d.push(source.url(d));
return d;
}
@@ -83,8 +81,8 @@ iD.TileLayer = function() {
}
var pixelOffset = [
- Math.round(offset[0] * Math.pow(2, z)),
- Math.round(offset[1] * Math.pow(2, z))
+ Math.round(source.offset()[0] * Math.pow(2, z)),
+ Math.round(source.offset()[1] * Math.pow(2, z))
];
function load(d) {
@@ -141,19 +139,6 @@ iD.TileLayer = function() {
.classed('tile-removing', false);
}
- background.offset = function(_) {
- if (!arguments.length) return offset;
- offset = _;
- if (source.data) offsets[source.data.name] = offset;
- return background;
- };
-
- background.nudge = function(_, zoomlevel) {
- offset[0] += _[0] / Math.pow(2, zoomlevel);
- offset[1] += _[1] / Math.pow(2, zoomlevel);
- return background;
- };
-
background.projection = function(_) {
if (!arguments.length) return projection;
projection = _;
@@ -169,13 +154,8 @@ iD.TileLayer = function() {
background.source = function(_) {
if (!arguments.length) return source;
source = _;
- if (source.data) {
- offset = offsets[source.data.name] = offsets[source.data.name] || [0, 0];
- } else {
- offset = [0, 0];
- }
cache = {};
- tile.scaleExtent((source.data && source.data.scaleExtent) || [1, 20]);
+ tile.scaleExtent(source.scaleExtent);
return background;
};
diff --git a/js/id/ui/attribution.js b/js/id/ui/attribution.js
index 40ded77f0..bd499f7a1 100644
--- a/js/id/ui/attribution.js
+++ b/js/id/ui/attribution.js
@@ -8,22 +8,22 @@ iD.ui.Attribution = function(context) {
}
var attribution = selection.selectAll('.provided-by')
- .data([context.background().baseLayerSource()], function(d) { return d.data.name; });
+ .data([context.background().baseLayerSource()], function(d) { return d.name; });
attribution.enter()
.append('span')
.attr('class', 'provided-by')
.each(function(d) {
- var source = d.data.sourcetag || d.data.name;
+ var source = d.sourcetag || d.name;
- if (d.data.logo) {
- source = '
';
+ if (d.logo) {
+ source = '
';
}
- if (d.data.terms_url) {
+ if (d.terms_url) {
d3.select(this)
.append('a')
- .attr('href', d.data.terms_url)
+ .attr('href', d.terms_url)
.attr('target', '_blank')
.html(source);
} else {
diff --git a/js/id/ui/background.js b/js/id/ui/background.js
index f88ac2b4f..2c900f35c 100644
--- a/js/id/ui/background.js
+++ b/js/id/ui/background.js
@@ -28,7 +28,7 @@ iD.ui.Background = function(context) {
return context.background().showsLayer(d);
}
- content.selectAll('label.layer')
+ content.selectAll('label.layer, label.custom_layer')
.classed('active', active)
.selectAll('input')
.property('checked', active);
@@ -36,18 +36,24 @@ iD.ui.Background = function(context) {
function clickSetSource(d) {
d3.event.preventDefault();
- if (d.data.name === 'Custom') {
- var configured = d();
- if (!configured) {
- selectLayer();
- return;
- }
- d = configured;
- }
context.background().baseLayerSource(d);
selectLayer();
}
+ function clickCustom() {
+ d3.event.preventDefault();
+ var template = window.prompt(t('background.custom_prompt'));
+ if (!template) {
+ selectLayer();
+ return;
+ }
+ context.background().baseLayerSource(iD.BackgroundSource({
+ template: template,
+ name: 'Custom'
+ }));
+ selectLayer();
+ }
+
function clickSetOverlay(d) {
d3.event.preventDefault();
context.background().toggleOverlayLayer(d);
@@ -65,29 +71,27 @@ iD.ui.Background = function(context) {
.filter(filter);
var layerLinks = layerList.selectAll('label.layer')
- .data(sources, function(d) { return d.data.name; });
+ .data(sources, function(d) { return d.name; });
var layerInner = layerLinks.enter()
- .append('label')
+ .insert('label', '.custom_layer')
.attr('class', 'layer');
// only set tooltips for layers with tooltips
layerInner
- .filter(function(d) { return d.data.description; })
+ .filter(function(d) { return d.description; })
.call(bootstrap.tooltip()
- .title(function(d) { return d.data.description; })
- .placement('left')
- );
+ .title(function(d) { return d.description; })
+ .placement('left'));
layerInner.append('input')
.attr('type', type)
.attr('name', 'layers')
- .attr('value', function(d) { return d.data.name; })
+ .attr('value', function(d) { return d.name; })
.on('change', change);
- layerInner.insert('span').text(function(d) {
- return d.data.name;
- });
+ layerInner.append('span')
+ .text(function(d) { return d.name; });
layerLinks.exit()
.remove();
@@ -96,13 +100,8 @@ iD.ui.Background = function(context) {
}
function update() {
- backgroundList.call(drawList, 'radio', clickSetSource, function(d) {
- return !d.data.overlay;
- });
-
- overlayList.call(drawList, 'checkbox', clickSetOverlay, function(d) {
- return d.data.overlay;
- });
+ backgroundList.call(drawList, 'radio', clickSetSource, function(d) { return !d.overlay; });
+ overlayList.call(drawList, 'checkbox', clickSetOverlay, function(d) { return d.overlay; });
var hasGpx = context.background().hasGpxLayer(),
showsGpx = context.background().showsGpxLayer();
@@ -219,6 +218,19 @@ iD.ui.Background = function(context) {
.append('div')
.attr('class', 'toggle-list layer-list');
+ var custom = backgroundList
+ .append('label')
+ .attr('class', 'custom_layer')
+ .datum({name: 'Custom'});
+
+ custom.append('input')
+ .attr('type', 'radio')
+ .attr('name', 'layers')
+ .on('change', clickCustom);
+
+ custom.append('span')
+ .text(t('background.custom'));
+
var overlayList = content
.append('div')
.attr('class', 'toggle-list layer-list');
diff --git a/test/spec/renderer/background_source.js b/test/spec/renderer/background_source.js
index 9758905f6..f091734fa 100644
--- a/test/spec/renderer/background_source.js
+++ b/test/spec/renderer/background_source.js
@@ -1,27 +1,27 @@
-describe('iD.BackgroundSource.Template', function() {
+describe('iD.BackgroundSource', function() {
it('does not error with blank template', function() {
- var source = iD.BackgroundSource.template({ template: '' });
+ var source = iD.BackgroundSource({ template: '' });
expect(source([0,1,2])).to.equal('');
});
it('generates a tile-generating source', function() {
- var source = iD.BackgroundSource.template({ template: '{z}/{x}/{y}' });
+ var source = iD.BackgroundSource({ template: '{z}/{x}/{y}' });
expect(source([0,1,2])).to.equal('2/0/1');
});
it('supports subdomains', function() {
- var source = iD.BackgroundSource.template({ template: '{t}/{z}/{x}/{y}', subdomains: ['apples', 'oranges'] });
+ var source = iD.BackgroundSource({ template: '{t}/{z}/{x}/{y}', subdomains: ['apples', 'oranges'] });
expect(source([0,1,2])).to.equal('oranges/2/0/1');
});
it('distributes requests between subdomains', function() {
- var source = iD.BackgroundSource.template({ template: '{t}/{z}/{x}/{y}', subdomains: ['apples', 'oranges'] });
+ var source = iD.BackgroundSource({ template: '{t}/{z}/{x}/{y}', subdomains: ['apples', 'oranges'] });
expect(source([0,1,1])).to.equal('oranges/1/0/1');
expect(source([0,2,1])).to.equal('apples/1/0/2');
});
it('supports josm style templates', function() {
- var source = iD.BackgroundSource.template({ template: '{switch:foo,bar}/{zoom}/{x}/{y}' });
+ var source = iD.BackgroundSource({ template: '{switch:foo,bar}/{zoom}/{x}/{y}' });
expect(source([0,1,1])).to.equal('bar/1/0/1');
});
});