diff --git a/lib/constants.js b/lib/constants.js index e657d01b..5252e567 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -26,7 +26,8 @@ module.exports.API_TILESTATS_STATISTICS = '/tilestats/v1/{owner}/{tileset}'; module.exports.API_TILESTATS_LAYER = '/tilestats/v1/{owner}/{tileset}/{layer}'; module.exports.API_TILESTATS_ATTRIBUTE = '/tilestats/v1/{owner}/{tileset}/{layer}/{attribute}'; -module.exports.API_STATIC = '/v4/{mapid}{+overlay}/{+xyz}/{width}x{height}{+retina}{.format}{?access_token}'; +module.exports.API_STATIC = '/styles/v1/{username}/{styleid}/static{+overlay}/{+xyzbp}/{width}x{height}{+retina}{?access_token,attribution,logo,before_layer}'; +module.exports.API_STATIC_CLASSIC = '/v4/{mapid}{+overlay}/{+xyz}/{width}x{height}{+retina}{.format}{?access_token}'; module.exports.API_STYLES_LIST = '/styles/v1/{owner}'; module.exports.API_STYLES_CREATE = '/styles/v1/{owner}'; diff --git a/lib/mapbox.js b/lib/mapbox.js index 4b4e2a7c..a0bd2489 100644 --- a/lib/mapbox.js +++ b/lib/mapbox.js @@ -11,6 +11,7 @@ var MapboxMatching = require('./services/matching'); var MapboxDatasets = require('./services/datasets'); var MapboxDistance = require('./services/distance'); var MapboxTilestats = require('./services/tilestats'); +var MapboxStatic = require('./services/static'); /** @@ -43,7 +44,8 @@ xtend( MapboxMatching.prototype, MapboxDatasets.prototype, MapboxUploads.prototype, - MapboxTilestats.prototype + MapboxTilestats.prototype, + MapboxStatic.prototype ); MapboxClient.getUser = getUser; diff --git a/lib/services/static.js b/lib/services/static.js index 27572156..ee2daabe 100644 --- a/lib/services/static.js +++ b/lib/services/static.js @@ -11,7 +11,109 @@ var invariant = require('../../vendor/invariant'), var MapboxStatic = makeService('MapboxStatic'); /** - * Determine a URL for a static map image, using the [Mapbox Static Map API](https://www.mapbox.com/developers/api/static/). + * Determine a URL for a static map image, using the [Mapbox Static Map API](https://www.mapbox.com/api-documentation/#static). + * + * @param {string} username + * @param {string} styleid + * @param {number} width width of the image + * @param {number} height height of the image + * + * @param {Object|string} position either an object with longitude and latitude members, or the string 'auto' + * @param {number} position.longitude east, west bearing + * @param {number} position.latitude north, south bearing + * @param {number} position.zoom zoom level + * @param {number} position.bearing + * @param {number} position.pitch + * + * @param {Object} options all map options + * @param {boolean} [options.retina=false] whether to double image pixel density + * + * @param {Array} [options.markers=[]] an array of simple marker objects as an overlay + * @param {Object} [options.geojson={}] geojson data for the overlay + * @param {Object} [options.path={}] a path and + * @param {Array} options.path.geojson data for the path as an array of longitude, latitude objects + * @param {Array} options.path.style optional style definitions for a path + * @param {boolean} options.attribution controlling whether there is attribution on the image; defaults to true + * @param {boolean} options.logo controlling whether there is a Mapbox logo on the image; defaults to true + * @param {string} options.before_layer value for controlling where the overlay is inserted in the style + * + * @returns {string} static map url + * @memberof MapboxClient + * @example + * var mapboxClient = new MapboxClient('ACCESSTOKEN'); + */ +MapboxStatic.prototype.getStaticURL = function(username, styleid, width, height, position, options) { + invariant(typeof username === 'string', 'username option required and must be a string'); + invariant(typeof styleid === 'string', 'styleid option required and must be a string'); + invariant(typeof width === 'number', 'width option required and must be a number'); + invariant(typeof height === 'number', 'height option required and must be a number'); + + var defaults = { + retina: '' + }; + + var xyzbp; + + if (position === 'auto') { + xyzbp = 'auto'; + } else { + invariantLocation(position); + xyzbp = position.longitude + ',' + position.latitude + ',' + position.zoom + + (('bearing' in position) ? ',' + position.bearing : '') + + (('pitch' in position) ? ',' + position.pitch : ''); + } + + var userOptions = {}; + + if (options) { + invariant(typeof options === 'object', 'options must be an object'); + if (options.format) { + invariant(typeof options.format === 'string', 'format must be a string'); + userOptions.format = options.format; + } + if (options.retina) { + invariant(typeof options.retina === 'boolean', 'retina must be a boolean'); + userOptions.retina = options.retina; + } + if (options.markers) { + userOptions.overlay = '/' + encodeOverlay.encodeMarkers(options.markers); + } else if (options.geojson) { + userOptions.overlay = '/' + encodeOverlay.encodeGeoJSON(options.geojson); + } else if (options.path) { + userOptions.overlay = '/' + encodeOverlay.encodePath(options.path); + } + if ('attribution' in options) { + invariant(typeof options.attribution === 'boolean', 'attribution must be a boolean'); + userOptions.attribution = options.attribution; + } + if ('logo' in options) { + invariant(typeof options.logo === 'boolean', 'logo must be a boolean'); + userOptions.logo = options.logo; + } + if (options.before_layer) { + invariant(typeof options.before_layer === 'string', 'before_layer must be a string'); + userOptions.before_layer = options.before_layer; + } + } + + var params = xtend(defaults, userOptions, { + username: username, + styleid: styleid, + width: width, + xyzbp: xyzbp, + height: height, + access_token: this.accessToken + }); + + if (params.retina === true) { + params.retina = '@2x'; + } + + return this.endpoint + uriTemplate.expand(constants.API_STATIC, params); +}; + +/** + * Determine a URL for a static classic map image, using the [Mapbox Static (Classic) Map API](https://www.mapbox.com/api-documentation/pages/static_classic.html). * * @param {string} mapid a Mapbox map id in username.id form * @param {number} width width of the image @@ -32,12 +134,12 @@ var MapboxStatic = makeService('MapboxStatic'); * @param {Array} options.path.geojson data for the path as an array of longitude, latitude objects * @param {Array} options.path.style optional style definitions for a path * - * @returns {string} static map url + * @returns {string} static classic map url * @memberof MapboxClient * @example * var mapboxClient = new MapboxClient('ACCESSTOKEN'); */ -MapboxStatic.prototype.getStaticURL = function(mapid, width, height, position, options) { +MapboxStatic.prototype.getStaticClassicURL = function(mapid, width, height, position, options) { invariant(typeof mapid === 'string', 'mapid option required and must be a string'); invariant(typeof width === 'number', 'width option required and must be a number'); invariant(typeof height === 'number', 'height option required and must be a number'); @@ -89,7 +191,9 @@ MapboxStatic.prototype.getStaticURL = function(mapid, width, height, position, o params.retina = '@2x'; } - return this.endpoint + uriTemplate.expand(constants.API_STATIC, params); + return this.endpoint + uriTemplate.expand(constants.API_STATIC_CLASSIC, params); }; + + module.exports = MapboxStatic; diff --git a/test/static.js b/test/static.js index 70f177f0..c022fcb7 100644 --- a/test/static.js +++ b/test/static.js @@ -5,51 +5,108 @@ var test = require('tap').test; var MapboxClient = require('../lib/services/static'); function removeToken(url) { - return url.replace(/\?access_token.*$/, ''); + return url.replace(/access_token[^&]*&?/, '').replace(/\?$/, ''); } test('MapboxStatic', function(t) { var client = new MapboxClient(process.env.MapboxAccessToken); t.throws(function() { client.getStaticURL(); }); t.throws(function() { client.getStaticURL('foo'); }); - t.throws(function() { client.getStaticURL('foo', 10); }); t.throws(function() { client.getStaticURL('foo', 'foo', 10); }); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 20, { + longitude: 1, latitude: 2, zoom: 3 + })), 'https://api.mapbox.com/styles/v1/user/style/static/1,2,3/10x20', 'basic url'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 20, { + longitude: 1, latitude: 2, zoom: 3, bearing: 90, pitch: 60 + })), 'https://api.mapbox.com/styles/v1/user/style/static/1,2,3,90,60/10x20', 'bearing and pitch option'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, { + longitude: 1, latitude: 2, zoom: 3 + }, { + retina: true + })), 'https://api.mapbox.com/styles/v1/user/style/static/1,2,3/10x10@2x', 'retina option'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, 'auto', { + retina: true + })), 'https://api.mapbox.com/styles/v1/user/style/static/auto/10x10@2x', 'auto'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, { + longitude: 1, latitude: 2, zoom: 3 + }, { + retina: true, + markers: [{ longitude: 1, latitude: 2 }] + })), 'https://api.mapbox.com/styles/v1/user/style/static/pin-l-circle(1,2)/1,2,3/10x10@2x', 'with markers'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, { + longitude: 1, latitude: 2, zoom: 3 + }, { + retina: true, + geojson: { type: 'Point', coordinates: [0, 0] } + })), 'https://api.mapbox.com/styles/v1/user/style/static/geojson(%257B%2522type%2522%253A%2522Point%2522%252C%2522coordinates%2522%253A%255B0%252C0%255D%257D)/1,2,3/10x10@2x', 'with geojson'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, { + longitude: 1, latitude: 2, zoom: 3 + }, { + retina: true, + path: { geojson: { type: 'LineString', coordinates: [[0, 0], [1, 1]] } } + })), 'https://api.mapbox.com/styles/v1/user/style/static/path(??_ibE_ibE)/1,2,3/10x10@2x', 'with path'); + + t.equal(removeToken(client.getStaticURL('user', 'style', 10, 10, { + longitude: 1, latitude: 2, zoom: 3 + }, { + retina: true, + attribution: false, + logo: false, + before_layer: 'foo' + })), 'https://api.mapbox.com/styles/v1/user/style/static/1,2,3/10x10@2x?attribution=false&logo=false&before_layer=foo', 'attribution, logo & before_layer options'); + + t.end(); +}); + +test('MapboxStatic classic', function(t) { + var client = new MapboxClient(process.env.MapboxAccessToken); + t.throws(function() { client.getStaticClassicURL(); }); + t.throws(function() { client.getStaticClassicURL('foo'); }); + t.throws(function() { client.getStaticClassicURL('foo', 10); }); + t.throws(function() { client.getStaticClassicURL('foo', 'foo', 10); }); + + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 })), 'https://api.mapbox.com/v4/foo/1,2,3/10x10.png', 'basic url'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 }, { format: 'jpg80' })), 'https://api.mapbox.com/v4/foo/1,2,3/10x10.jpg80', 'format option'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 }, { retina: true })), 'https://api.mapbox.com/v4/foo/1,2,3/10x10@2x.png', 'retina option'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, 'auto', { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, 'auto', { retina: true })), 'https://api.mapbox.com/v4/foo/auto/10x10@2x.png', 'auto'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 }, { retina: true, markers: [{ longitude: 1, latitude: 2 }] })), 'https://api.mapbox.com/v4/foo/pin-l-circle(1,2)/1,2,3/10x10@2x.png', 'with markers'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 }, { retina: true, geojson: { type: 'Point', coordinates: [0, 0] } })), 'https://api.mapbox.com/v4/foo/geojson(%257B%2522type%2522%253A%2522Point%2522%252C%2522coordinates%2522%253A%255B0%252C0%255D%257D)/1,2,3/10x10@2x.png', 'with geojson'); - t.equal(removeToken(client.getStaticURL('foo', 10, 10, { + t.equal(removeToken(client.getStaticClassicURL('foo', 10, 10, { longitude: 1, latitude: 2, zoom: 3 }, { retina: true,