diff --git a/debug/custom-source.html b/debug/custom-source.html index 56f6de16f1e..0d0db52e538 100644 --- a/debug/custom-source.html +++ b/debug/custom-source.html @@ -8,11 +8,16 @@
+
+ + +
@@ -20,42 +25,71 @@ var map = window.map = new mapboxgl.Map({ container: 'map', - center: [-74.5, 40], - zoom: 2, - style: { - version: 8, - sources: {}, - layers: [] - }, + center: [0, 0], + zoom: 0, + style: 'mapbox://styles/mapbox/light-v10', hash: true }); +const tileSize = 256; +const colors = ['#74a9cf', '#3690c0', '#0570b0', '#045a8d']; +let currentColor = colors[0]; + map.on('load', () => { map.addSource('custom-source', { type: 'custom', - tileSize: 256, - attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA', - async loadTile(tile, {signal}) { - const tilesUrl = 'https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg'; - const url = tilesUrl - .replace('{z}', String(tile.z)) - .replace('{x}', String(tile.x)) - .replace('{y}', String(tile.y)); + tileSize, + async loadTile({z, x, y}) { + const tileSize = 256; + const offscreenCanvas = new OffscreenCanvas(tileSize, tileSize); + const context = offscreenCanvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, tileSize, tileSize); + + context.font = '18px serif'; + context.fillStyle = 'white'; + context.textAlign = 'center'; + context.fillText(`${z}/${x}/${y}`, tileSize / 2, tileSize / 2, tileSize); - const response = await fetch(url, {signal}); - const data = await response.arrayBuffer(); + const imageData = context.getImageData(0, 0, tileSize, tileSize); + return imageData; + }, + prepareTile({z, x, y}) { + const tileSize = 256; + const offscreenCanvas = new OffscreenCanvas(tileSize, tileSize); + const context = offscreenCanvas.getContext('2d'); + context.fillStyle = currentColor; + context.fillRect(0, 0, tileSize, tileSize); - const blob = new window.Blob([new Uint8Array(data)], {type: 'image/png'}); - const imageBitmap = await window.createImageBitmap(blob); + context.font = '18px serif'; + context.fillStyle = 'white'; + context.textAlign = 'center'; + context.fillText(`${z}/${x}/${y}`, tileSize / 2, tileSize / 2, tileSize); - return imageBitmap; + const imageData = context.getImageData(0, 0, tileSize, tileSize); + return imageData; + }, + hasTile({z, x, y}) { + return (x + y) % 2 === 0; } }); map.addLayer({ id: 'custom-source', type: 'raster', - source: 'custom-source' + source: 'custom-source', + paint: { + 'raster-opacity': 0.75, + 'raster-fade-duration': 0 + } + }); + + const slider = document.getElementById('slider'); + slider.value = 0; + slider.max = colors.length - 1; + slider.addEventListener('input', (e) => { + currentColor = colors[e.target.value]; + map.triggerRepaint(); }); }); diff --git a/src/source/custom_source.js b/src/source/custom_source.js index 4dea3c61e97..d22bd71f02a 100644 --- a/src/source/custom_source.js +++ b/src/source/custom_source.js @@ -326,6 +326,7 @@ class CustomSource extends Evented implements Source { if (!data) return null; this.loadTileData(tile, data); + tile.state = 'loaded'; return data; } diff --git a/test/release/custom-source.html b/test/release/custom-source.html new file mode 120000 index 00000000000..2178163f7e7 --- /dev/null +++ b/test/release/custom-source.html @@ -0,0 +1 @@ +../../debug/custom-source.html \ No newline at end of file diff --git a/test/release/index.js b/test/release/index.js index 6723ab328bb..68ccb96d324 100644 --- a/test/release/index.js +++ b/test/release/index.js @@ -101,6 +101,11 @@ const pages = [ "key": "preload-tiles", "title": "Preload tiles", "url": "./preload-tiles.html" + }, + { + "key": "custom-source", + "title": "Custom Source", + "url": "./custom-source.html" } ]; diff --git a/test/unit/source/custom_source.test.js b/test/unit/source/custom_source.test.js index de3b6a9ccb6..d1d39730909 100644 --- a/test/unit/source/custom_source.test.js +++ b/test/unit/source/custom_source.test.js @@ -202,6 +202,27 @@ test('CustomSource', (t) => { sourceCache._addTile(tileID); }); + t.test('loadTile is not called if prepareTile returns data', (t) => { + const tileID = new OverscaledTileID(0, 0, 0, 0, 0); + + const loadTile = t.spy(); + const prepareTile = t.spy((tile) => { + const {x, y, z} = tileID.canonical; + t.deepEqual(tile, {x, y, z}); + return new window.ImageData(512, 512); + }); + + const {sourceCache} = createSource(t, {loadTile, prepareTile}); + + sourceCache.onAdd(); + sourceCache._addTile(tileID); + + t.ok(prepareTile.calledOnce, 'prepareTile must be called'); + t.notOk(loadTile.calledOnce, 'loadTile must not be called'); + t.equal(sourceCache._tiles[tileID.key].state, 'loaded', 'tile must be in the loaded state'); + t.end(); + }); + t.test('prepareTile updates the tile data if it returns valid tile data', (t) => { const tileID = new OverscaledTileID(0, 0, 0, 0, 0);