Mapbox vector layer

This commit is contained in:
Tim Schaub
2020-05-03 14:15:14 -06:00
parent fcc9163494
commit 4d9975754f
20 changed files with 4642 additions and 33 deletions

View File

@@ -200,6 +200,11 @@ exports.handlers = {
processingComplete(e) {
const byLongname = e.doclets.index.longname;
for (const name in defaultExports) {
if (!(name in byLongname)) {
throw new Error(
`missing ${name} in doclet index, did you forget a @module tag?`
);
}
byLongname[name].forEach(function (doclet) {
doclet.isDefaultExport = true;
});

View File

@@ -0,0 +1,3 @@
.map {
background: #f8f4f0;
}

View File

@@ -0,0 +1,15 @@
---
layout: example.html
title: Mapbox Vector Layer
shortdesc: Rendering a layer with a Mapbox-hosted style.
docs: >
The MapboxVector layer allows you to create a layer based on a Mapbox-hosted style using a single
vector source. If your style uses more than one source, use the `source` property to choose a
single vector source. Use the `layers` property if you only want to render a subset of the style's
layers (provided they all share the same source).
tags: "mapbox, studio, vector, tiles"
cloak:
- key: pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2pzbmg0Nmk5MGF5NzQzbzRnbDNoeHJrbiJ9.7_-_gL8ur7ZtEiNwRfCy7Q
value: Your Mapbox access token from https://mapbox.com/ here
---
<div id="map" class="map"></div>

View File

@@ -0,0 +1,18 @@
import Map from '../src/ol/Map.js';
import MapboxVector from '../src/ol/layer/MapboxVector.js';
import View from '../src/ol/View.js';
const map = new Map({
target: 'map',
layers: [
new MapboxVector({
styleUrl: 'mapbox://styles/mapbox/bright-v9',
accessToken:
'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2pzbmg0Nmk5MGF5NzQzbzRnbDNoeHJrbiJ9.7_-_gL8ur7ZtEiNwRfCy7Q',
}),
],
view: new View({
center: [0, 0],
zoom: 2,
}),
});

36
package-lock.json generated
View File

@@ -1637,14 +1637,12 @@
"@mapbox/jsonlint-lines-primitives": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
"integrity": "sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=",
"dev": true
"integrity": "sha1-zlblOfg1UrWNENZy6k1vya3HsjQ="
},
"@mapbox/mapbox-gl-style-spec": {
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.13.0.tgz",
"integrity": "sha512-PBXa/Bw2G87NZckBZqY3omI3THF5MYQQY5B1IZWVLI7Ujsy149cjC8Sm1Ub1BgAnyslepdjtwWVS43IOjVYmUw==",
"dev": true,
"requires": {
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
"@mapbox/point-geometry": "^0.1.0",
@@ -1659,22 +1657,19 @@
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}
}
},
"@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=",
"dev": true
"integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI="
},
"@mapbox/unitbezier": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
"integrity": "sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4=",
"dev": true
"integrity": "sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4="
},
"@nodelib/fs.scandir": {
"version": "2.1.3",
@@ -3786,8 +3781,7 @@
"csscolorparser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
"integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs=",
"dev": true
"integrity": "sha1-s085HupNqPPpgjHizNjfnAQfFxs="
},
"custom-event": {
"version": "1.0.1",
@@ -7644,8 +7638,7 @@
"json-stringify-pretty-compact": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==",
"dev": true
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ=="
},
"json-stringify-safe": {
"version": "5.0.1",
@@ -8168,8 +8161,7 @@
"mapbox-to-css-font": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.0.tgz",
"integrity": "sha512-v674D0WtpxCXlA6E+sBlG1QJWdUkz/s9qAD91bJSXBGuBL5lL4tJXpoJEftecphCh2SVQCjWMS2vhylc3AIQTg==",
"dev": true
"integrity": "sha512-v674D0WtpxCXlA6E+sBlG1QJWdUkz/s9qAD91bJSXBGuBL5lL4tJXpoJEftecphCh2SVQCjWMS2vhylc3AIQTg=="
},
"markdown-it": {
"version": "10.0.0",
@@ -9039,7 +9031,6 @@
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-6.1.1.tgz",
"integrity": "sha512-0Hgz2BX2tWe1ZNPMLpJkLdm3XI6ILrFbgmJIvdrlDYRce2ul1mXLmkJmbyLFs2tozsBJDcPJmI55UsKbiKg6ow==",
"dev": true,
"requires": {
"@mapbox/mapbox-gl-style-spec": "13.13.0",
"mapbox-to-css-font": "^2.4.0",
@@ -10383,8 +10374,7 @@
"rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=",
"dev": true
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
},
"rxjs": {
"version": "6.5.5",
@@ -11075,20 +11065,17 @@
"sort-asc": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz",
"integrity": "sha1-q3md9h/HPqCVbHnEtTHtHp53J+k=",
"dev": true
"integrity": "sha1-q3md9h/HPqCVbHnEtTHtHp53J+k="
},
"sort-desc": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz",
"integrity": "sha1-GYuMDN6wlcRjNBhh45JdTuNZqe4=",
"dev": true
"integrity": "sha1-GYuMDN6wlcRjNBhh45JdTuNZqe4="
},
"sort-object": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz",
"integrity": "sha1-mODRme3kDgfGGoRAPGHWw7KQ+eI=",
"dev": true,
"requires": {
"sort-asc": "^0.1.0",
"sort-desc": "^0.1.1"
@@ -12335,8 +12322,7 @@
"webfont-matcher": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/webfont-matcher/-/webfont-matcher-1.1.0.tgz",
"integrity": "sha1-mM6VCXsp4x++czBT4Q5XFkLRxsc=",
"dev": true
"integrity": "sha1-mM6VCXsp4x++czBT4Q5XFkLRxsc="
},
"webpack": {
"version": "4.43.0",

View File

@@ -42,6 +42,7 @@
},
"dependencies": {
"elm-pep": "^1.0.4",
"ol-mapbox-style": "^6.1.1",
"pbf": "3.2.1",
"pixelworks": "1.1.0",
"rbush": "^3.0.1"
@@ -85,7 +86,6 @@
"loglevelnext": "^4.0.1",
"marked": "1.0.0",
"mocha": "7.1.2",
"ol-mapbox-style": "^6.1.1",
"pixelmatch": "^5.1.0",
"pngjs": "^5.0.0",
"proj4": "2.6.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,22 @@
import Map from '../../../src/ol/Map.js';
import MapboxVector from '../../../src/ol/layer/MapboxVector.js';
import View from '../../../src/ol/View.js';
new Map({
layers: [
new MapboxVector({
styleUrl: '/data/styles/bright-v9.json',
accessToken: 'test-token',
}),
],
target: 'map',
view: new View({
center: [1825927.7316762917, 6143091.089223046],
zoom: 15,
}),
});
render({
message: 'Mapbox vector layer renders',
tolerance: 0.025,
});

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,12 @@ To run a single rendering test case:
node rendering/test.js --match your-test-case-name
```
If you want to leave the test server running (and the test browser open) after running a test, use the `--interactive` option.
```bash
node rendering/test.js --match your-test-case-name --interactive
```
## Creating a new test
To create a new test case, add a directory under `cases` and add a `main.js` to it (copied from one of the other cases). Then to generate the `expected.png` screenshot, run the test script with the `--fix` flag:

View File

@@ -251,7 +251,7 @@ async function render(entries, options) {
page.on('console', (message) => {
const type = message.type();
if (options.log[type]) {
options.log[type](message.text());
options.log[type](`console: ${message.text()}`);
}
});
@@ -260,6 +260,10 @@ async function render(entries, options) {
await page.setViewport({width: 256, height: 256});
fail = await renderEach(page, entries, options);
} finally {
if (options.interactive) {
options.log.info('🐛 you have thirty minutes to debug, go!');
await sleep(30 * 60 * 1000);
}
browser.close();
}
@@ -324,10 +328,14 @@ async function main(entries, options) {
try {
await render(entries, options);
} finally {
if (!options.interactive) {
done();
}
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
if (require.main === module) {
@@ -356,8 +364,7 @@ if (require.main === module) {
default: false,
})
.option('interactive', {
describe:
'Run all tests and keep the test server running (this option will be reworked later)',
describe: 'Run all tests and keep the test server running for 30 minutes',
type: 'boolean',
default: false,
})

View File

@@ -34,4 +34,10 @@ module.exports = {
},
],
},
resolve: {
alias: {
// allow imports from 'ol/module' instead of specifiying the source path
ol: path.join(__dirname, '..', 'src', 'ol'),
},
},
};

View File

@@ -0,0 +1,405 @@
/**
* @module ol/layer/MapboxVector
*/
import BaseEvent from '../events/Event.js';
import EventType from '../events/EventType.js';
import MVT from '../format/MVT.js';
import SourceState from '../source/State.js';
import VectorTileLayer from '../layer/VectorTile.js';
import VectorTileSource from '../source/VectorTile.js';
import {applyStyle} from 'ol-mapbox-style';
const mapboxBaseUrl = 'https://api.mapbox.com';
/**
* Gets the path from a mapbox:// URL.
* @param {string} url The Mapbox URL.
* @return {string} The path.
* @private
*/
export function getMapboxPath(url) {
const startsWith = 'mapbox://';
if (url.indexOf(startsWith) !== 0) {
return '';
}
return url.slice(startsWith.length);
}
/**
* Turns mapbox:// sprite URLs into resolvable URLs.
* @param {string} url The sprite URL.
* @param {string} token The access token.
* @return {string} A resolvable URL.
* @private
*/
export function normalizeSpriteUrl(url, token) {
const mapboxPath = getMapboxPath(url);
if (!mapboxPath) {
return url;
}
const startsWith = 'sprites/';
if (mapboxPath.indexOf(startsWith) !== 0) {
throw new Error(`unexpected sprites url: ${url}`);
}
const sprite = mapboxPath.slice(startsWith.length);
return `${mapboxBaseUrl}/styles/v1/${sprite}/sprite?access_token=${token}`;
}
/**
* Turns mapbox:// glyphs URLs into resolvable URLs.
* @param {string} url The glyphs URL.
* @param {string} token The access token.
* @return {string} A resolvable URL.
* @private
*/
export function normalizeGlyphsUrl(url, token) {
const mapboxPath = getMapboxPath(url);
if (!mapboxPath) {
return url;
}
const startsWith = 'fonts/';
if (mapboxPath.indexOf(startsWith) !== 0) {
throw new Error(`unexpected fonts url: ${url}`);
}
const font = mapboxPath.slice(startsWith.length);
return `${mapboxBaseUrl}/fonts/v1/${font}/0-255.pbf?access_token=${token}`;
}
/**
* Turns mapbox:// style URLs into resolvable URLs.
* @param {string} url The style URL.
* @param {string} token The access token.
* @return {string} A resolvable URL.
* @private
*/
export function normalizeStyleUrl(url, token) {
const mapboxPath = getMapboxPath(url);
if (!mapboxPath) {
return url;
}
const startsWith = 'styles/';
if (mapboxPath.indexOf(startsWith) !== 0) {
throw new Error(`unexpected style url: ${url}`);
}
const style = mapboxPath.slice(startsWith.length);
return `${mapboxBaseUrl}/styles/v1/${style}?&access_token=${token}`;
}
/**
* Turns mapbox:// source URLs into vector tile URL templates.
* @param {string} url The source URL.
* @param {string} token The access token.
* @return {string} A vector tile template.
* @private
*/
export function normalizeSourceUrl(url, token) {
const mapboxPath = getMapboxPath(url);
if (!mapboxPath) {
return url;
}
return `https://{a-d}.tiles.mapbox.com/v4/${mapboxPath}/{z}/{x}/{y}.vector.pbf?access_token=${token}`;
}
/**
* @classdesc
* Event emitted on configuration or loading error.
*/
class ErrorEvent extends BaseEvent {
/**
* @param {Error} error error object.
*/
constructor(error) {
super(EventType.ERROR);
/**
* @type {Error}
*/
this.error = error;
}
}
/**
* @typedef {Object} StyleObject
* @property {Object<string, SourceObject>} sources The style sources.
* @property {string} sprite The sprite URL.
* @property {string} glyphs The glyphs URL.
* @property {Array<LayerObject>} layers The style layers.
*/
/**
* @typedef {Object} SourceObject
* @property {string} url The source URL.
* @property {SourceType} type The source type.
*/
/**
* The Mapbox source type.
* @enum {string}
*/
const SourceType = {
VECTOR: 'vector',
};
/**
* @typedef {Object} LayerObject
* @property {string} id The layer id.
* @property {string} source The source id.
*/
/**
* @typedef {Object} Options
* @property {string} styleUrl The URL of the Mapbox style object to use for this layer. For a
* style created with Mapbox Studio and hosted on Mapbox, this will look like
* 'mapbox://styles/you/your-style'.
* @property {string} accessToken The access token for your Mapbox style.
* @property {string} [source] If your style uses more than one source, you need to use either the
* `source` property or the `layers` property to limit rendering to a single vector source. The
* `source` property corresponds to the id of a vector source in your Mapbox style.
* @property {Array<string>} [layers] Limit rendering to the list of included layers. All layers
* must share the same vector soource. If your style uses more than one source, you need to use
* either the `source` property or the `layers` property to limit rendering to a single vector
* source.
* @property {boolean} [declutter=true] Declutter images and text. Decluttering is applied to all
* image and text styles of all Vector and VectorTile layers that have set this to `true`. The priority
* is defined by the z-index of the layer, the `zIndex` of the style and the render order of features.
* Higher z-index means higher priority. Within the same z-index, a feature rendered before another has
* higher priority.
* @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
* @property {number} [opacity=1] Opacity (0, 1).
* @property {boolean} [visible=true] Visibility.
* @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be
* rendered outside of this extent.
* @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
* will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
* for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
* method was used.
* @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../render.js").OrderFunction} [renderOrder] Render order. Function to be used when sorting
* features before rendering. By default features are drawn in the order that they are created. Use
* `null` to avoid the sort, but get an undefined draw order.
* @property {number} [renderBuffer=100] The buffer in pixels around the tile extent used by the
* renderer when getting features from the vector tile for the rendering or hit-detection.
* Recommended value: Vector tiles are usually generated with a buffer, so this value should match
* the largest possible buffer of the used tiles. It should be at least the size of the largest
* point symbol or line width.
* @property {import("./VectorTileRenderType.js").default|string} [renderMode='hybrid'] Render mode for vector tiles:
* * `'image'`: Vector tiles are rendered as images. Great performance, but point symbols and texts
* are always rotated with the view and pixels are scaled during zoom animations. When `declutter`
* is set to `true`, the decluttering is done per tile resulting in labels and point symbols getting
* cut off at tile boundaries.
* * `'hybrid'`: Polygon and line elements are rendered as images, so pixels are scaled during zoom
* animations. Point symbols and texts are accurately rendered as vectors and can stay upright on
* rotated views.
* * `'vector'`: Everything is rendered as vectors. Use this mode for improved performance on vector
* tile layers with only a few rendered features (e.g. for highlighting a subset of features of
* another layer with the same source).
* @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
* this layer in its layers collection, and the layer will be rendered on top. This is useful for
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
* use {@link module:ol/Map#addLayer}.
* @property {boolean} [updateWhileAnimating=false] When set to `true`, feature batches will be
* recreated during animations. This means that no vectors will be shown clipped, but the setting
* will have a performance impact for large amounts of vector data. When set to `false`, batches
* will be recreated when no animation is active.
* @property {boolean} [updateWhileInteracting=false] When set to `true`, feature batches will be
* recreated during interactions. See also `updateWhileAnimating`.
* @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
* means no preloading.
* @property {boolean} [useInterimTilesOnError=true] Use interim tiles on error.
*/
/**
* @classdesc
* A vector tile layer based on a Mapbox style that uses a single vector source. Configure
* the layer with the `styleUrl` and `accessToken` shown in Mapbox Studio's share panel.
* If the style uses more than one source, use the `source` property to choose a single
* vector source. If you want to render a subset of the layers in the style, use the `layers`
* property (all layers must share the same vector source). See the constructor options for
* more detail.
*
* var map = new Map({
* view: new View({
* center: [0, 0],
* zoom: 1
* }),
* layers: [
* new MapboxVector({
* styleUrl: 'mapbox://styles/mapbox/bright-v9',
* accessToken: 'your-mapbox-access-token-here'
* })
* ],
* target: 'map'
* });
*
* On configuration or loading error, the layer will trigger an `'error'` event. Listeners
* will receive an object with an `error` property that can be used to diagnose the problem.
*
* @param {Options} options Options.
* @extends {VectorTileLayer}
* @fires module:ol/events/Event~BaseEvent#event:error
* @api
*/
class MapboxVectorLayer extends VectorTileLayer {
/**
* @param {Options} options Layer options. At a minimum, `styleUrl` and `accessToken`
* must be provided.
*/
constructor(options) {
const declutter = 'declutter' in options ? options.declutter : true;
const source = new VectorTileSource({
state: SourceState.LOADING,
format: new MVT(),
});
super({
source: source,
declutter: declutter,
className: options.className,
opacity: options.opacity,
visible: options.visible,
zIndex: options.zIndex,
minResolution: options.minResolution,
maxResolution: options.maxResolution,
minZoom: options.minZoom,
maxZoom: options.maxZoom,
renderOrder: options.renderOrder,
renderBuffer: options.renderBuffer,
renderMode: options.renderMode,
map: options.map,
updateWhileAnimating: options.updateWhileAnimating,
updateWhileInteracting: options.updateWhileInteracting,
preload: options.preload,
useInterimTilesOnError: options.useInterimTilesOnError,
});
this.sourceId = options.source;
this.layers = options.layers;
this.accessToken = options.accessToken;
this.fetchStyle(options.styleUrl);
}
/**
* Fetch the style object.
* @param {string} styleUrl The URL of the style to load.
* @protected
*/
fetchStyle(styleUrl) {
const url = normalizeStyleUrl(styleUrl, this.accessToken);
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(
`unexpected response when fetching style: ${response.status}`
);
}
return response.json();
})
.then((style) => {
this.onStyleLoad(style);
})
.catch((error) => {
this.handleError(error);
});
}
/**
* Handle the loaded style object.
* @param {StyleObject} style The loaded style.
* @protected
*/
onStyleLoad(style) {
let sourceId;
let sourceIdOrLayersList;
if (this.layers) {
// confirm all layers share the same source
const lookup = {};
for (let i = 0; i < style.layers.length; ++i) {
const layer = style.layers[i];
if (layer.source) {
lookup[layer.id] = layer.source;
}
}
let firstSource;
for (let i = 0; i < this.layers.length; ++i) {
const candidate = lookup[this.layers[i]];
if (!candidate) {
this.handleError(
new Error(`could not find source for ${this.layers[i]}`)
);
return;
}
if (!firstSource) {
firstSource = candidate;
} else if (firstSource !== candidate) {
this.handleError(
new Error(
`layers can only use a single source, found ${firstSource} and ${candidate}`
)
);
return;
}
}
sourceId = firstSource;
sourceIdOrLayersList = this.layers;
} else {
sourceId = this.sourceId;
sourceIdOrLayersList = sourceId;
}
if (!sourceIdOrLayersList) {
// default to the first source in the style
sourceId = Object.keys(style.sources)[0];
sourceIdOrLayersList = sourceId;
}
if (style.sprite) {
style.sprite = normalizeSpriteUrl(style.sprite, this.accessToken);
}
if (style.glyphs) {
style.glyphs = normalizeGlyphsUrl(style.glyphs, this.accessToken);
}
const styleSource = style.sources[sourceId];
if (styleSource.type !== SourceType.VECTOR) {
this.handleError(
new Error(`only works for vector sources, found ${styleSource.type}`)
);
return;
}
const source = this.getSource();
source.setUrl(normalizeSourceUrl(styleSource.url, this.accessToken));
applyStyle(this, style, sourceIdOrLayersList)
.then(() => {
source.setState(SourceState.READY);
})
.catch((error) => {
this.handleError(error);
});
}
/**
* Handle configuration or loading error.
* @param {Error} error The error.
* @protected
*/
handleError(error) {
this.dispatchEvent(new ErrorEvent(error));
const source = this.getSource();
source.setState(SourceState.ERROR);
}
}
export default MapboxVectorLayer;

View File

@@ -163,7 +163,6 @@ class Source extends BaseObject {
/**
* Set the state of the source.
* @param {import("./State.js").default} state State.
* @protected
*/
setState(state) {
this.state_ = state;

View File

@@ -118,6 +118,12 @@ module.exports = function (karma) {
},
],
},
resolve: {
alias: {
// allow imports from 'ol/module' instead of specifiying the source path
ol: path.join(__dirname, '..', 'src', 'ol'),
},
},
},
webpackMiddleware: {
noInfo: true,

View File

@@ -0,0 +1,94 @@
import {
getMapboxPath,
normalizeSourceUrl,
normalizeSpriteUrl,
normalizeStyleUrl,
} from '../../../../src/ol/layer/MapboxVector.js';
describe('ol/layer/MapboxVector', () => {
describe('getMapboxPath()', () => {
const cases = [
{
url: 'mapbox://path/to/resource',
expected: 'path/to/resource',
},
{
url: 'mapbox://path/to/resource?query',
expected: 'path/to/resource?query',
},
{
url: 'https://example.com/resource',
expected: '',
},
];
for (const c of cases) {
it(`works for ${c.url}`, () => {
expect(getMapboxPath(c.url)).to.be(c.expected);
});
}
});
describe('normalizeStyleUrl()', () => {
const cases = [
{
url: 'mapbox://styles/mapbox/bright-v9',
expected:
'https://api.mapbox.com/styles/v1/mapbox/bright-v9?&access_token=test-token',
},
{
url: 'https://example.com/style',
expected: 'https://example.com/style',
},
];
const token = 'test-token';
for (const c of cases) {
it(`works for ${c.url}`, () => {
expect(normalizeStyleUrl(c.url, token)).to.be(c.expected);
});
}
});
describe('normalizeSpriteUrl()', () => {
const cases = [
{
url: 'mapbox://sprites/mapbox/bright-v9',
expected:
'https://api.mapbox.com/styles/v1/mapbox/bright-v9/sprite?access_token=test-token',
},
{
url: 'https://example.com/sprite',
expected: 'https://example.com/sprite',
},
];
const token = 'test-token';
for (const c of cases) {
it(`works for ${c.url}`, () => {
expect(normalizeSpriteUrl(c.url, token)).to.be(c.expected);
});
}
});
describe('normalizeSourceUrl()', () => {
const cases = [
{
url: 'mapbox://mapbox.mapbox-streets-v7',
expected:
'https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v7/{z}/{x}/{y}.vector.pbf?access_token=test-token',
},
{
url: 'https://example.com/source/{z}/{x}/{y}.pbf',
expected: 'https://example.com/source/{z}/{x}/{y}.pbf',
},
];
const token = 'test-token';
for (const c of cases) {
it(`works for ${c.url}`, () => {
expect(normalizeSourceUrl(c.url, token)).to.be(c.expected);
});
}
});
});