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 @@
+
+
+
+
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
+}