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'); }); });