maplibre-gl 3.3.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/maplibre-gl-csp-worker.js +1 -1
- package/dist/maplibre-gl-csp-worker.js.map +1 -1
- package/dist/maplibre-gl-csp.js +1 -1
- package/dist/maplibre-gl-csp.js.map +1 -1
- package/dist/maplibre-gl-dev.js +430 -128
- package/dist/maplibre-gl-dev.js.map +1 -1
- package/dist/maplibre-gl.d.ts +18 -9
- package/dist/maplibre-gl.js +4 -4
- package/dist/maplibre-gl.js.map +1 -1
- package/package.json +41 -41
- package/src/data/dem_data.test.ts +120 -165
- package/src/data/dem_data.ts +38 -18
- package/src/render/glyph_manager.test.ts +10 -9
- package/src/render/glyph_manager.ts +17 -10
- package/src/source/image_source.test.ts +17 -24
- package/src/source/raster_dem_tile_source.ts +36 -11
- package/src/source/raster_dem_tile_worker_source.ts +9 -26
- package/src/source/raster_tile_source.test.ts +1 -1
- package/src/source/raster_tile_source.ts +1 -1
- package/src/source/terrain_source_cache.test.ts +1 -1
- package/src/source/vector_tile_source.test.ts +1 -1
- package/src/source/vector_tile_worker_source.test.ts +45 -1
- package/src/source/vector_tile_worker_source.ts +19 -6
- package/src/source/worker_source.ts +6 -2
- package/src/style/load_glyph_range.test.ts +6 -8
- package/src/style/load_sprite.test.ts +48 -71
- package/src/style/style.test.ts +19 -49
- package/src/style/style.ts +3 -0
- package/src/style/style_glyph.ts +4 -3
- package/src/style/style_layer/line_style_layer.test.ts +50 -0
- package/src/style/style_layer/line_style_layer.ts +8 -4
- package/src/symbol/quads.ts +4 -2
- package/src/ui/handler/scroll_zoom.ts +6 -0
- package/src/ui/handler_manager.ts +2 -1
- package/src/ui/map.test.ts +17 -0
- package/src/ui/map.ts +1 -0
- package/src/ui/marker.test.ts +25 -0
- package/src/ui/marker.ts +8 -1
- package/src/util/ajax.test.ts +1 -1
- package/src/util/image_request.test.ts +1 -1
- package/src/util/offscreen_canvas_distorted.test.ts +13 -0
- package/src/util/offscreen_canvas_distorted.ts +39 -0
- package/src/util/test/util.ts +12 -0
- package/src/util/util.test.ts +171 -1
- package/src/util/util.ts +150 -0
|
@@ -180,6 +180,10 @@ export class GlyphManager {
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// Client-generated glyphs are rendered at 2x texture scale,
|
|
184
|
+
// because CJK glyphs are more detailed than others.
|
|
185
|
+
const textureScale = 2;
|
|
186
|
+
|
|
183
187
|
let tinySDF = entry.tinySDF;
|
|
184
188
|
if (!tinySDF) {
|
|
185
189
|
let fontWeight = '400';
|
|
@@ -191,9 +195,9 @@ export class GlyphManager {
|
|
|
191
195
|
fontWeight = '200';
|
|
192
196
|
}
|
|
193
197
|
tinySDF = entry.tinySDF = new GlyphManager.TinySDF({
|
|
194
|
-
fontSize: 24,
|
|
195
|
-
buffer: 3,
|
|
196
|
-
radius: 8,
|
|
198
|
+
fontSize: 24 * textureScale,
|
|
199
|
+
buffer: 3 * textureScale,
|
|
200
|
+
radius: 8 * textureScale,
|
|
197
201
|
cutoff: 0.25,
|
|
198
202
|
fontFamily,
|
|
199
203
|
fontWeight
|
|
@@ -215,17 +219,20 @@ export class GlyphManager {
|
|
|
215
219
|
* To approximately align TinySDF glyphs with server-provided glyphs, we use this baseline adjustment
|
|
216
220
|
* factor calibrated to be in between DIN Pro and Arial Unicode (but closer to Arial Unicode)
|
|
217
221
|
*/
|
|
218
|
-
const topAdjustment = 27;
|
|
222
|
+
const topAdjustment = 27.5;
|
|
223
|
+
|
|
224
|
+
const leftAdjustment = 0.5;
|
|
219
225
|
|
|
220
226
|
return {
|
|
221
227
|
id,
|
|
222
|
-
bitmap: new AlphaImage({width: char.width || 30, height: char.height || 30}, char.data),
|
|
228
|
+
bitmap: new AlphaImage({width: char.width || 30 * textureScale, height: char.height || 30 * textureScale}, char.data),
|
|
223
229
|
metrics: {
|
|
224
|
-
width: char.glyphWidth || 24,
|
|
225
|
-
height: char.glyphHeight || 24,
|
|
226
|
-
left: char.glyphLeft || 0,
|
|
227
|
-
top: char.glyphTop - topAdjustment || -8,
|
|
228
|
-
advance: char.glyphAdvance || 24
|
|
230
|
+
width: char.glyphWidth / textureScale || 24,
|
|
231
|
+
height: char.glyphHeight / textureScale || 24,
|
|
232
|
+
left: (char.glyphLeft / textureScale + leftAdjustment) || 0,
|
|
233
|
+
top: char.glyphTop / textureScale - topAdjustment || -8,
|
|
234
|
+
advance: char.glyphAdvance / textureScale || 24,
|
|
235
|
+
isDoubleResolution: true
|
|
229
236
|
}
|
|
230
237
|
};
|
|
231
238
|
}
|
|
@@ -2,7 +2,7 @@ import {ImageSource} from './image_source';
|
|
|
2
2
|
import {Evented} from '../util/evented';
|
|
3
3
|
import {Transform} from '../geo/transform';
|
|
4
4
|
import {extend} from '../util/util';
|
|
5
|
-
import {
|
|
5
|
+
import {type FakeServer, fakeServer} from 'nise';
|
|
6
6
|
import {RequestManager} from '../util/request_manager';
|
|
7
7
|
import {Dispatcher} from '../util/dispatcher';
|
|
8
8
|
import {stubAjaxGetImage} from '../util/test/util';
|
|
@@ -44,20 +44,15 @@ class StubMap extends Evented {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
describe('ImageSource', () => {
|
|
47
|
-
const requests = [];
|
|
48
|
-
fakeXhr.useFakeXMLHttpRequest().onCreate = (req) => { requests.push(req); };
|
|
49
47
|
stubAjaxGetImage(undefined);
|
|
48
|
+
let server: FakeServer;
|
|
49
|
+
|
|
50
50
|
beforeEach(() => {
|
|
51
51
|
global.fetch = null;
|
|
52
|
+
server = fakeServer.create();
|
|
53
|
+
server.respondWith(new ArrayBuffer(1));
|
|
52
54
|
});
|
|
53
55
|
|
|
54
|
-
const respond = () => {
|
|
55
|
-
const req = requests.shift();
|
|
56
|
-
req.setStatus(200);
|
|
57
|
-
req.response = new ArrayBuffer(1);
|
|
58
|
-
req.onload();
|
|
59
|
-
};
|
|
60
|
-
|
|
61
56
|
test('constructor', () => {
|
|
62
57
|
const source = createSource({url: '/image.png'});
|
|
63
58
|
|
|
@@ -72,7 +67,7 @@ describe('ImageSource', () => {
|
|
|
72
67
|
expect(e.dataType).toBe('source');
|
|
73
68
|
});
|
|
74
69
|
source.onAdd(new StubMap() as any);
|
|
75
|
-
respond();
|
|
70
|
+
server.respond();
|
|
76
71
|
expect(source.image).toBeTruthy();
|
|
77
72
|
});
|
|
78
73
|
|
|
@@ -81,7 +76,7 @@ describe('ImageSource', () => {
|
|
|
81
76
|
const map = new StubMap() as any;
|
|
82
77
|
const spy = jest.spyOn(map._requestManager, 'transformRequest');
|
|
83
78
|
source.onAdd(map);
|
|
84
|
-
respond();
|
|
79
|
+
server.respond();
|
|
85
80
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
86
81
|
expect(spy.mock.calls[0][0]).toBe('/image.png');
|
|
87
82
|
expect(spy.mock.calls[0][1]).toBe('Image');
|
|
@@ -92,12 +87,12 @@ describe('ImageSource', () => {
|
|
|
92
87
|
const map = new StubMap() as any;
|
|
93
88
|
const spy = jest.spyOn(map._requestManager, 'transformRequest');
|
|
94
89
|
source.onAdd(map);
|
|
95
|
-
respond();
|
|
90
|
+
server.respond();
|
|
96
91
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
97
92
|
expect(spy.mock.calls[0][0]).toBe('/image.png');
|
|
98
93
|
expect(spy.mock.calls[0][1]).toBe('Image');
|
|
99
94
|
source.updateImage({url: '/image2.png'});
|
|
100
|
-
respond();
|
|
95
|
+
server.respond();
|
|
101
96
|
expect(spy).toHaveBeenCalledTimes(2);
|
|
102
97
|
expect(spy.mock.calls[1][0]).toBe('/image2.png');
|
|
103
98
|
expect(spy.mock.calls[1][1]).toBe('Image');
|
|
@@ -107,7 +102,7 @@ describe('ImageSource', () => {
|
|
|
107
102
|
const source = createSource({url: '/image.png'});
|
|
108
103
|
const map = new StubMap() as any;
|
|
109
104
|
source.onAdd(map);
|
|
110
|
-
respond();
|
|
105
|
+
server.respond();
|
|
111
106
|
const beforeSerialized = source.serialize();
|
|
112
107
|
expect(beforeSerialized.coordinates).toEqual([[0, 0], [1, 0], [1, 1], [0, 1]]);
|
|
113
108
|
source.setCoordinates([[0, 0], [-1, 0], [-1, -1], [0, -1]]);
|
|
@@ -119,14 +114,14 @@ describe('ImageSource', () => {
|
|
|
119
114
|
const source = createSource({url: '/image.png'});
|
|
120
115
|
const map = new StubMap() as any;
|
|
121
116
|
source.onAdd(map);
|
|
122
|
-
respond();
|
|
117
|
+
server.respond();
|
|
123
118
|
const beforeSerialized = source.serialize();
|
|
124
119
|
expect(beforeSerialized.coordinates).toEqual([[0, 0], [1, 0], [1, 1], [0, 1]]);
|
|
125
120
|
source.updateImage({
|
|
126
121
|
url: '/image2.png',
|
|
127
122
|
coordinates: [[0, 0], [-1, 0], [-1, -1], [0, -1]]
|
|
128
123
|
});
|
|
129
|
-
respond();
|
|
124
|
+
server.respond();
|
|
130
125
|
const afterSerialized = source.serialize();
|
|
131
126
|
expect(afterSerialized.coordinates).toEqual([[0, 0], [-1, 0], [-1, -1], [0, -1]]);
|
|
132
127
|
});
|
|
@@ -140,7 +135,7 @@ describe('ImageSource', () => {
|
|
|
140
135
|
}
|
|
141
136
|
});
|
|
142
137
|
source.onAdd(new StubMap() as any);
|
|
143
|
-
respond();
|
|
138
|
+
server.respond();
|
|
144
139
|
});
|
|
145
140
|
|
|
146
141
|
test('fires data event when metadata is loaded', done => {
|
|
@@ -151,7 +146,7 @@ describe('ImageSource', () => {
|
|
|
151
146
|
}
|
|
152
147
|
});
|
|
153
148
|
source.onAdd(new StubMap() as any);
|
|
154
|
-
respond();
|
|
149
|
+
server.respond();
|
|
155
150
|
});
|
|
156
151
|
|
|
157
152
|
test('fires idle event on prepare call when there is at least one not loaded tile', done => {
|
|
@@ -164,7 +159,7 @@ describe('ImageSource', () => {
|
|
|
164
159
|
}
|
|
165
160
|
});
|
|
166
161
|
source.onAdd(new StubMap() as any);
|
|
167
|
-
respond();
|
|
162
|
+
server.respond();
|
|
168
163
|
|
|
169
164
|
source.tiles[String(tile.tileID.wrap)] = tile;
|
|
170
165
|
source.image = new ImageBitmap();
|
|
@@ -190,10 +185,9 @@ describe('ImageSource', () => {
|
|
|
190
185
|
|
|
191
186
|
source.onAdd(map);
|
|
192
187
|
|
|
193
|
-
requests.shift();
|
|
194
188
|
expect(source.image).toBeUndefined();
|
|
195
189
|
source.updateImage({url: '/image2.png'});
|
|
196
|
-
respond();
|
|
190
|
+
server.respond();
|
|
197
191
|
expect(source.image).toBeTruthy();
|
|
198
192
|
});
|
|
199
193
|
|
|
@@ -203,8 +197,7 @@ describe('ImageSource', () => {
|
|
|
203
197
|
|
|
204
198
|
source.onAdd(map);
|
|
205
199
|
|
|
206
|
-
const
|
|
207
|
-
const spy = jest.spyOn(request, 'abort');
|
|
200
|
+
const spy = jest.spyOn(server.requests[0] as any, 'abort');
|
|
208
201
|
|
|
209
202
|
source.updateImage({url: '/image2.png'});
|
|
210
203
|
expect(spy).toHaveBeenCalled();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ImageRequest} from '../util/image_request';
|
|
2
2
|
import {ResourceType} from '../util/request_manager';
|
|
3
|
-
import {extend, isImageBitmap} from '../util/util';
|
|
3
|
+
import {extend, isImageBitmap, readImageUsingVideoFrame} from '../util/util';
|
|
4
4
|
import {Evented} from '../util/evented';
|
|
5
5
|
import {browser} from '../util/browser';
|
|
6
6
|
import {offscreenCanvasSupported} from '../util/offscreen_canvas_supported';
|
|
@@ -8,6 +8,7 @@ import {OverscaledTileID} from './tile_id';
|
|
|
8
8
|
import {RasterTileSource} from './raster_tile_source';
|
|
9
9
|
// ensure DEMData is registered for worker transfer on main thread:
|
|
10
10
|
import '../data/dem_data';
|
|
11
|
+
import type {DEMEncoding} from '../data/dem_data';
|
|
11
12
|
|
|
12
13
|
import type {Source} from './source';
|
|
13
14
|
import type {Dispatcher} from '../util/dispatcher';
|
|
@@ -15,6 +16,8 @@ import type {Tile} from './tile';
|
|
|
15
16
|
import type {Callback} from '../types/callback';
|
|
16
17
|
import type {RasterDEMSourceSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
17
18
|
import type {ExpiryData} from '../util/ajax';
|
|
19
|
+
import {isOffscreenCanvasDistorted} from '../util/offscreen_canvas_distorted';
|
|
20
|
+
import {RGBAImage} from '../util/image';
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* A source containing raster DEM tiles (See the [Style Specification](https://maplibre.org/maplibre-style-spec/) for detailed documentation of options.)
|
|
@@ -33,7 +36,11 @@ import type {ExpiryData} from '../util/ajax';
|
|
|
33
36
|
* @see [3D Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/3d-terrain/)
|
|
34
37
|
*/
|
|
35
38
|
export class RasterDEMTileSource extends RasterTileSource implements Source {
|
|
36
|
-
encoding:
|
|
39
|
+
encoding: DEMEncoding;
|
|
40
|
+
redFactor?: number;
|
|
41
|
+
greenFactor?: number;
|
|
42
|
+
blueFactor?: number;
|
|
43
|
+
baseShift?: number;
|
|
37
44
|
|
|
38
45
|
constructor(id: string, options: RasterDEMSourceSpecification, dispatcher: Dispatcher, eventedParent: Evented) {
|
|
39
46
|
super(id, options, dispatcher, eventedParent);
|
|
@@ -41,14 +48,17 @@ export class RasterDEMTileSource extends RasterTileSource implements Source {
|
|
|
41
48
|
this.maxzoom = 22;
|
|
42
49
|
this._options = extend({type: 'raster-dem'}, options);
|
|
43
50
|
this.encoding = options.encoding || 'mapbox';
|
|
51
|
+
this.redFactor = options.redFactor;
|
|
52
|
+
this.greenFactor = options.greenFactor;
|
|
53
|
+
this.blueFactor = options.blueFactor;
|
|
54
|
+
this.baseShift = options.baseShift;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
loadTile(tile: Tile, callback: Callback<void>) {
|
|
47
58
|
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
const request = this.map._requestManager.transformRequest(url, ResourceType.Tile);
|
|
50
60
|
tile.neighboringTiles = this._getNeighboringTiles(tile.tileID);
|
|
51
|
-
|
|
61
|
+
tile.request = ImageRequest.getImage(request, async (err: Error, img: (HTMLImageElement | ImageBitmap), expiry: ExpiryData) => {
|
|
52
62
|
delete tile.request;
|
|
53
63
|
if (tile.aborted) {
|
|
54
64
|
tile.state = 'unloaded';
|
|
@@ -57,24 +67,39 @@ export class RasterDEMTileSource extends RasterTileSource implements Source {
|
|
|
57
67
|
tile.state = 'errored';
|
|
58
68
|
callback(err);
|
|
59
69
|
} else if (img) {
|
|
60
|
-
if (this.map._refreshExpiredTiles) tile.setExpiryData(
|
|
61
|
-
delete img.cacheControl;
|
|
62
|
-
delete img.expires;
|
|
70
|
+
if (this.map._refreshExpiredTiles) tile.setExpiryData(expiry);
|
|
63
71
|
const transfer = isImageBitmap(img) && offscreenCanvasSupported();
|
|
64
|
-
const rawImageData = transfer ? img :
|
|
72
|
+
const rawImageData = transfer ? img : await readImageNow(img);
|
|
65
73
|
const params = {
|
|
66
74
|
uid: tile.uid,
|
|
67
75
|
coord: tile.tileID,
|
|
68
76
|
source: this.id,
|
|
69
77
|
rawImageData,
|
|
70
|
-
encoding: this.encoding
|
|
78
|
+
encoding: this.encoding,
|
|
79
|
+
redFactor: this.redFactor,
|
|
80
|
+
greenFactor: this.greenFactor,
|
|
81
|
+
blueFactor: this.blueFactor,
|
|
82
|
+
baseShift: this.baseShift
|
|
71
83
|
};
|
|
72
84
|
|
|
73
85
|
if (!tile.actor || tile.state === 'expired') {
|
|
74
86
|
tile.actor = this.dispatcher.getActor();
|
|
75
|
-
tile.actor.send('loadDEMTile', params, done
|
|
87
|
+
tile.actor.send('loadDEMTile', params, done);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, this.map._refreshExpiredTiles);
|
|
91
|
+
|
|
92
|
+
async function readImageNow(img: ImageBitmap | HTMLImageElement): Promise<RGBAImage | ImageData> {
|
|
93
|
+
if (typeof VideoFrame !== 'undefined' && isOffscreenCanvasDistorted()) {
|
|
94
|
+
const width = img.width + 2;
|
|
95
|
+
const height = img.height + 2;
|
|
96
|
+
try {
|
|
97
|
+
return new RGBAImage({width, height}, await readImageUsingVideoFrame(img, -1, -1, width, height));
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// fall-back to browser canvas decoding
|
|
76
100
|
}
|
|
77
101
|
}
|
|
102
|
+
return browser.getImageData(img, 1);
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
function done(err, data) {
|
|
@@ -6,46 +6,29 @@ import type {
|
|
|
6
6
|
WorkerDEMTileCallback,
|
|
7
7
|
TileParameters
|
|
8
8
|
} from './worker_source';
|
|
9
|
-
import {isImageBitmap} from '../util/util';
|
|
9
|
+
import {getImageData, isImageBitmap} from '../util/util';
|
|
10
10
|
|
|
11
11
|
export class RasterDEMTileWorkerSource {
|
|
12
12
|
actor: Actor;
|
|
13
13
|
loaded: {[_: string]: DEMData};
|
|
14
|
-
offscreenCanvas: OffscreenCanvas;
|
|
15
|
-
offscreenCanvasContext: OffscreenCanvasRenderingContext2D;
|
|
16
14
|
|
|
17
15
|
constructor() {
|
|
18
16
|
this.loaded = {};
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
loadTile(params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
|
|
22
|
-
const {uid, encoding, rawImageData} = params;
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
const
|
|
19
|
+
async loadTile(params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
|
|
20
|
+
const {uid, encoding, rawImageData, redFactor, greenFactor, blueFactor, baseShift} = params;
|
|
21
|
+
const width = rawImageData.width + 2;
|
|
22
|
+
const height = rawImageData.height + 2;
|
|
23
|
+
const imagePixels: RGBAImage = isImageBitmap(rawImageData) ?
|
|
24
|
+
new RGBAImage({width, height}, await getImageData(rawImageData, -1, -1, width, height)) :
|
|
25
|
+
rawImageData;
|
|
26
|
+
const dem = new DEMData(uid, imagePixels, encoding, redFactor, greenFactor, blueFactor, baseShift);
|
|
26
27
|
this.loaded = this.loaded || {};
|
|
27
28
|
this.loaded[uid] = dem;
|
|
28
29
|
callback(null, dem);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
getImageData(imgBitmap: ImageBitmap): RGBAImage {
|
|
32
|
-
// Lazily initialize OffscreenCanvas
|
|
33
|
-
if (!this.offscreenCanvas || !this.offscreenCanvasContext) {
|
|
34
|
-
// Dem tiles are typically 256x256
|
|
35
|
-
this.offscreenCanvas = new OffscreenCanvas(imgBitmap.width, imgBitmap.height);
|
|
36
|
-
this.offscreenCanvasContext = this.offscreenCanvas.getContext('2d', {willReadFrequently: true});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
this.offscreenCanvas.width = imgBitmap.width;
|
|
40
|
-
this.offscreenCanvas.height = imgBitmap.height;
|
|
41
|
-
|
|
42
|
-
this.offscreenCanvasContext.drawImage(imgBitmap, 0, 0, imgBitmap.width, imgBitmap.height);
|
|
43
|
-
// Insert an additional 1px padding around the image to allow backfilling for neighboring data.
|
|
44
|
-
const imgData = this.offscreenCanvasContext.getImageData(-1, -1, imgBitmap.width + 2, imgBitmap.height + 2);
|
|
45
|
-
this.offscreenCanvasContext.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
|
|
46
|
-
return new RGBAImage({width: imgData.width, height: imgData.height}, imgData.data);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
32
|
removeTile(params: TileParameters) {
|
|
50
33
|
const loaded = this.loaded,
|
|
51
34
|
uid = params.uid;
|
|
@@ -2,7 +2,7 @@ import {RasterTileSource} from './raster_tile_source';
|
|
|
2
2
|
import {OverscaledTileID} from './tile_id';
|
|
3
3
|
import {RequestManager} from '../util/request_manager';
|
|
4
4
|
import {Dispatcher} from '../util/dispatcher';
|
|
5
|
-
import {fakeServer, FakeServer} from 'nise';
|
|
5
|
+
import {fakeServer, type FakeServer} from 'nise';
|
|
6
6
|
import {Tile} from './tile';
|
|
7
7
|
import {stubAjaxGetImage} from '../util/test/util';
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
* ```ts
|
|
30
30
|
* map.addSource('raster-source', {
|
|
31
31
|
* 'type': 'raster',
|
|
32
|
-
* 'tiles': ['https://
|
|
32
|
+
* 'tiles': ['https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg'],
|
|
33
33
|
* 'tileSize': 256,
|
|
34
34
|
* });
|
|
35
35
|
* ```
|
|
@@ -2,7 +2,7 @@ import {TerrainSourceCache} from './terrain_source_cache';
|
|
|
2
2
|
import {Style} from '../style/style';
|
|
3
3
|
import {RequestManager} from '../util/request_manager';
|
|
4
4
|
import {Dispatcher} from '../util/dispatcher';
|
|
5
|
-
import {fakeServer, FakeServer} from 'nise';
|
|
5
|
+
import {fakeServer, type FakeServer} from 'nise';
|
|
6
6
|
import {Transform} from '../geo/transform';
|
|
7
7
|
import {Evented} from '../util/evented';
|
|
8
8
|
import {Painter} from '../render/painter';
|
|
@@ -4,7 +4,7 @@ import vt from '@mapbox/vector-tile';
|
|
|
4
4
|
import Protobuf from 'pbf';
|
|
5
5
|
import {VectorTileWorkerSource} from '../source/vector_tile_worker_source';
|
|
6
6
|
import {StyleLayerIndex} from '../style/style_layer_index';
|
|
7
|
-
import {fakeServer, FakeServer} from 'nise';
|
|
7
|
+
import {fakeServer, type FakeServer} from 'nise';
|
|
8
8
|
import {Actor} from '../util/actor';
|
|
9
9
|
import {TileParameters, WorkerTileParameters, WorkerTileResult} from './worker_source';
|
|
10
10
|
import {WorkerTile} from './worker_tile';
|
|
@@ -234,6 +234,50 @@ describe('vector tile worker source', () => {
|
|
|
234
234
|
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
+
test('VectorTileWorkerSource#returns a good error message when failing to parse a tile', () => {
|
|
238
|
+
const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
|
239
|
+
const parse = jest.fn();
|
|
240
|
+
const callback = jest.fn();
|
|
241
|
+
|
|
242
|
+
server.respondWith(request => {
|
|
243
|
+
request.respond(200, {'Content-Type': 'application/pbf'}, 'something...');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
source.loadTile({
|
|
247
|
+
source: 'source',
|
|
248
|
+
uid: 0,
|
|
249
|
+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
|
|
250
|
+
request: {url: 'http://localhost:2900/faketile.pbf'}
|
|
251
|
+
} as any as WorkerTileParameters, callback);
|
|
252
|
+
|
|
253
|
+
server.respond();
|
|
254
|
+
|
|
255
|
+
expect(parse).not.toHaveBeenCalled();
|
|
256
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(callback.mock.calls[0][0].message).toContain('Unable to parse the tile at');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('VectorTileWorkerSource#returns a good error message when failing to parse a gzipped tile', () => {
|
|
261
|
+
const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
|
262
|
+
const parse = jest.fn();
|
|
263
|
+
const callback = jest.fn();
|
|
264
|
+
|
|
265
|
+
server.respondWith(new Uint8Array([0x1f, 0x8b]).buffer);
|
|
266
|
+
|
|
267
|
+
source.loadTile({
|
|
268
|
+
source: 'source',
|
|
269
|
+
uid: 0,
|
|
270
|
+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
|
|
271
|
+
request: {url: 'http://localhost:2900/faketile.pbf'}
|
|
272
|
+
} as any as WorkerTileParameters, callback);
|
|
273
|
+
|
|
274
|
+
server.respond();
|
|
275
|
+
|
|
276
|
+
expect(parse).not.toHaveBeenCalled();
|
|
277
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(callback.mock.calls[0][0].message).toContain('gzipped');
|
|
279
|
+
});
|
|
280
|
+
|
|
237
281
|
test('VectorTileWorkerSource provides resource timing information', done => {
|
|
238
282
|
const rawTileData = fs.readFileSync(path.join(__dirname, '/../../test/unit/assets/mbsv5-6-18-23.vector.pbf'));
|
|
239
283
|
|
|
@@ -46,12 +46,25 @@ function loadVectorTile(params: WorkerTileParameters, callback: LoadVectorDataCa
|
|
|
46
46
|
if (err) {
|
|
47
47
|
callback(err);
|
|
48
48
|
} else if (data) {
|
|
49
|
-
|
|
50
|
-
vectorTile
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
try {
|
|
50
|
+
const vectorTile = new vt.VectorTile(new Protobuf(data));
|
|
51
|
+
callback(null, {
|
|
52
|
+
vectorTile,
|
|
53
|
+
rawData: data,
|
|
54
|
+
cacheControl,
|
|
55
|
+
expires
|
|
56
|
+
});
|
|
57
|
+
} catch (ex) {
|
|
58
|
+
const bytes = new Uint8Array(data);
|
|
59
|
+
const isGzipped = bytes[0] === 0x1f && bytes[1] === 0x8b;
|
|
60
|
+
let errorMessage = `Unable to parse the tile at ${params.request.url}, `;
|
|
61
|
+
if (isGzipped) {
|
|
62
|
+
errorMessage += 'please make sure the data is not gzipped and that you have configured the relevant header in the server';
|
|
63
|
+
} else {
|
|
64
|
+
errorMessage += `got error: ${ex.messge}`;
|
|
65
|
+
}
|
|
66
|
+
callback(new Error(errorMessage));
|
|
67
|
+
}
|
|
55
68
|
}
|
|
56
69
|
});
|
|
57
70
|
return () => {
|
|
@@ -6,7 +6,7 @@ import type {OverscaledTileID} from './tile_id';
|
|
|
6
6
|
import type {Bucket} from '../data/bucket';
|
|
7
7
|
import type {FeatureIndex} from '../data/feature_index';
|
|
8
8
|
import type {CollisionBoxArray} from '../data/array_types.g';
|
|
9
|
-
import type {DEMData} from '../data/dem_data';
|
|
9
|
+
import type {DEMData, DEMEncoding} from '../data/dem_data';
|
|
10
10
|
import type {StyleGlyph} from '../style/style_glyph';
|
|
11
11
|
import type {StyleImage} from '../style/style_image';
|
|
12
12
|
import type {PromoteIdSpecification} from '@maplibre/maplibre-gl-style-spec';
|
|
@@ -37,7 +37,11 @@ export type WorkerDEMTileParameters = TileParameters & {
|
|
|
37
37
|
w: number;
|
|
38
38
|
};
|
|
39
39
|
rawImageData: RGBAImage | ImageBitmap;
|
|
40
|
-
encoding:
|
|
40
|
+
encoding: DEMEncoding;
|
|
41
|
+
redFactor: number;
|
|
42
|
+
greenFactor: number;
|
|
43
|
+
blueFactor: number;
|
|
44
|
+
baseShift: number;
|
|
41
45
|
};
|
|
42
46
|
|
|
43
47
|
/**
|
|
@@ -2,7 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import {RequestManager} from '../util/request_manager';
|
|
4
4
|
import {loadGlyphRange} from './load_glyph_range';
|
|
5
|
-
import {
|
|
5
|
+
import {fakeServer} from 'nise';
|
|
6
|
+
import {bufferToArrayBuffer} from '../util/test/util';
|
|
6
7
|
|
|
7
8
|
test('loadGlyphRange', done => {
|
|
8
9
|
global.fetch = null;
|
|
@@ -13,8 +14,8 @@ test('loadGlyphRange', done => {
|
|
|
13
14
|
|
|
14
15
|
const manager = new RequestManager(transform);
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const server = fakeServer.create();
|
|
18
|
+
server.respondWith(bufferToArrayBuffer(fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf'))));
|
|
18
19
|
|
|
19
20
|
loadGlyphRange('Arial Unicode MS', 0, 'https://localhost/fonts/v1/{fontstack}/{range}.pbf', manager, (err, result) => {
|
|
20
21
|
expect(err).toBeFalsy();
|
|
@@ -35,9 +36,6 @@ test('loadGlyphRange', done => {
|
|
|
35
36
|
}
|
|
36
37
|
done();
|
|
37
38
|
});
|
|
38
|
-
|
|
39
|
-
expect(
|
|
40
|
-
request.setStatus(200);
|
|
41
|
-
request.response = fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf'));
|
|
42
|
-
request.onload();
|
|
39
|
+
server.respond();
|
|
40
|
+
expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf');
|
|
43
41
|
});
|