Skip to content

Commit f6d2cde

Browse files
Cherry picking CustomSource fix to release v2.8.0 (#11677)
* fix: force loaded state for CustomSource tiles (#11674) * improve debug page for the CustomSource (#11675) Co-authored-by: Stepan Kuzmin <[email protected]>
1 parent f290a45 commit f6d2cde

File tree

5 files changed

+83
-21
lines changed

5 files changed

+83
-21
lines changed

debug/custom-source.html

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,88 @@
88
<style>
99
body { margin: 0; padding: 0; }
1010
html, body, #map { height: 100%; }
11+
#controls { position: absolute; top: 0; left: 0; }
1112
</style>
1213
</head>
1314

1415
<body>
1516
<div id='map'></div>
17+
<div id='controls'>
18+
<input id="slider" type="range" list="colors" min="0" step="1">
19+
<datalist id="colors"></datalist>
20+
</div>
1621

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

2126
var map = window.map = new mapboxgl.Map({
2227
container: 'map',
23-
center: [-74.5, 40],
24-
zoom: 2,
25-
style: {
26-
version: 8,
27-
sources: {},
28-
layers: []
29-
},
28+
center: [0, 0],
29+
zoom: 0,
30+
style: 'mapbox://styles/mapbox/light-v10',
3031
hash: true
3132
});
3233

34+
const tileSize = 256;
35+
const colors = ['#74a9cf', '#3690c0', '#0570b0', '#045a8d'];
36+
let currentColor = colors[0];
37+
3338
map.on('load', () => {
3439
map.addSource('custom-source', {
3540
type: 'custom',
36-
tileSize: 256,
37-
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>',
38-
async loadTile(tile, {signal}) {
39-
const tilesUrl = 'https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg';
40-
const url = tilesUrl
41-
.replace('{z}', String(tile.z))
42-
.replace('{x}', String(tile.x))
43-
.replace('{y}', String(tile.y));
41+
tileSize,
42+
async loadTile({z, x, y}) {
43+
const tileSize = 256;
44+
const offscreenCanvas = new OffscreenCanvas(tileSize, tileSize);
45+
const context = offscreenCanvas.getContext('2d');
46+
context.fillStyle = 'red';
47+
context.fillRect(0, 0, tileSize, tileSize);
48+
49+
context.font = '18px serif';
50+
context.fillStyle = 'white';
51+
context.textAlign = 'center';
52+
context.fillText(`${z}/${x}/${y}`, tileSize / 2, tileSize / 2, tileSize);
4453

45-
const response = await fetch(url, {signal});
46-
const data = await response.arrayBuffer();
54+
const imageData = context.getImageData(0, 0, tileSize, tileSize);
55+
return imageData;
56+
},
57+
prepareTile({z, x, y}) {
58+
const tileSize = 256;
59+
const offscreenCanvas = new OffscreenCanvas(tileSize, tileSize);
60+
const context = offscreenCanvas.getContext('2d');
61+
context.fillStyle = currentColor;
62+
context.fillRect(0, 0, tileSize, tileSize);
4763

48-
const blob = new window.Blob([new Uint8Array(data)], {type: 'image/png'});
49-
const imageBitmap = await window.createImageBitmap(blob);
64+
context.font = '18px serif';
65+
context.fillStyle = 'white';
66+
context.textAlign = 'center';
67+
context.fillText(`${z}/${x}/${y}`, tileSize / 2, tileSize / 2, tileSize);
5068

51-
return imageBitmap;
69+
const imageData = context.getImageData(0, 0, tileSize, tileSize);
70+
return imageData;
71+
},
72+
hasTile({z, x, y}) {
73+
return (x + y) % 2 === 0;
5274
}
5375
});
5476

5577
map.addLayer({
5678
id: 'custom-source',
5779
type: 'raster',
58-
source: 'custom-source'
80+
source: 'custom-source',
81+
paint: {
82+
'raster-opacity': 0.75,
83+
'raster-fade-duration': 0
84+
}
85+
});
86+
87+
const slider = document.getElementById('slider');
88+
slider.value = 0;
89+
slider.max = colors.length - 1;
90+
slider.addEventListener('input', (e) => {
91+
currentColor = colors[e.target.value];
92+
map.triggerRepaint();
5993
});
6094
});
6195

src/source/custom_source.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ class CustomSource<T> extends Evented implements Source {
326326
if (!data) return null;
327327

328328
this.loadTileData(tile, data);
329+
tile.state = 'loaded';
329330
return data;
330331
}
331332

test/release/custom-source.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../debug/custom-source.html

test/release/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ const pages = [
101101
"key": "preload-tiles",
102102
"title": "Preload tiles",
103103
"url": "./preload-tiles.html"
104+
},
105+
{
106+
"key": "custom-source",
107+
"title": "Custom Source",
108+
"url": "./custom-source.html"
104109
}
105110
];
106111

test/unit/source/custom_source.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,27 @@ test('CustomSource', (t) => {
202202
sourceCache._addTile(tileID);
203203
});
204204

205+
t.test('loadTile is not called if prepareTile returns data', (t) => {
206+
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
207+
208+
const loadTile = t.spy();
209+
const prepareTile = t.spy((tile) => {
210+
const {x, y, z} = tileID.canonical;
211+
t.deepEqual(tile, {x, y, z});
212+
return new window.ImageData(512, 512);
213+
});
214+
215+
const {sourceCache} = createSource(t, {loadTile, prepareTile});
216+
217+
sourceCache.onAdd();
218+
sourceCache._addTile(tileID);
219+
220+
t.ok(prepareTile.calledOnce, 'prepareTile must be called');
221+
t.notOk(loadTile.calledOnce, 'loadTile must not be called');
222+
t.equal(sourceCache._tiles[tileID.key].state, 'loaded', 'tile must be in the loaded state');
223+
t.end();
224+
});
225+
205226
t.test('prepareTile updates the tile data if it returns valid tile data', (t) => {
206227
const tileID = new OverscaledTileID(0, 0, 0, 0, 0);
207228

0 commit comments

Comments
 (0)