diff --git a/src/tileLayer.js b/src/tileLayer.js index a22f66dfa0..21d1ff416a 100644 --- a/src/tileLayer.js +++ b/src/tileLayer.js @@ -15,28 +15,62 @@ module.exports = (function () { /** * Pick a subdomain from a list of subdomains based on a the tile location. * - * @param {number} x: the x tile coordinate. - * @param {number} y: the y tile coordinate. - * @param {list} subdomains: the list of known subdomains. + * @param {number} x The x tile coordinate. + * @param {number} y The y tile coordinate. + * @param {number} z The tile layer. + * @param {string[]} subdomains The list of known subdomains. + * @returns {string} A subdomain based on the location. */ - function m_getTileSubdomain(x, y, subdomains) { - return subdomains[modulo(x + y, subdomains.length)]; + function m_getTileSubdomain(x, y, z, subdomains) { + return subdomains[modulo(x + y + z, subdomains.length)]; } /** * Returns an OSM tile server formatting function from a standard format - * string. Replaces {s}, {z}, {x}, and {y}. + * string. Replaces `{s}`, `{z}`, `{x}`, and `{y}`. These may be any case + * and may be prefixed with `$` (e.g., `${X}` is the same as `{x}`). The + * subdomain can be specifed by a string of characters, listed as a range, + * or as a comma-separated list (e.g., `{s:abc}`, `{a-c}`, `{a,b,c}` are + * all equivalent. The comma-separated list can have subdimains that are of + * any length; the string and range both use one-character subdomains. * * @param {string} base The tile format string - * @returns: a conversion function. + * @returns {function} A conversion function. * @private. */ function m_tileUrlFromTemplate(base) { + var xPattern = new RegExp(/\$?\{[xX]\}/), + yPattern = new RegExp(/\$?\{[yY]\}/), + zPattern = new RegExp(/\$?\{[zZ]\}/), + sPattern = new RegExp(/\$?\{(s|S|[sS]:[^{}]+|[^-{}]-[^-{}]|([^,{}]+,)+[^,{}]+)\}/); + var url = base + .replace(sPattern, '{s}') + .replace(xPattern, '{x}') + .replace(yPattern, '{y}') + .replace(zPattern, '{z}'); + var urlSubdomains; + var sMatch = base.match(sPattern); + if (sMatch) { + if (sMatch[2]) { + urlSubdomains = sMatch[1].split(','); + } else if (sMatch[1][1] === ':') { + urlSubdomains = sMatch[1].substr(2).split(''); + } else if (sMatch[1][1] === '-') { + urlSubdomains = []; + var start = sMatch[1].charCodeAt(0), + end = sMatch[1].charCodeAt(2); + for (var i = Math.min(start, end); i <= Math.max(start, end); i += 1) { + urlSubdomains.push(String.fromCharCode(i)); + } + } + } + return function (x, y, z, subdomains) { - return base.replace('{s}', m_getTileSubdomain(x, y, subdomains)) - .replace('{z}', z) + return url + .replace('{s}', m_getTileSubdomain(x, y, z, urlSubdomains || subdomains)) .replace('{x}', x) - .replace('{y}', y); + .replace('{y}', y) + .replace('{z}', z); }; } diff --git a/tests/cases/tileLayer.js b/tests/cases/tileLayer.js index 9b2b9fc357..7233b5057e 100644 --- a/tests/cases/tileLayer.js +++ b/tests/cases/tileLayer.js @@ -648,8 +648,10 @@ describe('geo.tileLayer', function () { l._getTiles = function (level, bounds) { expect(level).toBe(1); expect(bounds).toEqual({ - left: 0, right: 1, - bottom: 0, top: 1 + left: 0, + right: 1, + bottom: 0, + top: 1 }); return [ {fetch: function () { @@ -1325,21 +1327,36 @@ describe('geo.tileLayer', function () { }); }); it('url templating', function () { + /* eslint-disable no-template-curly-in-string */ + var urls = { + 's={s}&x={x}&y={y}&z={z}': ['a', 'b', 'c'], + 's=${S}&x=${X}&y=${Y}&z=${Z}': ['a', 'b', 'c'], + 's={s:abc}&x={x}&y={y}&z={z}': ['a', 'b', 'c'], + 's={a,b,c}&x={x}&y={y}&z={z}': ['a', 'b', 'c'], + 's={a-c}&x={x}&y={y}&z={z}': ['a', 'b', 'c'], + 's={c-a}&x={x}&y={y}&z={z}': ['a', 'b', 'c'], + 's=${s:1234}&x={x}&y={y}&z={z}': ['1', '2', '3', '4'], + 's=${1,2,3,4}&x={x}&y={y}&z={z}': ['1', '2', '3', '4'], + 's=${1-4}&x={x}&y={y}&z={z}': ['1', '2', '3', '4'], + 's={ab,bc,12}&x={x}&y={y}&z={z}': ['ab', 'bc', '12'] + }; + /* eslint-enable no-template-curly-in-string */ var tiles, l = geo.tileLayer({ map: map({unitsPerPixel: 1}), wrapX: false, wrapY: false, - topDown: true, - url: '/testdata/white.jpg?s={s}&x={x}&y={y}&z={z}' + topDown: true }); - - tiles = l._getTiles(1, {left: 50, right: 500, bottom: 500, top: 50}); - expect(tiles.length).toBe(5); - tiles.forEach(function (tile) { - expect($.inArray(tile._url.split('?s=')[1].split('&')[0], - ['a', 'b', 'c'])).toBeGreaterThan(-1); - expect(tile._url.split('&x')[1]).toBe('=' + tile.index.x + '&y=' + - tile.index.y + '&z=' + tile.index.level); + $.each(urls, function (url, subdomains) { + l.url('/testdata/white.jpg?' + url); + tiles = l._getTiles(1, {left: 50, right: 500, bottom: 500, top: 50}); + expect(tiles.length).toBe(5); + tiles.forEach(function (tile) { + expect($.inArray(tile._url.split('?s=')[1].split('&')[0], + subdomains)).toBeGreaterThan(-1); + expect(tile._url.split('&x')[1]).toBe('=' + tile.index.x + '&y=' + + tile.index.y + '&z=' + tile.index.level); + }); }); }); it('baseUrl', function () {