diff --git a/lib/constants.js b/lib/constants.js index f75b36dd..aa21e51f 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -2,20 +2,42 @@ // place, so that we could concievably update this for API layout // revisions. module.exports.DEFAULT_ENDPOINT = 'https://api.mapbox.com'; + module.exports.API_GEOCODING_FORWARD = '/geocoding/v5/{dataset}/{query}.json{?proximity,country,types}'; module.exports.API_GEOCODING_REVERSE = '/geocoding/v5/{dataset}/{longitude},{latitude}.json{?types}'; + module.exports.API_DIRECTIONS = '/v4/directions/{profile}/{encodedWaypoints}.json{?alternatives,instructions,geometry,steps}'; module.exports.API_DISTANCE = '/distances/v1/mapbox/{profile}'; + module.exports.API_SURFACE = '/v4/surface/{mapid}.json{?layer,fields,points,geojson,interpolate,encoded_polyline}'; + module.exports.API_UPLOADS = '/uploads/v1/{owner}'; module.exports.API_UPLOAD = '/uploads/v1/{owner}/{upload}'; module.exports.API_UPLOAD_CREDENTIALS = '/uploads/v1/{owner}/credentials'; + module.exports.API_MATCHING = '/matching/v4/{profile}.json'; + module.exports.API_DATASET_DATASETS = '/datasets/v1/{owner}'; module.exports.API_DATASET_DATASET = '/datasets/v1/{owner}/{dataset}'; module.exports.API_DATASET_FEATURES = '/datasets/v1/{owner}/{dataset}/features{?reverse,limit,start}'; module.exports.API_DATASET_FEATURE = '/datasets/v1/{owner}/{dataset}/features/{id}'; + 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}'; + +module.exports.API_STATIC = '/v4/{mapid}{+overlay}/{+xyz}/{width}x{height}{+retina}{.format}{?api_token}'; + +module.exports.API_STYLES_LIST = '/styles/v1/{owner}'; +module.exports.API_STYLES_CREATE = '/styles/v1/{owner}'; +module.exports.API_STYLES_READ = '/styles/v1/{owner}/{styleid}'; +module.exports.API_STYLES_UPDATE = '/styles/v1/{owner}/{styleid}'; +module.exports.API_STYLES_DELETE = '/styles/v1/{owner}/{styleid}'; +module.exports.API_STYLES_EMBED = '/styles/v1/{owner}/{styleid}.html{?zoomwheel,title,access_token}'; +module.exports.API_STYLES_SPRITE = '/styles/v1/{owner}/{styleid}/sprite{+retina}{.format}'; +module.exports.API_STYLES_SPRITE_ADD_ICON = '/styles/v1/{owner}/{styleid}/sprite/{iconName}'; +module.exports.API_STYLES_SPRITE_DELETE_ICON = '/styles/v1/{owner}/{styleid}/sprite/{iconName}'; + +module.exports.API_STYLES_FONT_GLYPH_RANGES = '/fonts/v1/{owner}/{font}/{start}-{end}.pbf' + +module.exports.API_STYLES_SPRITE_DELETE_ICON = '/styles/v1/{owner}/{styleid}/sprite/{iconName}'; diff --git a/lib/encode_overlay.js b/lib/encode_overlay.js index b0a71aa5..a144432a 100644 --- a/lib/encode_overlay.js +++ b/lib/encode_overlay.js @@ -1,8 +1,8 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../vendor/invariant'), invariantLocation = require('./invariant_location'), - polyline = require('polyline'), + polyline = require('../vendor/polyline'), geojsonhint = require('geojsonhint/object'); /** diff --git a/lib/invariant_location.js b/lib/invariant_location.js index 8acb556f..daad8d40 100644 --- a/lib/invariant_location.js +++ b/lib/invariant_location.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'); +var invariant = require('../vendor/invariant'); /** * Given an object that should be a location, ensure that it has diff --git a/lib/make_service.js b/lib/make_service.js index 506cafab..e3180e74 100644 --- a/lib/make_service.js +++ b/lib/make_service.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'); +var invariant = require('../vendor/invariant'); var constants = require('./constants'); var client = require('./client'); var getUser = require('./get_user'); diff --git a/lib/mapbox.js b/lib/mapbox.js index 0fd80f33..ef648df5 100644 --- a/lib/mapbox.js +++ b/lib/mapbox.js @@ -1,7 +1,7 @@ 'use strict'; var makeClient = require('./make_service'); -var xtend = require('xtend/mutable'); +var xtend = require('../vendor/xtend').extendMutable; var MapboxGeocoding = require('./services/geocoding'); var MapboxSurface = require('./services/surface'); var MapboxDirections = require('./services/directions'); diff --git a/lib/services/datasets.js b/lib/services/datasets.js index 9f1b656d..8a213602 100644 --- a/lib/services/datasets.js +++ b/lib/services/datasets.js @@ -1,8 +1,8 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), geojsonhint = require('geojsonhint/object'), - hat = require('hat'), + hat = require('../../vendor/hat'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/directions.js b/lib/services/directions.js index 808d79ef..781e3f1e 100644 --- a/lib/services/directions.js +++ b/lib/services/directions.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), formatPoints = require('../format_points'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/distance.js b/lib/services/distance.js index aef4f182..81ae5de6 100644 --- a/lib/services/distance.js +++ b/lib/services/distance.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/geocoding.js b/lib/services/geocoding.js index 587ffa6b..1248d71e 100644 --- a/lib/services/geocoding.js +++ b/lib/services/geocoding.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/matching.js b/lib/services/matching.js index fd878ba5..f87d4225 100644 --- a/lib/services/matching.js +++ b/lib/services/matching.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), geojsonhint = require('geojsonhint/object'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/static.js b/lib/services/static.js index 20f580f8..27572156 100644 --- a/lib/services/static.js +++ b/lib/services/static.js @@ -1,7 +1,7 @@ 'use strict'; -var invariant = require('invariant'), - xtend = require('xtend'), +var invariant = require('../../vendor/invariant'), + xtend = require('../../vendor/xtend').extend, uriTemplate = require('rest/util/uriTemplate'), encodeOverlay = require('../encode_overlay'), invariantLocation = require('../invariant_location'), @@ -81,15 +81,15 @@ MapboxStatic.prototype.getStaticURL = function(mapid, width, height, position, o mapid: mapid, width: width, xyz: xyz, - height: height + height: height, + access_token: this.accessToken }); if (params.retina === true) { params.retina = '@2x'; } - return this.endpoint + uriTemplate.expand(constants.API_STATIC, params) + - '?access_token=' + this.accessToken; + return this.endpoint + uriTemplate.expand(constants.API_STATIC, params); }; module.exports = MapboxStatic; diff --git a/lib/services/styles.js b/lib/services/styles.js new file mode 100644 index 00000000..45b19407 --- /dev/null +++ b/lib/services/styles.js @@ -0,0 +1,362 @@ +'use strict'; + +var invariant = require('../../vendor/invariant'), + hat = require('../../vendor/hat'), + uriTemplate = require('rest/util/uriTemplate'), + makeService = require('../make_service'), + constants = require('../constants'); + +var Styles = module.exports = makeService('MapboxStyles'); + +/** + * To retrieve a listing of styles for a particular account. + * + * @param {Function} callback called with (err, datasets) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.listStyles(function(err, styles) { + * console.log(datasets); + * // [{ version: 8, + * // name: 'Light', + * // center: [ -77.0469979435026, 38.898634927602814 ], + * // zoom: 12.511766533145998, + * // bearing: 0, + * // pitch: 0, + * // created: '2016-02-09T14:26:15.059Z', + * // id: 'STYLEID', + * // modified: '2016-02-09T14:28:31.253Z', + * // owner: '{username}' }, + * // { version: 8, + * // name: 'Dark', + * // created: '2015-08-28T18:05:22.517Z', + * // id: 'STYILEID', + * // modified: '2015-08-28T18:05:22.517Z', + * // owner: '{username}' }] + * }); + */ +Styles.prototype.listStyles = function(callback) { + return this.client({ + path: constants.API_STYLES_LIST, + params: { + owner: this.owner + }, + callback: callback + }).entity(); +}; + +/** + * Create a style, given the style as a JSON object. + * + * @param {Object} style Mapbox GL Style Spec object + * @param {Function} callback called with (err, datasets) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * var style = { + * 'version': 8, + * 'name': 'My Awesome Style', + * 'metadata': {}, + * 'sources': {}, + * 'layers': [], + * 'glyphs': 'mapbox://fonts/{owner}/{fontstack}/{range}.pbf' + * }; + * client.createStyle(style, function(err, createdStyle) { + * console.log(createdStyle); + * }); + */ +Styles.prototype.createStyle = function(style, callback) { + return this.client({ + path: constants.API_STYLES_CREATE, + params: { + owner: this.owner + }, + entity: style, + callback: callback + }).entity(); +}; + +/** + * Update a style, given the style as a JSON object. + * + * @param {Object} style Mapbox GL Style Spec object + * @param {string} id style id + * @param {Function} callback called with (err, datasets) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * var style = { + * 'version': 8, + * 'name': 'My Awesome Style', + * 'metadata': {}, + * 'sources': {}, + * 'layers': [], + * 'glyphs': 'mapbox://fonts/{owner}/{fontstack}/{range}.pbf' + * }; + * client.updateStyle(style, 'style-id', function(err, createdStyle) { + * console.log(createdStyle); + * }); + */ +Styles.prototype.updateStyle = function(style, styleid, callback) { + invariant(typeof styleid === 'string', 'style id must be a string'); + return this.client({ + path: constants.API_STYLES_UPDATE, + params: { + owner: this.owner, + styleid: styleid + }, + entity: style, + method: 'patch', + callback: callback + }).entity(); +}; + +/** + * Deletes a particular style. + * + * @param {string} styleid the id for an existing style + * @param {Function} callback called with (err) + * @returns {Promise} a promise with the response + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.readStyle('style-id', function(err, response) { + * if (!err) console.log(response); + * }); + */ +Styles.prototype.deleteStyle = function(styleid, callback) { + invariant(typeof styleid === 'string', 'styleid must be a string'); + + return this.client({ + path: constants.API_STYLES_DELETE, + params: { + owner: this.owner, + styleid: styleid + }, + method: 'delete', + callback: callback + }).entity(); +}; + +/** + * Reads a particular style. + * + * @param {string} styleid the id for an existing style + * @param {Function} callback called with (err) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.deleteStyle('style-id', function(err) { + * if (!err) console.log('deleted!'); + * }); + */ +Styles.prototype.readStyle = function(styleid, callback) { + invariant(typeof styleid === 'string', 'styleid must be a string'); + + return this.client({ + path: constants.API_STYLES_READ, + params: { + owner: this.owner, + styleid: styleid + }, + callback: callback + }).entity(); +}; + +/** + * Read sprite + * + * @param {string} styleid the id for an existing style + * @param {Object=} options optional options + * @param {boolean} options.retina whether the sprite JSON should be for a + * retina sprite. + * @param {Function} callback called with (err) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.readSprite('style-id', { + * retina: true + * }, function(err) { + * if (!err) console.log('deleted!'); + * }); + */ +Styles.prototype.readSprite = function(styleid, options, callback) { + invariant(typeof styleid === 'string', 'styleid must be a string'); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var retina = ''; + if (options.retina) { + invariant(typeof options.retina === boolean, + 'retina option must be a boolean value'); + if (options.retina) { + retina = '@2x'; + } + } + + var format = 'json'; + if (options.format) { + invariant(options.format === 'json' || + options.format === 'png', + 'format parameter must be either json or png'); + format = options.format; + } + + return this.client({ + path: constants.API_STYLES_SPRITE, + params: { + owner: this.owner, + retina: retina, + format: format, + styleid: styleid + }, + callback: callback + }).entity(); +}; + +/** + * Get font glyph ranges + * + * @param {string} font or fonts + * @param {number} start character code of starting glyph + * @param {number} end character code of last glyph. typically the same + * as start + 255 + * @param {Function} callback called with (err) + * @returns {Promise} + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.readFontGlyphRanges('Arial Unicode', 0, 255, function(err, ranges) { + * if (!err) console.log(ranges); + * }); + */ +Styles.prototype.readFontGlyphRanges = function(font, start, end, callback) { + invariant(typeof font === 'string', 'font must be a string'); + invariant(typeof start === 'number', 'start must be a number'); + invariant(typeof end === 'number', 'end must be a number'); + + return this.client({ + path: constants.API_STYLES_FONT_GLYPH_RANGES, + params: { + owner: this.owner, + font: font, + start: start, + end: end + }, + callback: callback + }).entity(); +}; + +/** + * Add an icon to a sprite. + * + * @param {string} style the id for an existing style + * @param {string} iconName icon's name + * @param {Buffer} icon icon content as a buffer + * @param {Function} callback called with (err) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var fs = require('fs'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.addIcon('style-id', 'icon-name', fs.readFileSync('icon.png'), function(err) { + * if (!err) console.log('added icon!'); + * }); + */ +Styles.prototype.addIcon = function(styleid, iconName, icon, callback) { + invariant(typeof styleid === 'string', 'style must be a string'); + invariant(typeof iconName === 'string', 'icon name must be a string'); + invariant(Buffer.isBuffer(icon), 'icon must be a Buffer'); + + return this.client({ + path: constants.API_STYLES_SPRITE_ADD_ICON, + params: { + owner: this.owner, + styleid: styleid, + iconName: iconName + }, + headers: { + 'Content-Type': 'text/plain' + }, + entity: icon, + method: 'put', + callback: callback + }).entity(); +}; + +/** + * Delete an icon from a sprite. + * + * @param {string} style the id for an existing style + * @param {string} iconName icon's name + * @param {Function} callback called with (err) + * @returns {undefined} nothing, calls callback + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * client.deleteIcon('style-id', 'icon-name', function(err) { + * if (!err) console.log('deleted icon!'); + * }); + */ +Styles.prototype.deleteIcon = function(styleid, iconName, callback) { + invariant(typeof styleid === 'string', 'style must be a string'); + invariant(typeof iconName === 'string', 'icon name must be a string'); + + return this.client({ + path: constants.API_STYLES_SPRITE_ADD_ICON, + params: { + owner: this.owner, + styleid: styleid, + iconName: iconName + }, + method: 'delete', + callback: callback + }).entity(); +}; + +/** + * Embed a style. + * + * @param {string} style the id for an existing style + * @param {Object} options + * @param {boolean=false} options.title If true, shows a title box in upper right + * corner with map title and owner + * @param {boolean=true} options.zoomwheel Disables zooming with mouse scroll wheel + * @returns {string} URL of style embed page + * @example + * var MapboxClient = require('mapbox'); + * var client = new MapboxClient('ACCESSTOKEN'); + * var url = client.embedStyle('style-id'); + */ +Styles.prototype.embedStyle = function(styleid, options) { + invariant(typeof styleid === 'string', 'style must be a string'); + + var params = { + styleid: styleid, + access_token: this.accessToken, + owner: this.owner, + title: false, + zoomwheel: true + }; + + if (options) { + if (options.title !== undefined) { + invariant(typeof options.title === 'boolean', 'title must be a boolean'); + params.title = options.title; + } + if (options.zoomwheel !== undefined) { + invariant(typeof options.zoomwheel === 'boolean', 'zoomwheel must be a boolean'); + params.zoomwheel = options.zoomwheel; + } + } + + return this.endpoint + uriTemplate.expand(constants.API_STYLES_EMBED, params); +}; diff --git a/lib/services/surface.js b/lib/services/surface.js index 44020cb1..dc66ddf4 100644 --- a/lib/services/surface.js +++ b/lib/services/surface.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), formatPoints = require('../format_points'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/tilestats.js b/lib/services/tilestats.js index 8b25a5c9..63c11418 100644 --- a/lib/services/tilestats.js +++ b/lib/services/tilestats.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/lib/services/uploads.js b/lib/services/uploads.js index 2317d4c2..e089dabd 100644 --- a/lib/services/uploads.js +++ b/lib/services/uploads.js @@ -1,6 +1,6 @@ 'use strict'; -var invariant = require('invariant'), +var invariant = require('../../vendor/invariant'), makeService = require('../make_service'), constants = require('../constants'); diff --git a/package.json b/package.json index 1e33987f..f68ba33a 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,6 @@ "dependencies": { "atob": "^1.1.2", "geojsonhint": "^1.1.0", - "hat": "0.0.3", - "invariant": "^2.2.1", - "polyline": "^0.1.0", - "rest": "^1.3.2", - "xtend": "^4.0.0" + "rest": "^1.3.2" } } diff --git a/test/datasets.js b/test/datasets.js index 1e74add6..c2c07835 100644 --- a/test/datasets.js +++ b/test/datasets.js @@ -5,7 +5,7 @@ var test = require('tap').test; var MapboxClient = require('../lib/services/datasets'); var geojsonhint = require('geojsonhint').hint; var geojsonRandom = require('geojson-random'); -var hat = require('hat'); +var hat = require('../vendor/hat'); function randomFeature() { return geojsonRandom.polygon(1).features[0]; diff --git a/test/fixtures/airport-12.svg b/test/fixtures/airport-12.svg new file mode 100644 index 00000000..e5fb3afb --- /dev/null +++ b/test/fixtures/airport-12.svg @@ -0,0 +1,59 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/test/styles.js b/test/styles.js new file mode 100644 index 00000000..324eb90c --- /dev/null +++ b/test/styles.js @@ -0,0 +1,196 @@ +/* eslint no-shadow: 0 */ +'use strict'; + +var test = require('tap').test; +var fs = require('fs'); +var path = require('path'); +var MapboxClient = require('../lib/services/styles'); +var hat = require('../vendor/hat'); + +var newStyleFixture = { + 'version': 8, + 'name': 'MAPBOX_SDK_TEST_STYLE_DELETEME', + 'metadata': {}, + 'sources': {}, + 'layers': [], + 'glyphs': 'mapbox://fonts/{owner}/{fontstack}/{range}.pbf' +}; + +function removeToken(url) { + return url.replace(/(\?|&)access_token.*$/, ''); +} + +test('StyleClient', function(styleClient) { + + if (process.browser) { + styleClient.pass('skipping style api in browser'); + return styleClient.end(); + } + + + /* + styleClient.test('cleanup old test styles', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.listStyles(function(err, styles) { + assert.ifError(err, 'success'); + styles.filter(function(style) { + return style.name = newStyleFixture.name; + }).forEach(function(style) { + assert.test('delete old style', function(deleteRequest) { + client.deleteStyle(style.id, function(err, res) { + deleteRequest.ifError(err); + deleteRequest.end(); + }); + }); + }); + assert.end(); + }); + }); + */ + + styleClient.test('#listStyles', function(listStyles) { + + listStyles.test('simple list', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.listStyles(function(err, styles) { + assert.ifError(err, 'success'); + assert.ok(Array.isArray(styles), 'lists styles'); + styles.forEach(function(style) { + assert.ok(style.id, 'Each style has an id'); + }); + assert.end(); + }); + }); + + listStyles.end(); + }); + + var newStyleId = ''; + + styleClient.test('#createStyle', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + newStyleFixture.glyphs = newStyleFixture.glyphs + .replace('{owner}', client.owner); + client.createStyle(newStyleFixture, function(err, style) { + assert.ifError(err, 'success'); + assert.ok(style.id, 'returned style has a valid id'); + newStyleId = style.id; + assert.end(); + }); + }); + + // unfortunate workaround for cross-region replication + styleClient.test('#retrieveStyle', function(assert) { + setTimeout(function() { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.readStyle(newStyleId, function(err, style) { + assert.ifError(err, 'success'); + assert.ok(style.id, 'returned style has a valid id'); + assert.end(); + }); + }, 1000); + }); + + styleClient.test('#readSprite - json', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.readSprite(newStyleId, function(err, sprite) { + assert.ifError(err, 'sprite could be seen'); + assert.deepEqual(sprite, {}, 'sprite is an empty object'); + assert.end(); + }); + }); + + styleClient.test('#readFontGlyphRanges', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.readFontGlyphRanges('Arial Unicode MS Regular', 0, 255, function(err, ranges) { + assert.ifError(err, 'sprite could be seen'); + assert.equal(typeof ranges, 'string'); + assert.end(); + }); + }); + + styleClient.test('#embedStyle', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + assert.equal(removeToken(client.embedStyle('f00')), + 'https://api.mapbox.com/styles/v1/' + client.owner + '/f00.html?zoomwheel=true&title=false'); + assert.equal(removeToken(client.embedStyle('f00', { + zoomwheel: false, title: true + })), 'https://api.mapbox.com/styles/v1/' + client.owner + '/f00.html?zoomwheel=false&title=true'); + assert.end(); + }); + + styleClient.test('#readSprite - png', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.readSprite(newStyleId, { + format: 'png' + }, function(err, sprite) { + assert.ifError(err, 'sprite could be seen'); + assert.equal(typeof sprite, 'string', 'sprite is a png'); + assert.end(); + }); + }); + + styleClient.test('#addIcon', function(assert) { + var imageBuffer = fs.readFileSync(path.join(__dirname, 'fixtures/airport-12.svg')); + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.addIcon(newStyleId, 'aerialway', imageBuffer, function(err, sprite) { + assert.deepEqual(sprite, { + aerialway: { + width: 12, + height: 12, + x: 0, + y: 0, + pixelRatio: 1 + } + }, 'sprite is reflected in the response'); + assert.ifError(err, 'icon has been added'); + assert.end(); + }); + }); + + styleClient.test('#deleteIcon', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.deleteIcon(newStyleId, 'aerialway', function(err) { + assert.ifError(err, 'icon has been deleted'); + assert.end(); + }); + }); + + styleClient.test('#updateStyle', function(assert) { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + newStyleFixture.glyphs = newStyleFixture.glyphs + .replace('{owner}', client.owner); + newStyleFixture.id = newStyleId; + client.updateStyle(newStyleFixture, newStyleId, function(err, style) { + assert.ifError(err, 'success'); + assert.ok(style.id, 'returned style has a valid id'); + assert.end(); + }); + }); + + // we've waited for replication in the last step, so this can run + // safely, immediately + styleClient.test('#deleteStyle', function(assert) { + setTimeout(function() { + var client = new MapboxClient(process.env.MapboxAccessToken); + assert.ok(client, 'created style client'); + client.deleteStyle(newStyleId, function(err, style) { + assert.ifError(err, 'item deleted'); + assert.end(); + }); + }, 1000); + }); + + styleClient.end(); +}); diff --git a/test/surface.js b/test/surface.js index afd6fb70..ea8762db 100644 --- a/test/surface.js +++ b/test/surface.js @@ -4,7 +4,7 @@ var test = require('tap').test, // fs = require('fs'), // path = require('path'), - polyline = require('polyline'), + polyline = require('../vendor/polyline'), geojsonhint = require('geojsonhint'), MapboxClient = require('../lib/services/surface'); diff --git a/test/tilestats.js b/test/tilestats.js index 15c7a424..c6626c1d 100644 --- a/test/tilestats.js +++ b/test/tilestats.js @@ -3,7 +3,7 @@ var test = require('tap').test; var MapboxClient = require('../lib/services/tilestats'); -var hat = require('hat'); +var hat = require('../vendor/hat'); test('TilestatsClient', function(tilestatsClient) { var tilesetid = hat().slice(0, 6); diff --git a/test/uploads.js b/test/uploads.js index b6c48f35..4407dd1d 100644 --- a/test/uploads.js +++ b/test/uploads.js @@ -4,7 +4,7 @@ var test = require('tap').test; var MapboxClient = require('../lib/services/uploads'); var AWS = require('aws-sdk'); -var hat = require('hat'); +var hat = require('../vendor/hat'); var path = require('path'); var fs = require('fs'); diff --git a/vendor/hat.js b/vendor/hat.js new file mode 100644 index 00000000..72833585 --- /dev/null +++ b/vendor/hat.js @@ -0,0 +1,37 @@ +/* eslint-disable */ +/** + * hat + * written by James Halliday, licensed under MIT/X11 + * https://github.com/substack/node-hat + */ +var hat = module.exports = function (bits, base) { + if (!base) base = 16; + if (bits === undefined) bits = 128; + if (bits <= 0) return '0'; + + var digits = Math.log(Math.pow(2, bits)) / Math.log(base); + for (var i = 2; digits === Infinity; i *= 2) { + digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i; + } + + var rem = digits - Math.floor(digits); + + var res = ''; + + for (var i = 0; i < Math.floor(digits); i++) { + var x = Math.floor(Math.random() * base).toString(base); + res = x + res; + } + + if (rem) { + var b = Math.pow(base, rem); + var x = Math.floor(Math.random() * b).toString(base); + res = x + res; + } + + var parsed = parseInt(res, base); + if (parsed !== Infinity && parsed >= Math.pow(2, bits)) { + return hat(bits, base) + } + else return res; +}; diff --git a/vendor/invariant.js b/vendor/invariant.js new file mode 100644 index 00000000..1859a2a5 --- /dev/null +++ b/vendor/invariant.js @@ -0,0 +1,53 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +/** + * Use invariant() to assert state which your program assumes to be true. + * + * Provide sprintf-style format (only %s is supported) and arguments + * to provide information about what broke and what you were + * expecting. + * + * The invariant message will be stripped in production, but the invariant + * will remain to ensure logic does not differ in production. + */ + +var NODE_ENV = process.env.NODE_ENV; + +var invariant = function(condition, format, a, b, c, d, e, f) { + if (NODE_ENV !== 'production') { + if (format === undefined) { + throw new Error('invariant requires an error message argument'); + } + } + + if (!condition) { + var error; + if (format === undefined) { + error = new Error( + 'Minified exception occurred; use the non-minified dev environment ' + + 'for the full error message and additional helpful warnings.' + ); + } else { + var args = [a, b, c, d, e, f]; + var argIndex = 0; + error = new Error( + format.replace(/%s/g, function() { return args[argIndex++]; }) + ); + error.name = 'Invariant Violation'; + } + + error.framesToPop = 1; // we don't care about invariant's own frame + throw error; + } +}; + +module.exports = invariant; diff --git a/vendor/polyline.js b/vendor/polyline.js new file mode 100644 index 00000000..3c338c7e --- /dev/null +++ b/vendor/polyline.js @@ -0,0 +1,162 @@ +'use strict'; + +/** + * polyline + * + * https://github.com/mapbox/polyline + * + * by John Firebaugh, Tom MacWright, and contributors + * licensed under BSD 3-clause + */ + +/** + * Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) + * + * Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js) + * by [Mark McClure](http://facstaff.unca.edu/mcmcclur/) + * + * @module polyline + */ + +var polyline = {}; + +function encode(coordinate, factor) { + coordinate = Math.round(coordinate * factor); + coordinate <<= 1; + if (coordinate < 0) { + coordinate = ~coordinate; + } + var output = ''; + while (coordinate >= 0x20) { + output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63); + coordinate >>= 5; + } + output += String.fromCharCode(coordinate + 63); + return output; +} + +/** + * Decodes to a [latitude, longitude] coordinates array. + * + * This is adapted from the implementation in Project-OSRM. + * + * @param {String} str + * @param {Number} precision + * @returns {Array} + * + * @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js + */ +polyline.decode = function(str, precision) { + var index = 0, + lat = 0, + lng = 0, + coordinates = [], + shift = 0, + result = 0, + byte = null, + latitude_change, + longitude_change, + factor = Math.pow(10, precision || 5); + + // Coordinates have variable length when encoded, so just keep + // track of whether we've hit the end of the string. In each + // loop iteration, a single coordinate is decoded. + while (index < str.length) { + + // Reset shift, result, and byte + byte = null; + shift = 0; + result = 0; + + do { + byte = str.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + shift = result = 0; + + do { + byte = str.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + lat += latitude_change; + lng += longitude_change; + + coordinates.push([lat / factor, lng / factor]); + } + + return coordinates; +}; + +/** + * Encodes the given [latitude, longitude] coordinates array. + * + * @param {Array.>} coordinates + * @param {Number} precision + * @returns {String} + */ +polyline.encode = function(coordinates, precision) { + if (!coordinates.length) { return ''; } + + var factor = Math.pow(10, precision || 5), + output = encode(coordinates[0][0], factor) + encode(coordinates[0][1], factor); + + for (var i = 1; i < coordinates.length; i++) { + var a = coordinates[i], b = coordinates[i - 1]; + output += encode(a[0] - b[0], factor); + output += encode(a[1] - b[1], factor); + } + + return output; +}; + +function flipped(coords) { + var flipped = []; + for (var i = 0; i < coords.length; i++) { + flipped.push(coords[i].slice().reverse()); + } + return flipped; +} + +/** + * Encodes a GeoJSON LineString feature/geometry. + * + * @param {Object} geojson + * @param {Number} precision + * @returns {String} + */ +polyline.fromGeoJSON = function(geojson, precision) { + if (geojson && geojson.type === 'Feature') { + geojson = geojson.geometry; + } + if (!geojson || geojson.type !== 'LineString') { + throw new Error('Input must be a GeoJSON LineString'); + } + return polyline.encode(flipped(geojson.coordinates), precision); +}; + +/** + * Decodes to a GeoJSON LineString geometry. + * + * @param {String} str + * @param {Number} precision + * @returns {Object} + */ +polyline.toGeoJSON = function(str, precision) { + var coords = polyline.decode(str, precision); + return { + type: 'LineString', + coordinates: flipped(coords) + }; +}; + +if (typeof module === 'object' && module.exports) { + module.exports = polyline; +} diff --git a/vendor/xtend.js b/vendor/xtend.js new file mode 100644 index 00000000..4a35f697 --- /dev/null +++ b/vendor/xtend.js @@ -0,0 +1,41 @@ +/** + * xtend by Jake Verbaten + * + * Licensed under MIT + * https://github.com/Raynos/xtend + */ +module.exports.extendMutable = extendMutable +module.exports.extend = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extendMutable(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} + + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +}