diff --git a/package.json b/package.json index 2e695f588f..fd2fc4517b 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "potpack": "^1.0.1", "quickselect": "^2.0.0", "rw": "^1.3.3", - "supercluster": "^7.0.0", + "supercluster": "^7.1.0", "tinyqueue": "^2.0.3", "vt-pbf": "^3.1.1" }, diff --git a/rollup/bundle_prelude.js b/rollup/bundle_prelude.js index 50fe63085e..4787f64f4d 100644 --- a/rollup/bundle_prelude.js +++ b/rollup/bundle_prelude.js @@ -14,6 +14,8 @@ if (!shared) { var sharedChunk = {}; shared(sharedChunk); mapboxgl = chunk(sharedChunk); - mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })); + if (typeof window !== 'undefined') { + mapboxgl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })); + } } } diff --git a/src/data/load_geometry.js b/src/data/load_geometry.js index 5a094fa1f8..c8885c57bd 100644 --- a/src/data/load_geometry.js +++ b/src/data/load_geometry.js @@ -10,14 +10,9 @@ import type Point from '@mapbox/point-geometry'; // While visible coordinates are within [0, EXTENT], tiles may theoretically // contain cordinates within [-Infinity, Infinity]. Our range is limited by the // number of bits used to represent the coordinate. -function createBounds(bits) { - return { - min: -1 * Math.pow(2, bits - 1), - max: Math.pow(2, bits - 1) - 1 - }; -} - -const bounds = createBounds(15); +const BITS = 15; +const MAX = Math.pow(2, BITS - 1) - 1; +const MIN = -MAX - 1; /** * Loads a geometry from a VectorTileFeature and scales it to the common extent @@ -34,13 +29,16 @@ export default function loadGeometry(feature: VectorTileFeature): Array bounds.max || point.y < bounds.min || point.y > bounds.max) { + if (x < point.x || x > point.x + 1 || y < point.y || y > point.y + 1) { + // warn when exceeding allowed extent except for the 1-px-off case + // https://github.com/mapbox/mapbox-gl-js/issues/8992 warnOnce('Geometry exceeds allowed extent, reduce your vector tile buffer size'); - point.x = clamp(point.x, bounds.min, bounds.max); - point.y = clamp(point.y, bounds.min, bounds.max); } } } diff --git a/src/source/geojson_source.js b/src/source/geojson_source.js index 67cfab6f8a..6bfc40b0ce 100644 --- a/src/source/geojson_source.js +++ b/src/source/geojson_source.js @@ -138,6 +138,7 @@ class GeoJSONSource extends Evented implements Source { maxZoom: options.clusterMaxZoom !== undefined ? Math.min(options.clusterMaxZoom, this.maxzoom - 1) : (this.maxzoom - 1), + minPoints: Math.max(2, options.clusterMinPoints || 2), extent: EXTENT, radius: (options.clusterRadius || 50) * scale, log: false, diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index dc09427c64..e713ee924e 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -377,6 +377,10 @@ "type": "number", "doc": "Max zoom on which to cluster points if clustering is enabled. Defaults to one zoom less than maxzoom (so that last zoom features are not clustered)." }, + "clusterMinPoints": { + "type": "number", + "doc": "Minimum number of points necessary to form a cluster if clustering is enabled. Defaults to `2`." + }, "clusterProperties": { "type": "*", "doc": "An object defining custom properties on the generated clusters if clustering is enabled, aggregating values from clustered points. Has the form `{\"property_name\": [operator, map_expression]}`. `operator` is any expression function that accepts at least 2 operands (e.g. `\"+\"` or `\"max\"`) — it accumulates the property value from clusters/points the cluster contains; `map_expression` produces the value of a single point.\n\nExample: `{\"sum\": [\"+\", [\"get\", \"scalerank\"]]}`.\n\nFor more advanced use cases, in place of `operator`, you can use a custom reduce expression that references a special `[\"accumulated\"]` value, e.g.:\n`{\"sum\": [[\"+\", [\"accumulated\"], [\"get\", \"sum\"]], [\"get\", \"scalerank\"]]}`" diff --git a/src/ui/handler_manager.js b/src/ui/handler_manager.js index e244ef1086..6ebba919b6 100644 --- a/src/ui/handler_manager.js +++ b/src/ui/handler_manager.js @@ -128,12 +128,14 @@ class HandlerManager { const el = this._el; this._listeners = [ - // Bind touchstart and touchmove with passive: false because, even though - // they only fire a map events and therefore could theoretically be - // passive, binding with passive: true causes iOS not to respect - // e.preventDefault() in _other_ handlers, even if they are non-passive - // (see https://bugs.webkit.org/show_bug.cgi?id=184251) - [el, 'touchstart', {passive: false}], + // This needs to be `passive: true` so that a double tap fires two + // pairs of touchstart/end events in iOS Safari 13. If this is set to + // `passive: false` then the second pair of events is only fired if + // preventDefault() is called on the first touchstart. Calling preventDefault() + // undesirably prevents click events. + [el, 'touchstart', {passive: true}], + // This needs to be `passive: false` so that scrolls and pinches can be + // prevented in browsers that don't support `touch-actions: none`, for example iOS Safari 12. [el, 'touchmove', {passive: false}], [el, 'touchend', undefined], [el, 'touchcancel', undefined], diff --git a/src/util/browser.js b/src/util/browser.js index 710841b0d1..808a6fcc5c 100755 --- a/src/util/browser.js +++ b/src/util/browser.js @@ -54,7 +54,7 @@ const exported = { return linkEl.href; }, - hardwareConcurrency: window.navigator.hardwareConcurrency || 4, + hardwareConcurrency: window.navigator && window.navigator.hardwareConcurrency || 4, get devicePixelRatio() { return window.devicePixelRatio; }, get prefersReducedMotion(): boolean { diff --git a/src/util/browser/window.js b/src/util/browser/window.js index 7a525659db..d2ceea5613 100644 --- a/src/util/browser/window.js +++ b/src/util/browser/window.js @@ -2,4 +2,5 @@ /* eslint-env browser */ import type {Window} from '../../types/window'; -export default (self: Window); +// shim window for the case of requiring the browser bundle in Node +export default typeof self !== 'undefined' ? (self: Window) : (({}: any): Window); diff --git a/src/util/dom.js b/src/util/dom.js index fd5d3e9149..34e2af64f8 100644 --- a/src/util/dom.js +++ b/src/util/dom.js @@ -20,7 +20,7 @@ DOM.createNS = function (namespaceURI: string, tagName: string) { return el; }; -const docStyle = window.document.documentElement.style; +const docStyle = window.document && window.document.documentElement.style; function testProp(props) { if (!docStyle) return props[0]; diff --git a/test/build/min.test.js b/test/build/min.test.js index 5cdcda5040..649a893274 100644 --- a/test/build/min.test.js +++ b/test/build/min.test.js @@ -36,6 +36,11 @@ test('can be browserified', (t) => { }); }); +test('evaluates without errors', (t) => { + t.doesNotThrow(() => require(path.join(__dirname, '../../dist/mapbox-gl.js'))); + t.end(); +}); + test('distributed in plain ES5 code', (t) => { const linter = new Linter(); const messages = linter.verify(minBundle, { diff --git a/test/unit/source/geojson_source.test.js b/test/unit/source/geojson_source.test.js index ee672172ae..ba8328460a 100644 --- a/test/unit/source/geojson_source.test.js +++ b/test/unit/source/geojson_source.test.js @@ -176,6 +176,7 @@ test('GeoJSONSource#update', (t) => { t.equal(message, 'geojson.loadData'); t.deepEqual(params.superclusterOptions, { maxZoom: 12, + minPoints: 3, extent: 8192, radius: 1600, log: false, @@ -190,6 +191,7 @@ test('GeoJSONSource#update', (t) => { cluster: true, clusterMaxZoom: 12, clusterRadius: 100, + clusterMinPoints: 3, generateId: true }, mockDispatcher).load(); }); diff --git a/yarn.lock b/yarn.lock index 9d306d9ede..e15ae38e4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10299,10 +10299,10 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" -supercluster@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.0.0.tgz#75d474fafb0a055db552ed7bd7bbda583f6ab321" - integrity sha512-8VuHI8ynylYQj7Qf6PBMWy1PdgsnBiIxujOgc9Z83QvJ8ualIYWNx2iMKyKeC4DZI5ntD9tz/CIwwZvIelixsA== +supercluster@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.0.tgz#f0a457426ec0ab95d69c5f03b51e049774b94479" + integrity sha512-LDasImUAFMhTqhK+cUXfy9C2KTUqJ3gucLjmNLNFmKWOnDUBxLFLH9oKuXOTCLveecmxh8fbk8kgh6Q0gsfe2w== dependencies: kdbush "^3.0.0"