diff --git a/.eslintrc b/.eslintrc
index de7ce0b3bc4..dee956d3063 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,16 +1,17 @@
{
"extends": "mourner",
"rules": {
- "object-curly-spacing": "off",
- "key-spacing": "off",
"array-bracket-spacing": "off",
- "space-before-function-paren": "off",
- "quotes": "off",
- "no-eq-null": "off",
+ "block-scoped-var": "error",
"consistent-return": "off",
"global-require": "off",
- "block-scoped-var": "error",
- "no-warning-comments": "error"
+ "key-spacing": "off",
+ "no-eq-null": "off",
+ "no-new": "off",
+ "no-warning-comments": "error",
+ "object-curly-spacing": "off",
+ "quotes": "off",
+ "space-before-function-paren": "off",
},
"env": {
"es6": true
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..e8940462ce0
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+
+
+## Checklist
+
+ - [ ] briefly describe the changes in this PR
+ - [ ] write tests for all new functionality
+ - [ ] [document any changes or additions to public APIs](https://github.com/mapbox/mapbox-gl-js/blob/master/docs/README.md)
+ - [ ] [post scores for all benchmarks on your branch and `master`](https://github.com/mapbox/mapbox-gl-js/blob/master/bench/README.md#running-benchmarks)
+ - [ ] [do a quick sanity check on the debug page](https://github.com/mapbox/mapbox-gl-js/blob/master/CONTRIBUTING.md#serving-the-debug-page)
+ - [ ] get a PR review :+1: / :-1:
diff --git a/README.md b/README.md
index 36f409ed3e0..c7afb37755e 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,25 @@ These projects are written and maintained by the GL JS community. Feel free to o
- [Typescript Interface Definition](https://github.com/Smartrak/mapbox-gl-js-typescript)
- [Webtoolkit Integration](https://github.com/yvanvds/wtMapbox)
+## Using Mapbox GL JS with [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP)
+
+You may use a Content Security Policy to restrict the resources your page has
+access to, as a way of guarding against Cross-Site Scripting and other types of
+attacks. If you do, Mapbox GL JS requires the following directives:
+
+```
+child-src blob: ;
+img-src data: blob: ;
+script-src 'unsafe-eval' ;
+```
+
+Requesting styles from Mapbox or other services will require additional
+directives. For Mapbox, you can use this `connect-src` setting:
+
+```
+connect-src https://*.tiles.mapbox.com https://api.mapbox.com
+```
+
## Contributing to Mapbox GL JS
See [CONTRIBUTING.md](https://github.com/mapbox/mapbox-gl-js/blob/master/CONTRIBUTING.md).
diff --git a/bench/.eslintrc b/bench/.eslintrc
new file mode 100644
index 00000000000..d34a4506724
--- /dev/null
+++ b/bench/.eslintrc
@@ -0,0 +1,7 @@
+{
+ "parserOptions": {
+ "ecmaFeatures": {
+ "jsx": true
+ }
+ }
+}
diff --git a/bench/README.md b/bench/README.md
index e89e251b1bf..b4224ce9505 100644
--- a/bench/README.md
+++ b/bench/README.md
@@ -2,7 +2,7 @@
Benchmarks help us catch performance regressions and improve performance.
-## Running a Benchmark
+## Running Benchmarks
Start the benchmark server
@@ -10,10 +10,9 @@ Start the benchmark server
MAPBOX_ACCESS_TOKEN={YOUR MAPBOX ACCESS TOKEN} npm start
```
-Open a benchmark runner page
+To run all benchmarks, open [the benchmark page, `http://localhost:9966/bench`](http://localhost:9966/bench).
- - **buffer benchmark** http://localhost:9966/bench/buffer
- - **fps benchmark** http://localhost:9966/bench/fps
+To run a specific benchmark, append its name to the url, for example [`http://localhost:9966/bench/buffer`](http://localhost:9966/bench/buffer).
## Writing a Benchmark
diff --git a/bench/benchmarks/fps.js b/bench/benchmarks/fps.js
index aa7a77f6028..2543e082679 100644
--- a/bench/benchmarks/fps.js
+++ b/bench/benchmarks/fps.js
@@ -31,7 +31,7 @@ module.exports = function(options) {
evented.fire('error', { error: err });
} else {
evented.fire('end', {
- message: formatNumber(fps) + ' frames per second',
+ message: formatNumber(fps) + ' fps',
score: 1 / fps
});
}
diff --git a/bench/benchmarks/frame_duration.js b/bench/benchmarks/frame_duration.js
index d9374342c2c..c3c115ffc10 100644
--- a/bench/benchmarks/frame_duration.js
+++ b/bench/benchmarks/frame_duration.js
@@ -30,16 +30,14 @@ module.exports = function(options) {
measureFrameTime(options, zooms[index], function(err_, result) {
results[index] = result;
evented.fire('log', {
- message: formatNumber(result.sum / result.count * 10) / 10 + ' ms per frame at zoom ' + zooms[index] + '. ' +
- formatNumber(result.countAbove16 / result.count * 100) + '% of frames took longer than 16ms.'
+ message: formatNumber(result.sum / result.count * 10) / 10 + ' ms, ' +
+ formatNumber(result.countAbove16 / result.count * 100) + '% > 16 ms at zoom ' + zooms[index]
});
callback();
});
}
function done() {
- document.getElementById('map').remove();
-
var sum = 0;
var count = 0;
var countAbove16 = 0;
@@ -50,7 +48,7 @@ module.exports = function(options) {
countAbove16 += result.countAbove16;
}
evented.fire('end', {
- message: formatNumber(sum / count * 10) / 10 + ' ms per frame. ' + formatNumber(countAbove16 / count * 100) + '% of frames took longer than 16ms.',
+ message: formatNumber(sum / count * 10) / 10 + ' ms, ' + formatNumber(countAbove16 / count * 100) + '% > 16ms',
score: sum / count
});
}
@@ -96,6 +94,7 @@ function measureFrameTime(options, zoom, callback) {
if (frameEnd - start > DURATION_MILLISECONDS) {
map.repaint = false;
map.remove();
+ map.getContainer().remove();
callback(undefined, {
sum: sum,
count: count,
diff --git a/bench/benchmarks/geojson_setdata_large.js b/bench/benchmarks/geojson_setdata_large.js
index 0006b23a90b..39a8e3bbfb3 100644
--- a/bench/benchmarks/geojson_setdata_large.js
+++ b/bench/benchmarks/geojson_setdata_large.js
@@ -27,7 +27,7 @@ module.exports = function(options) {
evented.fire('log', {message: 'loading large feature collection'});
setDataPerf(source, 50, featureCollection, function(err, ms) {
if (err) return evented.fire('error', {error: err});
- evented.fire('end', {message: 'average load time: ' + formatNumber(ms) + ' ms', score: ms});
+ evented.fire('end', {message: formatNumber(ms) + ' ms', score: ms});
});
});
diff --git a/bench/benchmarks/geojson_setdata_small.js b/bench/benchmarks/geojson_setdata_small.js
index 38906586a03..ee5e09eeea4 100644
--- a/bench/benchmarks/geojson_setdata_small.js
+++ b/bench/benchmarks/geojson_setdata_small.js
@@ -37,7 +37,7 @@ module.exports = function(options) {
evented.fire('log', {message: 'loading small feature collection'});
setDataPerf(source, 50, featureCollection, function(err, ms) {
if (err) return evented.fire('error', {error: err});
- evented.fire('end', {message: 'average load time: ' + formatNumber(ms) + ' ms', score: ms});
+ evented.fire('end', {message: formatNumber(ms) + ' ms', score: ms});
});
});
diff --git a/bench/benchmarks/query_box.js b/bench/benchmarks/query_box.js
index 9379f50a617..e2bab94a96d 100644
--- a/bench/benchmarks/query_box.js
+++ b/bench/benchmarks/query_box.js
@@ -28,7 +28,7 @@ module.exports = function(options) {
center: [-77.032194, 38.912753],
style: 'mapbox://styles/mapbox/streets-v9'
});
- document.getElementById('map').style.display = 'none';
+ map.getContainer().style.display = 'none';
map.on('load', function() {
@@ -36,7 +36,7 @@ module.exports = function(options) {
var zoomCount = 0;
asyncSeries(numSamples, function(n, callback) {
var start = performance.now();
- map.queryRenderedFeatures();
+ map.queryRenderedFeatures({});
var duration = performance.now() - start;
sum += duration;
count++;
@@ -45,7 +45,7 @@ module.exports = function(options) {
callback();
}, function() {
evented.fire('log', {
- message: 'zoom ' + zoomLevel + ' average: ' + (zoomSum / zoomCount).toFixed(2) + ' ms'
+ message: (zoomSum / zoomCount).toFixed(2) + ' ms at zoom ' + zoomLevel
});
callback();
});
@@ -80,4 +80,3 @@ function asyncSeries(times, work, callback) {
callback();
}
}
-
diff --git a/bench/benchmarks/query_point.js b/bench/benchmarks/query_point.js
index 49962b633bc..1b3ac282483 100644
--- a/bench/benchmarks/query_point.js
+++ b/bench/benchmarks/query_point.js
@@ -37,7 +37,7 @@ module.exports = function(options) {
center: [-77.032194, 38.912753],
style: 'mapbox://styles/mapbox/streets-v9'
});
- document.getElementById('map').style.display = 'none';
+ map.getContainer().style.display = 'none';
map.on('load', function() {
@@ -46,7 +46,7 @@ module.exports = function(options) {
asyncSeries(queryPoints.length, function(n, callback) {
var queryPoint = queryPoints[queryPoints.length - n];
var start = performance.now();
- map.queryRenderedFeatures(queryPoint);
+ map.queryRenderedFeatures(queryPoint, {});
var duration = performance.now() - start;
sum += duration;
count++;
@@ -55,7 +55,7 @@ module.exports = function(options) {
callback();
}, function() {
evented.fire('log', {
- message: 'zoom ' + zoomLevel + ' average: ' + (zoomSum / zoomCount).toFixed(2) + ' ms'
+ message: (zoomSum / zoomCount).toFixed(2) + ' ms at zoom ' + zoomLevel
});
callback();
});
@@ -90,4 +90,3 @@ function asyncSeries(times, work, callback) {
callback();
}
}
-
diff --git a/bench/index.html b/bench/index.html
index 9b346bd4175..fc698ac3d92 100644
--- a/bench/index.html
+++ b/bench/index.html
@@ -8,34 +8,10 @@
-
-
-
+
-
-
diff --git a/bench/index.js b/bench/index.js
index 538df4a5a71..b6d11c43111 100644
--- a/bench/index.js
+++ b/bench/index.js
@@ -1,67 +1,225 @@
'use strict';
+/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "BenchmarksView|clipboard" }]*/
-var mapboxgl = require('../js/mapbox-gl');
+var React = require('react');
+var ReactDOM = require('react-dom');
var util = require('../js/util/util');
-
-try {
- main();
-} catch (err) {
- log('red', err.toString());
- throw err;
+var async = require('async');
+var mapboxgl = require('../js/mapbox-gl');
+var Clipboard = require('clipboard');
+var URL = require('url');
+
+var BenchmarksView = React.createClass({
+
+ render: function() {
+ return
+ {this.renderBenchmarkSummaries()}
+ {this.renderBenchmarkDetails()}
+
;
+ },
+
+ renderBenchmarkSummaries: function() {
+ return
+
Benchmarks
+
+ {Object.keys(this.state.results).map(this.renderBenchmarkSummary)}
+
+
+ Copy Results
+
+
;
+ },
+
+ renderBenchmarkSummary: function(name) {
+ var results = this.state.results[name];
+ var that = this;
+
+ return
+ {name}: {results.message || '...'}
+
;
+ },
+
+ renderBenchmarkTextSummaries: function() {
+ var output = '# Benchmarks\n\n';
+ for (var name in this.state.results) {
+ var result = this.state.results[name];
+ output += '**' + name + ':** ' + (result.message || '...') + '\n';
+ }
+ return output;
+ },
+
+ renderBenchmarkDetails: function() {
+ return
+ {Object.keys(this.state.results).map(this.renderBenchmarkDetail)}
+
;
+ },
+
+ renderBenchmarkDetail: function(name) {
+ var results = this.state.results[name];
+ return (
+
+
+
"{name}" Benchmark
+ {results.logs.map(function(log, index) {
+ return
{log.message}
;
+ })}
+
+ );
+ },
+
+ scrollToBenchmark: function(name) {
+ var duration = 300;
+ var startTime = (new Date()).getTime();
+ var startYOffset = window.pageYOffset;
+
+ requestAnimationFrame(function frame() {
+ var endYOffset = document.getElementById(name).offsetTop;
+ var time = (new Date()).getTime();
+ var yOffset = Math.min((time - startTime) / duration, 1) * (endYOffset - startYOffset) + startYOffset;
+ window.scrollTo(0, yOffset);
+ if (time < startTime + duration) requestAnimationFrame(frame);
+ });
+ },
+
+ getInitialState: function() {
+ return {
+ state: 'waiting',
+ runningBenchmark: Object.keys(this.props.benchmarks)[0],
+ results: util.mapObject(this.props.benchmarks, function(_, name) {
+ return {
+ name: name,
+ state: 'waiting',
+ logs: []
+ };
+ })
+ };
+ },
+
+ componentDidMount: function() {
+ var that = this;
+ setTimeout(function() {
+ async.eachSeries(
+ Object.keys(that.props.benchmarks),
+ that.runBenchmark,
+ function() {
+ that.setState({state: 'ended'});
+ that.scrollToBenchmark(Object.keys(that.props.benchmarks)[0]);
+ }
+ );
+ }, 500);
+ },
+
+ runBenchmark: function(name, outerCallback) {
+ var that = this;
+ var results = this.state.results[name];
+ var maps = [];
+
+ results.state = 'running';
+ this.scrollToBenchmark(name);
+ this.setState({runningBenchmark: name});
+ log('dark', 'starting');
+
+ this.props.benchmarks[name]({
+ accessToken: getAccessToken(),
+ createMap: createMap
+
+ }).on('log', function(event) {
+ log(event.color, event.message);
+
+ }).on('end', function(event) {
+ results.message = event.message;
+ results.state = 'ended';
+ log('green', event.message);
+ callback();
+
+ }).on('error', function(event) {
+ results.state = 'errored';
+ log('red', event.error);
+ callback();
+ });
+
+ function log(color, message) {
+ results.logs.push({
+ color: color || 'blue',
+ message: message
+ });
+ that.forceUpdate();
+ }
+
+ function callback() {
+ for (var i = 0; i < maps.length; i++) {
+ maps[i].remove();
+ maps[i].getContainer().remove();
+ }
+ setTimeout(function() {
+ that.setState({runningBenchmark: null});
+ outerCallback();
+ }, 500);
+ }
+
+ function createMap(options) {
+ options = util.extend({width: 512, height: 512}, options);
+
+ var element = document.createElement('div');
+ element.style.width = options.width + 'px';
+ element.style.height = options.height + 'px';
+ element.style.margin = '0 auto';
+ document.body.appendChild(element);
+
+ var map = new mapboxgl.Map(util.extend({
+ container: element,
+ style: 'mapbox://styles/mapbox/streets-v9',
+ interactive: false
+ }, options));
+ maps.push(map);
+
+ return map;
+ }
+ }
+});
+
+var benchmarks = {
+ buffer: require('./benchmarks/buffer'),
+ fps: require('./benchmarks/fps'),
+ 'frame-duration': require('./benchmarks/frame_duration'),
+ 'query-point': require('./benchmarks/query_point'),
+ 'query-box': require('./benchmarks/query_box'),
+ 'geojson-setdata-small': require('./benchmarks/geojson_setdata_small'),
+ 'geojson-setdata-large': require('./benchmarks/geojson_setdata_large')
+};
+
+var filteredBenchmarks = {};
+var benchmarkName = URL.parse(window.location.toString()).pathname.split('/')[2];
+if (!benchmarkName) {
+ filteredBenchmarks = benchmarks;
+} else {
+ filteredBenchmarks[benchmarkName] = benchmarks[benchmarkName];
}
-function main() {
- log('dark', 'please keep this window in the foreground and close the debugger');
-
- var benchmarks = {
- buffer: require('./benchmarks/buffer'),
- fps: require('./benchmarks/fps'),
- 'frame-duration': require('./benchmarks/frame_duration'),
- 'query-point': require('./benchmarks/query_point'),
- 'query-box': require('./benchmarks/query_box'),
- 'geojson-setdata-small': require('./benchmarks/geojson_setdata_small'),
- 'geojson-setdata-large': require('./benchmarks/geojson_setdata_large')
- };
-
- var benchmarksDiv = document.getElementById('benchmarks');
-
- Object.keys(benchmarks).forEach(function(id) {
- benchmarksDiv.innerHTML += '' + id + '';
- });
-
- var pathnameArray = location.pathname.split('/');
- var benchmarkName = pathnameArray[pathnameArray.length - 1] || pathnameArray[pathnameArray.length - 2];
- var createBenchmark = benchmarks[benchmarkName];
- if (!createBenchmark) throw new Error('unknown benchmark "' + benchmarkName + '"');
-
- var benchmark = createBenchmark({
- accessToken: getAccessToken(),
- createMap: createMap
- });
-
- benchmark.on('log', function(event) {
- log(event.color || 'blue', event.message);
- scrollToBottom();
- });
-
- benchmark.on('end', function(event) {
- log('green', '' + event.message + '');
- scrollToBottom();
- });
-
- benchmark.on('error', function(event) {
- log('red', event.error);
- scrollToBottom();
- });
-}
+ReactDOM.render(, document.getElementById('benchmarks'));
-function scrollToBottom() {
- window.scrollTo(0, document.body.scrollHeight);
-}
+var clipboard = new Clipboard('.clipboard');
-function log(color, message) {
- document.getElementById('logs').innerHTML += '';
-}
+mapboxgl.accessToken = getAccessToken();
function getAccessToken() {
var accessToken = (
@@ -76,23 +234,6 @@ function getAccessToken() {
function getURLParameter(name) {
var regexp = new RegExp('[?&]' + name + '=([^]*)', 'i');
- var output = regexp.exec(window.location.href);
- return output && output[1];
-}
-
-function createMap(options) {
- var mapElement = document.getElementById('map');
-
- options = util.extend({width: 512, height: 512}, options);
-
- mapElement.style.display = 'block';
- mapElement.style.width = options.width + 'px';
- mapElement.style.height = options.height + 'px';
-
- mapboxgl.accessToken = getAccessToken();
- return new mapboxgl.Map(util.extend({
- container: 'map',
- style: 'mapbox://styles/mapbox/streets-v9',
- interactive: false
- }, options));
+ var results = regexp.exec(window.location.href);
+ return results && results[1];
}
diff --git a/ci.sh b/ci.sh
index 6da584037c4..51601868a2a 100755
--- a/ci.sh
+++ b/ci.sh
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
-set -e
+source ./nvm/nvm.sh
+nvm use ${NODE_VERSION}
+
+set -eu
set -o pipefail
# add npm packages to $PATH
@@ -22,8 +25,10 @@ tap --reporter dot --coverage --no-coverage-report test/js/*/*.js test/build/web
# allow writing core files for render tests
ulimit -c unlimited -S
echo 'ulimit -c: '`ulimit -c`
-echo '/proc/sys/kernel/core_pattern: '`cat /proc/sys/kernel/core_pattern`
-sysctl kernel.core_pattern
+if [[ $(uname -s) == 'Linux' ]]; then
+ echo '/proc/sys/kernel/core_pattern: '`cat /proc/sys/kernel/core_pattern`
+ sysctl kernel.core_pattern
+fi
# run render tests
istanbul cover --dir .nyc_output --include-pid --report none --print none test/render.test.js &&
@@ -34,10 +39,12 @@ EXIT_CODE=$?
if [[ ${EXIT_CODE} != 0 ]]; then
echo "The program crashed with exit code ${EXIT_CODE}. We're now trying to output the core dump."
fi
-for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do
- gdb `which node` ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch
- rm -rf ${DUMP}
-done
+if [[ $(uname -s) == 'Linux' ]]; then
+ for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do
+ gdb `which node` ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch
+ rm -rf ${DUMP}
+ done
+fi
# send coverage report to coveralls
nyc report --reporter=lcov
diff --git a/circle.yml b/circle.yml
index 25928305389..484a898a92f 100644
--- a/circle.yml
+++ b/circle.yml
@@ -1,9 +1,28 @@
machine:
- node:
- version: '4'
+ xcode:
+ version: 7.3
+ environment:
+ XCODE_SCHEME: "no"
+ XCODE_WORKSPACE: "no"
+ NODE_VERSION: 4
+
dependencies:
+ cache_directories:
+ - './nvm'
override:
- - if [ "$(git symbolic-ref --short -q HEAD)" == "master" ]; then npm update; else npm install; fi;
+ # disable spotlight to ensure we waste no CPU on needless file indexing
+ - if [[ $(uname -s) == 'Darwin' ]]; then sudo mdutil -i off /; fi;
+ - |
+ if [[ ! -d ./nvm ]]; then
+ git clone --depth 1 https://github.com/creationix/nvm.git ./nvm
+ fi
+ - source ./nvm/nvm.sh && nvm install ${NODE_VERSION}
+ - |
+ if [ "$(git symbolic-ref --short -q HEAD)" == "master" ]; then
+ source ./nvm/nvm.sh && nvm use ${NODE_VERSION} && npm update
+ else
+ source ./nvm/nvm.sh && nvm use ${NODE_VERSION} && npm install
+ fi
test:
override:
- ./ci.sh
diff --git a/debug/chinese.html b/debug/chinese.html
new file mode 100644
index 00000000000..4da63e62185
--- /dev/null
+++ b/debug/chinese.html
@@ -0,0 +1,67 @@
+
+
+
+ Mapbox GL JS debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/mapbox-gl.css b/dist/mapbox-gl.css
index 45fd3eae429..0f6bf2e6528 100644
--- a/dist/mapbox-gl.css
+++ b/dist/mapbox-gl.css
@@ -80,7 +80,7 @@
}
.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
padding: 5px;
- background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M13%2C7%20L10.5%2C11.75%20L10.25%2C10%20z%20M13.888%2C6.112%20C13.615%2C5.84%2013.382%2C6.076%2012.5%2C6.5%20C10.14%2C7.634%206%2C10%206%2C10%20L9.5%2C10.5%20L10%2C14%20C10%2C14%2012.366%2C9.86%2013.5%2C7.5%20C13.924%2C6.617%2014.16%2C6.385%2013.888%2C6.112%27%2F%3E%3C%2Fsvg%3E");
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgdmVyc2lvbj0iMS4xIj48cGF0aCBkPSJNMTAgNEM5IDQgOSA1IDkgNUw5IDUuMUE1IDUgMCAwIDAgNS4xIDlMNSA5QzUgOSA0IDkgNCAxMCA0IDExIDUgMTEgNSAxMUw1LjEgMTFBNSA1IDAgMCAwIDkgMTQuOUw5IDE1QzkgMTUgOSAxNiAxMCAxNiAxMSAxNiAxMSAxNSAxMSAxNUwxMSAxNC45QTUgNSAwIDAgMCAxNC45IDExTDE1IDExQzE1IDExIDE2IDExIDE2IDEwIDE2IDkgMTUgOSAxNSA5TDE0LjkgOUE1IDUgMCAwIDAgMTEgNS4xTDExIDVDMTEgNSAxMSA0IDEwIDR6TTEwIDYuNUEzLjUgMy41IDAgMCAxIDEzLjUgMTAgMy41IDMuNSAwIDAgMSAxMCAxMy41IDMuNSAzLjUgMCAwIDEgNi41IDEwIDMuNSAzLjUgMCAwIDEgMTAgNi41ek0xMCA4LjNBMS44IDEuOCAwIDAgMCA4LjMgMTAgMS44IDEuOCAwIDAgMCAxMCAxMS44IDEuOCAxLjggMCAwIDAgMTEuOCAxMCAxLjggMS44IDAgMCAwIDEwIDguM3oiIGZpbGw9IiMzMzMiLz48L3N2Zz4=");
}
.mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > div.arrow {
diff --git a/dist/svg/mapboxgl-ctrl-geolocate.svg b/dist/svg/mapboxgl-ctrl-geolocate.svg
new file mode 100644
index 00000000000..efb9a3f79e4
--- /dev/null
+++ b/dist/svg/mapboxgl-ctrl-geolocate.svg
@@ -0,0 +1 @@
+
diff --git a/js/data/bucket/symbol_bucket.js b/js/data/bucket/symbol_bucket.js
index b4438db3fab..12404232dcf 100644
--- a/js/data/bucket/symbol_bucket.js
+++ b/js/data/bucket/symbol_bucket.js
@@ -60,12 +60,12 @@ var layoutVertexArrayType = new Bucket.VertexArrayType([{
components: 2,
type: 'Int16'
}, {
- name: 'a_data1',
- components: 4,
- type: 'Uint8'
-}, {
- name: 'a_data2',
+ name: 'a_texture_pos',
components: 2,
+ type: 'Uint16'
+}, {
+ name: 'a_data',
+ components: 4,
type: 'Uint8'
}]);
@@ -73,20 +73,23 @@ var elementArrayType = new Bucket.ElementArrayType();
function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) {
return array.emplaceBack(
- // pos
+ // a_pos
x,
y,
- // offset
- Math.round(ox * 64), // use 1/64 pixels for placement
+
+ // a_offset
+ Math.round(ox * 64),
Math.round(oy * 64),
- // data1
- tx / 4, // tex
- ty / 4, // tex
+
+ // a_texture_pos
+ tx / 4, // x coordinate of symbol on glyph atlas texture
+ ty / 4, // y coordinate of symbol on glyph atlas texture
+
+ // a_data
(labelminzoom || 0) * 10, // labelminzoom
- labelangle, // labelangle
- // data2
- (minzoom || 0) * 10, // minzoom
- Math.min(maxzoom || 25, 25) * 10); // minzoom
+ labelangle, // labelangle
+ (minzoom || 0) * 10, // minzoom
+ Math.min(maxzoom || 25, 25) * 10); // maxzoom
}
SymbolBucket.prototype.addCollisionBoxVertex = function(layoutVertexArray, point, extrude, maxZoom, placementZoom) {
diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js
index f28ac0ad6a4..ac83327296b 100644
--- a/js/render/draw_fill.js
+++ b/js/render/draw_fill.js
@@ -8,13 +8,20 @@ function draw(painter, source, layer, coords) {
var gl = painter.gl;
gl.enable(gl.STENCIL_TEST);
- var color = layer.paint['fill-color'];
- var image = layer.paint['fill-pattern'];
- var opacity = layer.paint['fill-opacity'];
- var isOutlineColorDefined = layer.getPaintProperty('fill-outline-color');
+ var isOpaque;
+ if (layer.paint['fill-pattern']) {
+ isOpaque = false;
+ } else {
+ isOpaque = (
+ layer.isPaintValueFeatureConstant('fill-color') &&
+ layer.isPaintValueFeatureConstant('fill-opacity') &&
+ layer.paint['fill-color'][3] === 1 &&
+ layer.paint['fill-opacity'] === 1
+ );
+ }
// Draw fill
- if (image ? !painter.isOpaquePass : painter.isOpaquePass === (color[3] === 1 && opacity === 1)) {
+ if (painter.isOpaquePass === isOpaque) {
// Once we switch to earcut drawing we can pull most of the WebGL setup
// outside of this coords loop.
painter.setDepthSublayer(1);
@@ -27,6 +34,7 @@ function draw(painter, source, layer, coords) {
painter.lineWidth(2);
painter.depthMask(false);
+ var isOutlineColorDefined = layer.getPaintProperty('fill-outline-color');
if (isOutlineColorDefined || !layer.paint['fill-pattern']) {
if (isOutlineColorDefined) {
// If we defined a different color for the fill outline, we are
diff --git a/js/source/source_cache.js b/js/source/source_cache.js
index c72a10dd4ce..941ccd64182 100644
--- a/js/source/source_cache.js
+++ b/js/source/source_cache.js
@@ -35,6 +35,7 @@ function SourceCache(id, options, dispatcher) {
this.roundZoom = source.roundZoom;
this.reparseOverscaled = source.reparseOverscaled;
this.isTileClipped = source.isTileClipped;
+ this.attribution = source.attribution;
this.vectorLayerIds = source.vectorLayerIds;
diff --git a/js/source/vector_tile_source.js b/js/source/vector_tile_source.js
index 05bd1206f5e..7f39f4ed9fd 100644
--- a/js/source/vector_tile_source.js
+++ b/js/source/vector_tile_source.js
@@ -59,8 +59,13 @@ VectorTileSource.prototype = util.inherit(Evented, {
};
if (tile.workerID) {
- params.rawTileData = tile.rawTileData;
- this.dispatcher.send('reload tile', params, done.bind(this), tile.workerID);
+ if (tile.state === 'loading') {
+ // schedule tile reloading after it has been loaded
+ tile.reloadCallback = callback;
+ } else {
+ params.rawTileData = tile.rawTileData;
+ this.dispatcher.send('reload tile', params, done.bind(this), tile.workerID);
+ }
} else {
tile.workerID = this.dispatcher.send('load tile', params, done.bind(this));
}
@@ -81,6 +86,11 @@ VectorTileSource.prototype = util.inherit(Evented, {
}
callback(null);
+
+ if (tile.reloadCallback) {
+ this.loadTile(tile, tile.reloadCallback);
+ tile.reloadCallback = null;
+ }
}
},
diff --git a/js/style/style_declaration.js b/js/style/style_declaration.js
index 8ef97d330af..fee9e97333a 100644
--- a/js/style/style_declaration.js
+++ b/js/style/style_declaration.js
@@ -13,7 +13,7 @@ function StyleDeclaration(reference, value) {
// immutable representation of value. used for comparison
this.json = JSON.stringify(this.value);
- var parsedValue = reference.type === 'color' ? parseColor(this.value) : value;
+ var parsedValue = reference.type === 'color' && this.value ? parseColor(this.value) : value;
this.calculate = MapboxGLFunction[reference.function || 'piecewise-constant'](parsedValue);
this.isFeatureConstant = this.calculate.isFeatureConstant;
this.isZoomConstant = this.calculate.isZoomConstant;
diff --git a/js/symbol/glyph_atlas.js b/js/symbol/glyph_atlas.js
index 1177420f989..75275eb9b85 100644
--- a/js/symbol/glyph_atlas.js
+++ b/js/symbol/glyph_atlas.js
@@ -3,6 +3,11 @@
var ShelfPack = require('shelf-pack');
var util = require('../util/util');
+var SIZE_GROWTH_RATE = 4;
+var DEFAULT_SIZE = 128;
+// must be "DEFAULT_SIZE * SIZE_GROWTH_RATE ^ n" for some integer n
+var MAX_SIZE = 2048;
+
module.exports = GlyphAtlas;
@@ -10,8 +15,8 @@ function GlyphAtlas(width, height) {
this.width = width;
this.height = height;
- this.sprite = new ShelfPack(width, height);
- this.data = new Uint8Array(width * height);
+ this.sprite = new ShelfPack(this.width, this.height);
+ this.data = new Uint8Array(this.width * this.height);
this.tilebins = {};
}
@@ -46,7 +51,7 @@ GlyphAtlas.prototype.addGlyph = function(glyph, uid, buffer) {
// Increase to next number divisible by 4, but at least 1.
// This is so we can scale down the texture coordinates and pack them
- // into 2 bytes rather than 4 bytes.
+ // into fewer bytes.
packWidth += (4 - packWidth % 4);
packHeight += (4 - packHeight % 4);
@@ -91,12 +96,10 @@ GlyphAtlas.prototype.removeTileGlyphs = function(uid) {
GlyphAtlas.prototype.resize = function() {
- var origw = this.width,
- origh = this.height;
+ var prevWidth = this.width;
+ var prevHeight = this.height;
- // For now, don't grow the atlas beyond 1024x1024 because of how
- // texture coords pack into unsigned byte in symbol bucket.
- if (origw > 512 || origh > 512) return;
+ if (prevWidth >= MAX_SIZE || prevHeight >= MAX_SIZE) return;
if (this.texture) {
if (this.gl) {
@@ -105,15 +108,14 @@ GlyphAtlas.prototype.resize = function() {
this.texture = null;
}
- this.width *= 2;
- this.height *= 2;
+ this.width *= SIZE_GROWTH_RATE;
+ this.height *= SIZE_GROWTH_RATE;
this.sprite.resize(this.width, this.height);
- var buf = new ArrayBuffer(this.width * this.height),
- src, dst;
- for (var i = 0; i < origh; i++) {
- src = new Uint8Array(this.data.buffer, origh * i, origw);
- dst = new Uint8Array(buf, origh * i * 2, origw);
+ var buf = new ArrayBuffer(this.width * this.height);
+ for (var i = 0; i < prevHeight; i++) {
+ var src = new Uint8Array(this.data.buffer, prevHeight * i, prevWidth);
+ var dst = new Uint8Array(buf, prevHeight * i * SIZE_GROWTH_RATE, prevWidth);
dst.set(src);
}
this.data = new Uint8Array(buf);
diff --git a/js/symbol/glyph_source.js b/js/symbol/glyph_source.js
index ca7617d5e89..d31692074ce 100644
--- a/js/symbol/glyph_source.js
+++ b/js/symbol/glyph_source.js
@@ -30,7 +30,7 @@ GlyphSource.prototype.getSimpleGlyphs = function(fontstack, glyphIDs, uid, callb
this.stacks[fontstack] = {};
}
if (this.atlases[fontstack] === undefined) {
- this.atlases[fontstack] = new GlyphAtlas(128, 128);
+ this.atlases[fontstack] = new GlyphAtlas();
}
var glyphs = {};
diff --git a/js/ui/camera.js b/js/ui/camera.js
index 36ee7fe5af2..d9a02de6530 100644
--- a/js/ui/camera.js
+++ b/js/ui/camera.js
@@ -481,14 +481,16 @@ util.extend(Camera.prototype, /** @lends Map.prototype */{
},
_easeToEnd: function(eventData) {
- if (this.zooming) {
+ var wasZooming = this.zooming;
+ this.zooming = false;
+ this.rotating = false;
+ this.pitching = false;
+
+ if (wasZooming) {
this.fire('zoomend', eventData);
}
this.fire('moveend', eventData);
- this.zooming = false;
- this.rotating = false;
- this.pitching = false;
},
/**
@@ -688,11 +690,12 @@ util.extend(Camera.prototype, /** @lends Map.prototype */{
this.fire('pitch', eventData);
}
}, function() {
- this.fire('zoomend', eventData);
- this.fire('moveend', eventData);
this.zooming = false;
this.rotating = false;
this.pitching = false;
+
+ this.fire('zoomend', eventData);
+ this.fire('moveend', eventData);
}, options);
return this;
diff --git a/js/ui/map.js b/js/ui/map.js
index ec93aa44206..e58955027a4 100755
--- a/js/ui/map.js
+++ b/js/ui/map.js
@@ -1035,7 +1035,7 @@ util.extend(Map.prototype, /** @lends Map.prototype */{
this._styleDirty = true;
}
- if (this._sourcesDirty || this._repaint || !this.animationLoop.stopped()) {
+ if (this._sourcesDirty || this._repaint || this._styleDirty) {
this._rerender();
}
@@ -1058,6 +1058,8 @@ util.extend(Map.prototype, /** @lends Map.prototype */{
if (typeof window !== 'undefined') {
window.removeEventListener('resize', this._onWindowResize, false);
}
+ var extension = this.painter.gl.getExtension('WEBGL_lose_context');
+ if (extension) extension.loseContext();
removeNode(this._canvasContainer);
removeNode(this._controlContainer);
this._container.classList.remove('mapboxgl-map');
diff --git a/js/ui/popup.js b/js/ui/popup.js
index 08564ea9f1b..591250ced05 100644
--- a/js/ui/popup.js
+++ b/js/ui/popup.js
@@ -80,6 +80,17 @@ Popup.prototype = util.inherit(Evented, /** @lends Popup.prototype */{
delete this._map;
}
+ /**
+ * Fired when the popup is closed manually or programatically.
+ *
+ * @event close
+ * @memberof Popup
+ * @instance
+ * @type {Object}
+ * @property {Popup} popup object that was closed
+ */
+ this.fire('close');
+
return this;
},
diff --git a/js/util/browser/dispatcher.js b/js/util/browser/dispatcher.js
deleted file mode 100644
index 605d5b3bbdb..00000000000
--- a/js/util/browser/dispatcher.js
+++ /dev/null
@@ -1,73 +0,0 @@
-'use strict';
-
-var util = require('../util');
-var Actor = require('../actor');
-var WebWorkify = require('webworkify');
-
-module.exports = Dispatcher;
-
-/**
- * Responsible for sending messages from a {@link Source} to an associated
- * {@link WorkerSource}.
- *
- * @interface Dispatcher
- * @private
- */
-function Dispatcher(length, parent) {
- this.actors = [];
- this.currentActor = 0;
- for (var i = 0; i < length; i++) {
- var worker = new WebWorkify(require('../../source/worker'));
- var actor = new Actor(worker, parent);
- actor.name = "Worker " + i;
- this.actors.push(actor);
- }
-}
-
-Dispatcher.prototype = {
- /**
- * Broadcast a message to all Workers.
- * @method
- * @name broadcast
- * @param {string} type
- * @param {object} data
- * @param {Function} callback
- * @memberof Dispatcher
- * @instance
- */
- broadcast: function(type, data, cb) {
- cb = cb || function () {};
- util.asyncAll(this.actors, function (actor, done) {
- actor.send(type, data, done);
- }, cb);
- },
-
- /**
- * Send a message to a Worker.
- * @method
- * @name send
- * @param {string} type
- * @param {object} data
- * @param {Function} callback
- * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose.
- * @returns {number} The ID of the worker to which the message was sent.
- * @memberof Dispatcher
- * @instance
- */
- send: function(type, data, callback, targetID, buffers) {
- if (typeof targetID !== 'number' || isNaN(targetID)) {
- // Use round robin to send requests to web workers.
- targetID = this.currentActor = (this.currentActor + 1) % this.actors.length;
- }
-
- this.actors[targetID].send(type, data, callback, buffers);
- return targetID;
- },
-
- remove: function() {
- for (var i = 0; i < this.actors.length; i++) {
- this.actors[i].target.terminate();
- }
- this.actors = [];
- }
-};
diff --git a/js/util/browser/web_worker.js b/js/util/browser/web_worker.js
new file mode 100644
index 00000000000..7717220b6cf
--- /dev/null
+++ b/js/util/browser/web_worker.js
@@ -0,0 +1,6 @@
+'use strict';
+var WebWorkify = require('webworkify');
+
+module.exports = function () {
+ return new WebWorkify(require('../../source/worker'));
+};
diff --git a/js/util/dispatcher.js b/js/util/dispatcher.js
index 3406eaef8f8..7ca9bd7c367 100644
--- a/js/util/dispatcher.js
+++ b/js/util/dispatcher.js
@@ -1,57 +1,73 @@
'use strict';
-var Worker = require('../source/worker');
-var Actor = require('../util/actor');
+var util = require('./util');
+var Actor = require('./actor');
+var WebWorker = require('./web_worker');
module.exports = Dispatcher;
-function MessageBus(addListeners, postListeners) {
- return {
- addEventListener: function(event, callback) {
- if (event === 'message') {
- addListeners.push(callback);
- }
- },
- postMessage: function(data) {
- setImmediate(function() {
- for (var i = 0; i < postListeners.length; i++) {
- postListeners[i]({data: data, target: this.target});
- }
- }.bind(this));
- }
- };
-}
-
+/**
+ * Responsible for sending messages from a {@link Source} to an associated
+ * {@link WorkerSource}.
+ *
+ * @interface Dispatcher
+ * @private
+ */
function Dispatcher(length, parent) {
-
- this.actors = new Array(length);
-
- var parentListeners = [],
- workerListeners = [],
- parentBus = new MessageBus(workerListeners, parentListeners),
- workerBus = new MessageBus(parentListeners, workerListeners);
-
- parentBus.target = workerBus;
- workerBus.target = parentBus;
- // workerBus substitutes the WebWorker global `self`, and Worker uses
- // self.importScripts for the 'load worker source' target.
- workerBus.importScripts = function () {};
-
- this.worker = new Worker(workerBus);
- this.actor = new Actor(parentBus, parent);
-
- this.remove = function() {
- parentListeners.splice(0, parentListeners.length);
- workerListeners.splice(0, workerListeners.length);
- };
+ this.actors = [];
+ this.currentActor = 0;
+ for (var i = 0; i < length; i++) {
+ var worker = new WebWorker();
+ var actor = new Actor(worker, parent);
+ actor.name = "Worker " + i;
+ this.actors.push(actor);
+ }
}
Dispatcher.prototype = {
- broadcast: function(type, data, callback) {
- this.actor.send(type, data, callback);
+ /**
+ * Broadcast a message to all Workers.
+ * @method
+ * @name broadcast
+ * @param {string} type
+ * @param {object} data
+ * @param {Function} callback
+ * @memberof Dispatcher
+ * @instance
+ */
+ broadcast: function(type, data, cb) {
+ cb = cb || function () {};
+ util.asyncAll(this.actors, function (actor, done) {
+ actor.send(type, data, done);
+ }, cb);
},
+ /**
+ * Send a message to a Worker.
+ * @method
+ * @name send
+ * @param {string} type
+ * @param {object} data
+ * @param {Function} callback
+ * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose.
+ * @returns {number} The ID of the worker to which the message was sent.
+ * @memberof Dispatcher
+ * @instance
+ */
send: function(type, data, callback, targetID, buffers) {
- this.actor.send(type, data, callback, buffers);
+ if (typeof targetID !== 'number' || isNaN(targetID)) {
+ // Use round robin to send requests to web workers.
+ targetID = this.currentActor = (this.currentActor + 1) % this.actors.length;
+ }
+
+ this.actors[targetID].send(type, data, callback, buffers);
+ return targetID;
+ },
+
+ remove: function() {
+ for (var i = 0; i < this.actors.length; i++) {
+ this.actors[i].target.terminate();
+ }
+ this.actors = [];
}
};
diff --git a/js/util/vectortile_to_geojson.js b/js/util/vectortile_to_geojson.js
index aa3faa475ca..6d415119e36 100644
--- a/js/util/vectortile_to_geojson.js
+++ b/js/util/vectortile_to_geojson.js
@@ -10,8 +10,8 @@ function Feature(vectorTileFeature, z, x, y) {
this.properties = vectorTileFeature.properties;
- if (vectorTileFeature._id) {
- this.id = vectorTileFeature._id;
+ if (vectorTileFeature.id != null) {
+ this.id = vectorTileFeature.id;
}
}
diff --git a/js/util/web_worker.js b/js/util/web_worker.js
new file mode 100644
index 00000000000..df323369e31
--- /dev/null
+++ b/js/util/web_worker.js
@@ -0,0 +1,42 @@
+'use strict';
+
+var Worker = require('../source/worker');
+
+module.exports = function () {
+ var parentListeners = [],
+ workerListeners = [],
+ parentBus = new MessageBus(workerListeners, parentListeners),
+ workerBus = new MessageBus(parentListeners, workerListeners);
+
+ parentBus.target = workerBus;
+ workerBus.target = parentBus;
+ // workerBus substitutes the WebWorker global `self`, and Worker uses
+ // self.importScripts for the 'load worker source' target.
+ workerBus.importScripts = function () {};
+
+ new Worker(workerBus);
+
+ return parentBus;
+};
+
+function MessageBus(addListeners, postListeners) {
+ return {
+ addEventListener: function(event, callback) {
+ if (event === 'message') {
+ addListeners.push(callback);
+ }
+ },
+ postMessage: function(data) {
+ setImmediate(function() {
+ for (var i = 0; i < postListeners.length; i++) {
+ postListeners[i]({data: data, target: this.target});
+ }
+ }.bind(this));
+ },
+ terminate: function() {
+ addListeners.splice(0, addListeners.length);
+ postListeners.splice(0, postListeners.length);
+ }
+ };
+}
+
diff --git a/package.json b/package.json
index ae27299ea9f..4e586ce83c7 100644
--- a/package.json
+++ b/package.json
@@ -14,15 +14,15 @@
"dependencies": {
"csscolorparser": "^1.0.2",
"earcut": "^2.0.3",
- "feature-filter": "^2.1.0",
+ "feature-filter": "^2.2.0",
"geojson-rewind": "^0.1.0",
- "geojson-vt": "^2.3.0",
+ "geojson-vt": "^2.4.0",
"gl-matrix": "^2.3.1",
"grid-index": "^1.0.0",
"mapbox-gl-function": "^1.2.1",
- "mapbox-gl-supported": "^1.2.0",
- "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#4d1f89514bf03536c8e682439df165c33a37122a",
+ "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#de2ab007455aa2587c552694c68583f94c9f2747",
"mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#83b1a3e5837d785af582efd5ed1a212f2df6a4ae",
+ "mapbox-gl-supported": "^1.2.0",
"pbf": "^1.3.2",
"pngjs": "^2.2.0",
"point-geometry": "^0.0.0",
@@ -33,15 +33,19 @@
"supercluster": "^2.0.1",
"unassertify": "^2.0.0",
"unitbezier": "^0.0.0",
- "vector-tile": "1.2.1",
+ "vector-tile": "^1.3.0",
"vt-pbf": "^2.0.2",
"webworkify": "^1.3.0",
"whoots-js": "^2.0.0"
},
"devDependencies": {
+ "async": "^2.0.1",
+ "babel-preset-react": "^6.11.1",
+ "babelify": "^7.3.0",
"benchmark": "~2.1.0",
"browserify": "^13.0.0",
"browserify-middleware": "^7.0.0",
+ "clipboard": "^1.5.12",
"concat-stream": "1.5.1",
"coveralls": "^2.11.8",
"doctrine": "^1.2.1",
@@ -58,11 +62,13 @@
"istanbul": "^0.4.2",
"json-loader": "^0.5.4",
"lodash": "^4.13.1",
- "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#df624912cd514b89dc4405b233fc25db85bbb8b7",
+ "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#304729f1e66645e0d77e8cbd5d7093a496ee033e",
"memory-fs": "^0.3.0",
"minifyify": "^7.0.1",
"nyc": "6.4.0",
"proxyquire": "^1.7.9",
+ "react": "^15.3.0",
+ "react-dom": "^15.3.0",
"remark": "4.2.2",
"remark-html": "3.0.0",
"sinon": "^1.15.4",
@@ -87,7 +93,7 @@
"./js/util/browser.js": "./js/util/browser/browser.js",
"./js/util/canvas.js": "./js/util/browser/canvas.js",
"./js/util/dom.js": "./js/util/browser/dom.js",
- "./js/util/dispatcher.js": "./js/util/browser/dispatcher.js"
+ "./js/util/web_worker.js": "./js/util/browser/web_worker.js"
},
"scripts": {
"build-dev": "browserify js/mapbox-gl.js --debug --ignore-transform unassertify --standalone mapboxgl > dist/mapbox-gl-dev.js && tap --no-coverage test/build/dev.test.js",
diff --git a/server.js b/server.js
index acd8eaef7ea..a023f439072 100644
--- a/server.js
+++ b/server.js
@@ -23,26 +23,23 @@ app.get('/access-token.js', browserify('./debug/access-token.js', {
}));
app.get('/bench/index.js', browserify('./bench/index.js', {
- transform: ['unassertify', 'envify'],
+ transform: [['babelify', {presets: ['react']}], 'unassertify', 'envify'],
debug: true,
minify: true,
cache: 'dynamic',
precompile: true
}));
-app.get('/bench/:name', function(req, res) {
+app.get('/bench', function(req, res) {
res.sendFile(path.join(__dirname, 'bench', 'index.html'));
});
-app.get('/debug', function(req, res) {
- res.redirect('/');
+app.get('/bench/:name', function(req, res) {
+ res.sendFile(path.join(__dirname, 'bench', 'index.html'));
});
app.use(express.static(path.join(__dirname, 'debug')));
app.use('/dist', express.static(path.join(__dirname, 'dist')));
-
-// serve files in mapbox-gl-test-suite, so that debug files can use its
-// data/image/video/etc. assets
app.use('/mapbox-gl-test-suite', express.static(path.join(__dirname, 'node_modules/mapbox-gl-test-suite')));
downloadBenchData(function() {
diff --git a/test/js/data/symbol_bucket.test.js b/test/js/data/symbol_bucket.test.js
index b96bad2ca1e..a7390bfbacb 100644
--- a/test/js/data/symbol_bucket.test.js
+++ b/test/js/data/symbol_bucket.test.js
@@ -25,7 +25,7 @@ test('SymbolBucket', function(t) {
var symbolQuadsArray = new SymbolQuadsArray();
var symbolInstancesArray = new SymbolInstancesArray();
var collision = new Collision(0, 0, collisionBoxArray);
- var atlas = new GlyphAtlas(128, 128);
+ var atlas = new GlyphAtlas();
for (var id in glyphs) {
glyphs[id].bitmap = true;
glyphs[id].rect = atlas.addGlyph(glyphs[id], 1, 3);
diff --git a/test/js/source/source_cache.test.js b/test/js/source/source_cache.test.js
index 7e3d1c3b880..467beded1dd 100644
--- a/test/js/source/source_cache.test.js
+++ b/test/js/source/source_cache.test.js
@@ -49,6 +49,16 @@ function createSourceCache(options, used) {
return sc;
}
+test('SourceCache#attribution is set', function(t) {
+ var sourceCache = createSourceCache({
+ attribution: 'Mapbox Heavy Industries'
+ });
+ sourceCache.on('load', function() {
+ t.equal(sourceCache.attribution, 'Mapbox Heavy Industries');
+ t.end();
+ });
+});
+
test('SourceCache#addTile', function(t) {
t.test('loads tile when uncached', function(t) {
var coord = new TileCoord(0, 0, 0);
diff --git a/test/js/source/vector_tile_source.test.js b/test/js/source/vector_tile_source.test.js
index f48364154aa..a56fb469da6 100644
--- a/test/js/source/vector_tile_source.test.js
+++ b/test/js/source/vector_tile_source.test.js
@@ -112,6 +112,35 @@ test('VectorTileSource', function(t) {
testScheme('xyz', 'http://example.com/10/5/5.png');
testScheme('tms', 'http://example.com/10/5/1018.png');
+ t.test('reloads a loading tile properly', function (t) {
+ var source = createSource({
+ tiles: ["http://example.com/{z}/{x}/{y}.png"]
+ });
+ var events = [];
+ source.dispatcher.send = function(type, params, cb) {
+ events.push(type);
+ setTimeout(cb, 0);
+ return 1;
+ };
+
+ source.on('load', function () {
+ var tile = {
+ coord: new TileCoord(10, 5, 5, 0),
+ state: 'loading',
+ loadVectorData: function () {
+ this.state = 'loaded';
+ events.push('tile loaded');
+ }
+ };
+ source.loadTile(tile, function () {});
+ t.equal(tile.state, 'loading');
+ source.loadTile(tile, function () {
+ t.same(events, ['load tile', 'tile loaded', 'reload tile', 'tile loaded']);
+ t.end();
+ });
+ });
+ });
+
t.test('after', function(t) {
server.close(t.end);
});
diff --git a/test/js/style/style_layer.test.js b/test/js/style/style_layer.test.js
index d22bd8ca593..a54e02860f9 100644
--- a/test/js/style/style_layer.test.js
+++ b/test/js/style/style_layer.test.js
@@ -266,6 +266,26 @@ test('StyleLayer#setPaintProperty', function(t) {
});
});
+ t.test('can unset fill-outline-color #2886', function (t) {
+ var layer = StyleLayer.create({
+ id: 'building',
+ type: 'fill',
+ source: 'streets',
+ paint: {
+ 'fill-color': '#00f'
+ }
+ });
+
+ layer.setPaintProperty('fill-outline-color', '#f00');
+ layer.updatePaintTransitions([], {transition: false}, null, createAnimationLoop());
+ t.deepEqual(layer.getPaintValue('fill-outline-color'), [1, 0, 0, 1]);
+ layer.setPaintProperty('fill-outline-color', undefined);
+ layer.updatePaintTransitions([], {transition: false}, null, createAnimationLoop());
+ t.deepEqual(layer.getPaintValue('fill-outline-color'), [0, 0, 1, 1]);
+
+ t.end();
+ });
+
t.end();
});
diff --git a/test/js/ui/camera.test.js b/test/js/ui/camera.test.js
index 1afcd733a5d..3aa868f30df 100644
--- a/test/js/ui/camera.test.js
+++ b/test/js/ui/camera.test.js
@@ -627,7 +627,7 @@ test('camera', function(t) {
var movestarted, moved, rotated, pitched, zoomstarted, zoomed,
eventData = { data: 'ok' };
- t.plan(9);
+ t.plan(12);
camera
.on('movestart', function(d) { movestarted = d.data; })
@@ -635,6 +635,10 @@ test('camera', function(t) {
.on('rotate', function(d) { rotated = d.data; })
.on('pitch', function(d) { pitched = d.data; })
.on('moveend', function(d) {
+ t.notOk(camera.zooming);
+ t.notOk(camera.panning);
+ t.notOk(camera.rotating);
+
t.equal(movestarted, 'ok');
t.equal(moved, 'ok');
t.equal(zoomed, 'ok');
@@ -799,6 +803,10 @@ test('camera', function(t) {
.on('rotate', function(d) { rotated = d.data; })
.on('pitch', function(d) { pitched = d.data; })
.on('moveend', function(d) {
+ t.notOk(this.zooming);
+ t.notOk(this.panning);
+ t.notOk(this.rotating);
+
t.equal(movestarted, 'ok');
t.equal(moved, 'ok');
t.equal(zoomed, 'ok');
diff --git a/test/js/ui/map.test.js b/test/js/ui/map.test.js
index 3318d0db3df..5a743bf44f1 100755
--- a/test/js/ui/map.test.js
+++ b/test/js/ui/map.test.js
@@ -13,23 +13,23 @@ var fixed = require('../../testutil/fixed');
var fixedNum = fixed.Num;
var fixedLngLat = fixed.LngLat;
-test('Map', function(t) {
- function createMap(options) {
- return new Map(extend({
- container: {
- offsetWidth: 200,
- offsetHeight: 200,
- classList: {
- add: function() {},
- remove: function() {}
- }
- },
- interactive: false,
- attributionControl: false,
- trackResize: true
- }, options));
- }
+function createMap(options) {
+ return new Map(extend({
+ container: {
+ offsetWidth: 200,
+ offsetHeight: 200,
+ classList: {
+ add: function() {},
+ remove: function() {}
+ }
+ },
+ interactive: false,
+ attributionControl: false,
+ trackResize: true
+ }, options));
+}
+test('Map', function(t) {
t.test('constructor', function(t) {
var map = createMap({interactive: true});
t.ok(map.getContainer());
@@ -950,6 +950,39 @@ test('Map', function(t) {
t.end();
});
+ test('#removeLayer restores Map#loaded() to true', function (t) {
+ var style = createStyle();
+ style.sources.mapbox = {
+ type: 'vector',
+ minzoom: 1,
+ maxzoom: 10,
+ tiles: ['http://example.com/{z}/{x}/{y}.png']
+ };
+ style.layers.push({
+ id: 'layerId',
+ type: 'circle',
+ source: 'mapbox',
+ 'source-layer': 'sourceLayer'
+ });
+
+ var map = createMap({ style: style });
+ map.on('render', function () {
+ if (map.animationLoop.stopped()) {
+ map.removeLayer('layerId');
+ map.off('render');
+ map.on('render', check);
+ }
+ });
+
+ function check () {
+ if (map.loaded()) {
+ map.remove();
+ t.end();
+ }
+ }
+ });
+
+
t.end();
});