maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
214 lines (188 loc) • 7.62 kB
text/typescript
import {ExpiryData, getArrayBuffer} from '../util/ajax';
import vt from '@mapbox/vector-tile';
import Protobuf from 'pbf';
import WorkerTile from './worker_tile';
import {extend} from '../util/util';
import {RequestPerformance} from '../util/performance';
import type {
WorkerSource,
WorkerTileParameters,
WorkerTileCallback,
TileParameters
} from '../source/worker_source';
import type Actor from '../util/actor';
import type StyleLayerIndex from '../style/style_layer_index';
import type {Callback} from '../types/callback';
import type {VectorTile} from '@mapbox/vector-tile';
export type LoadVectorTileResult = {
vectorTile: VectorTile;
rawData: ArrayBuffer;
resourceTiming?: Array<PerformanceResourceTiming>;
} & ExpiryData;
/**
* @callback LoadVectorDataCallback
* @param error
* @param vectorTile
* @private
*/
export type LoadVectorDataCallback = Callback<LoadVectorTileResult>;
export type AbortVectorData = () => void;
export type LoadVectorData = (params: WorkerTileParameters, callback: LoadVectorDataCallback) => AbortVectorData | void;
/**
* @private
*/
function loadVectorTile(params: WorkerTileParameters, callback: LoadVectorDataCallback) {
const request = getArrayBuffer(params.request, (err?: Error | null, data?: ArrayBuffer | null, cacheControl?: string | null, expires?: string | null) => {
if (err) {
callback(err);
} else if (data) {
callback(null, {
vectorTile: new vt.VectorTile(new Protobuf(data)),
rawData: data,
cacheControl,
expires
});
}
});
return () => {
request.cancel();
callback();
};
}
/**
* The {@link WorkerSource} implementation that supports {@link VectorTileSource}.
* This class is designed to be easily reused to support custom source types
* for data formats that can be parsed/converted into an in-memory VectorTile
* representation. To do so, create it with
* `new VectorTileWorkerSource(actor, styleLayers, customLoadVectorDataFunction)`.
*
* @private
*/
class VectorTileWorkerSource implements WorkerSource {
actor: Actor;
layerIndex: StyleLayerIndex;
availableImages: Array<string>;
loadVectorData: LoadVectorData;
loading: {[_: string]: WorkerTile};
loaded: {[_: string]: WorkerTile};
/**
* @param [loadVectorData] Optional method for custom loading of a VectorTile
* object based on parameters passed from the main-thread Source. See
* {@link VectorTileWorkerSource#loadTile}. The default implementation simply
* loads the pbf at `params.url`.
* @private
*/
constructor(actor: Actor, layerIndex: StyleLayerIndex, availableImages: Array<string>, loadVectorData?: LoadVectorData | null) {
this.actor = actor;
this.layerIndex = layerIndex;
this.availableImages = availableImages;
this.loadVectorData = loadVectorData || loadVectorTile;
this.loading = {};
this.loaded = {};
}
/**
* Implements {@link WorkerSource#loadTile}. Delegates to
* {@link VectorTileWorkerSource#loadVectorData} (which by default expects
* a `params.url` property) for fetching and producing a VectorTile object.
* @private
*/
loadTile(params: WorkerTileParameters, callback: WorkerTileCallback) {
const uid = params.uid;
if (!this.loading)
this.loading = {};
const perf = (params && params.request && params.request.collectResourceTiming) ?
new RequestPerformance(params.request) : false;
const workerTile = this.loading[uid] = new WorkerTile(params);
workerTile.abort = this.loadVectorData(params, (err, response) => {
delete this.loading[uid];
if (err || !response) {
workerTile.status = 'done';
this.loaded[uid] = workerTile;
return callback(err);
}
const rawTileData = response.rawData;
const cacheControl = {} as {expires: any; cacheControl: any};
if (response.expires) cacheControl.expires = response.expires;
if (response.cacheControl) cacheControl.cacheControl = response.cacheControl;
const resourceTiming = {} as {resourceTiming: any};
if (perf) {
const resourceTimingData = perf.finish();
// it's necessary to eval the result of getEntriesByName() here via parse/stringify
// late evaluation in the main thread causes TypeError: illegal invocation
if (resourceTimingData)
resourceTiming.resourceTiming = JSON.parse(JSON.stringify(resourceTimingData));
}
workerTile.vectorTile = response.vectorTile;
workerTile.parse(response.vectorTile, this.layerIndex, this.availableImages, this.actor, (err, result) => {
if (err || !result) return callback(err);
// Transferring a copy of rawTileData because the worker needs to retain its copy.
callback(null, extend({rawTileData: rawTileData.slice(0)}, result, cacheControl, resourceTiming));
});
this.loaded = this.loaded || {};
this.loaded[uid] = workerTile;
}) as AbortVectorData;
}
/**
* Implements {@link WorkerSource#reloadTile}.
* @private
*/
reloadTile(params: WorkerTileParameters, callback: WorkerTileCallback) {
const loaded = this.loaded,
uid = params.uid,
vtSource = this;
if (loaded && loaded[uid]) {
const workerTile = loaded[uid];
workerTile.showCollisionBoxes = params.showCollisionBoxes;
const done = (err?: Error, data?: any) => {
const reloadCallback = workerTile.reloadCallback;
if (reloadCallback) {
delete workerTile.reloadCallback;
workerTile.parse(workerTile.vectorTile, vtSource.layerIndex, this.availableImages, vtSource.actor, reloadCallback);
}
callback(err, data);
};
if (workerTile.status === 'parsing') {
workerTile.reloadCallback = done;
} else if (workerTile.status === 'done') {
// if there was no vector tile data on the initial load, don't try and re-parse tile
if (workerTile.vectorTile) {
workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, done);
} else {
done();
}
}
}
}
/**
* Implements {@link WorkerSource#abortTile}.
*
* @param params
* @param params.uid The UID for this tile.
* @private
*/
abortTile(params: TileParameters, callback: WorkerTileCallback) {
const loading = this.loading,
uid = params.uid;
if (loading && loading[uid] && loading[uid].abort) {
loading[uid].abort();
delete loading[uid];
}
callback();
}
/**
* Implements {@link WorkerSource#removeTile}.
*
* @param params
* @param params.uid The UID for this tile.
* @private
*/
removeTile(params: TileParameters, callback: WorkerTileCallback) {
const loaded = this.loaded,
uid = params.uid;
if (loaded && loaded[uid]) {
delete loaded[uid];
}
callback();
}
}
export default VectorTileWorkerSource;