Skip to content

Commit

Permalink
Cherry picking CustomSource fix to release v2.8.0 (#11677)
Browse files Browse the repository at this point in the history
* fix: force loaded state for CustomSource tiles (#11674)

* improve debug page for the CustomSource (#11675)

Co-authored-by: Stepan Kuzmin <stepan.kuzmin@mapbox.com>
  • Loading branch information
SnailBones and stepankuzmin authored Apr 6, 2022
1 parent f290a45 commit f6d2cde
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 21 deletions.
76 changes: 55 additions & 21 deletions debug/custom-source.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,88 @@
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#controls { position: absolute; top: 0; left: 0; }
</style>
</head>

<body>
<div id='map'></div>
<div id='controls'>
<input id="slider" type="range" list="colors" min="0" step="1">
<datalist id="colors"></datalist>
</div>

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

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 <a target="_top" rel="noopener" href="http://stamen.com">Stamen Design</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>',
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();
});
});

Expand Down
1 change: 1 addition & 0 deletions src/source/custom_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ class CustomSource<T> extends Evented implements Source {
if (!data) return null;

this.loadTileData(tile, data);
tile.state = 'loaded';
return data;
}

Expand Down
1 change: 1 addition & 0 deletions test/release/custom-source.html
5 changes: 5 additions & 0 deletions test/release/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
];

Expand Down
21 changes: 21 additions & 0 deletions test/unit/source/custom_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit f6d2cde

Please sign in to comment.