maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
274 lines (235 loc) • 9.41 kB
text/typescript
import Actor from '../util/actor';
import StyleLayerIndex from '../style/style_layer_index';
import VectorTileWorkerSource from './vector_tile_worker_source';
import RasterDEMTileWorkerSource from './raster_dem_tile_worker_source';
import GeoJSONWorkerSource from './geojson_worker_source';
import assert from 'assert';
import {plugin as globalRTLTextPlugin} from './rtl_text_plugin';
import {enforceCacheSizeLimit} from '../util/tile_request_cache';
import {isWorker} from '../util/util';
import type {
WorkerSource,
WorkerTileParameters,
WorkerDEMTileParameters,
WorkerTileCallback,
WorkerDEMTileCallback,
TileParameters
} from '../source/worker_source';
import type {WorkerGlobalScopeInterface} from '../util/web_worker';
import type {Callback} from '../types/callback';
import type {LayerSpecification} from '../style-spec/types.g';
import type {PluginState} from './rtl_text_plugin';
/**
* @private
*/
export default class Worker {
self: WorkerGlobalScopeInterface;
actor: Actor;
layerIndexes: {[_: string]: StyleLayerIndex};
availableImages: {[_: string]: Array<string>};
workerSourceTypes: {
[_: string]: {
new (...args: any): WorkerSource;
};
};
workerSources: {
[_: string]: {
[_: string]: {
[_: string]: WorkerSource;
};
};
};
demWorkerSources: {
[_: string]: {
[_: string]: RasterDEMTileWorkerSource;
};
};
referrer: string;
constructor(self: WorkerGlobalScopeInterface) {
this.self = self;
this.actor = new Actor(self, this);
this.layerIndexes = {};
this.availableImages = {};
this.workerSourceTypes = {
vector: VectorTileWorkerSource,
geojson: GeoJSONWorkerSource
};
// [mapId][sourceType][sourceName] => worker source instance
this.workerSources = {};
this.demWorkerSources = {};
this.self.registerWorkerSource = (name: string, WorkerSource: {
new (...args: any): WorkerSource;
}) => {
if (this.workerSourceTypes[name]) {
throw new Error(`Worker source with name "${name}" already registered.`);
}
this.workerSourceTypes[name] = WorkerSource;
};
// This is invoked by the RTL text plugin when the download via the `importScripts` call has finished, and the code has been parsed.
this.self.registerRTLTextPlugin = (rtlTextPlugin: {
applyArabicShaping: Function;
processBidirectionalText: ((b: string, a: Array<number>) => Array<string>);
processStyledBidirectionalText?: ((c: string, b: Array<number>, a: Array<number>) => Array<[string, Array<number>]>);
}) => {
if (globalRTLTextPlugin.isParsed()) {
throw new Error('RTL text plugin already registered.');
}
globalRTLTextPlugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping;
globalRTLTextPlugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText;
globalRTLTextPlugin['processStyledBidirectionalText'] = rtlTextPlugin.processStyledBidirectionalText;
};
}
setReferrer(mapID: string, referrer: string) {
this.referrer = referrer;
}
setImages(mapId: string, images: Array<string>, callback: WorkerTileCallback) {
this.availableImages[mapId] = images;
for (const workerSource in this.workerSources[mapId]) {
const ws = this.workerSources[mapId][workerSource];
for (const source in ws) {
ws[source].availableImages = images;
}
}
callback();
}
setLayers(mapId: string, layers: Array<LayerSpecification>, callback: WorkerTileCallback) {
this.getLayerIndex(mapId).replace(layers);
callback();
}
updateLayers(mapId: string, params: {
layers: Array<LayerSpecification>;
removedIds: Array<string>;
}, callback: WorkerTileCallback) {
this.getLayerIndex(mapId).update(params.layers, params.removedIds);
callback();
}
loadTile(mapId: string, params: WorkerTileParameters & {
type: string;
}, callback: WorkerTileCallback) {
assert(params.type);
this.getWorkerSource(mapId, params.type, params.source).loadTile(params, callback);
}
loadDEMTile(mapId: string, params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
this.getDEMWorkerSource(mapId, params.source).loadTile(params, callback);
}
reloadTile(mapId: string, params: WorkerTileParameters & {
type: string;
}, callback: WorkerTileCallback) {
assert(params.type);
this.getWorkerSource(mapId, params.type, params.source).reloadTile(params, callback);
}
abortTile(mapId: string, params: TileParameters & {
type: string;
}, callback: WorkerTileCallback) {
assert(params.type);
this.getWorkerSource(mapId, params.type, params.source).abortTile(params, callback);
}
removeTile(mapId: string, params: TileParameters & {
type: string;
}, callback: WorkerTileCallback) {
assert(params.type);
this.getWorkerSource(mapId, params.type, params.source).removeTile(params, callback);
}
removeDEMTile(mapId: string, params: TileParameters) {
this.getDEMWorkerSource(mapId, params.source).removeTile(params);
}
removeSource(mapId: string, params: {
source: string;
} & {
type: string;
}, callback: WorkerTileCallback) {
assert(params.type);
assert(params.source);
if (!this.workerSources[mapId] ||
!this.workerSources[mapId][params.type] ||
!this.workerSources[mapId][params.type][params.source]) {
return;
}
const worker = this.workerSources[mapId][params.type][params.source];
delete this.workerSources[mapId][params.type][params.source];
if (worker.removeSource !== undefined) {
worker.removeSource(params, callback);
} else {
callback();
}
}
/**
* Load a {@link WorkerSource} script at params.url. The script is run
* (using importScripts) with `registerWorkerSource` in scope, which is a
* function taking `(name, workerSourceObject)`.
* @private
*/
loadWorkerSource(map: string, params: {
url: string;
}, callback: Callback<void>) {
try {
this.self.importScripts(params.url);
callback();
} catch (e) {
callback(e.toString());
}
}
syncRTLPluginState(map: string, state: PluginState, callback: Callback<boolean>) {
try {
globalRTLTextPlugin.setState(state);
const pluginURL = globalRTLTextPlugin.getPluginURL();
if (
globalRTLTextPlugin.isLoaded() &&
!globalRTLTextPlugin.isParsed() &&
pluginURL != null // Not possible when `isLoaded` is true, but keeps flow happy
) {
this.self.importScripts(pluginURL);
const complete = globalRTLTextPlugin.isParsed();
const error = complete ? undefined : new Error(`RTL Text Plugin failed to import scripts from ${pluginURL}`);
callback(error, complete);
}
} catch (e) {
callback(e.toString());
}
}
getAvailableImages(mapId: string) {
let availableImages = this.availableImages[mapId];
if (!availableImages) {
availableImages = [];
}
return availableImages;
}
getLayerIndex(mapId: string) {
let layerIndexes = this.layerIndexes[mapId];
if (!layerIndexes) {
layerIndexes = this.layerIndexes[mapId] = new StyleLayerIndex();
}
return layerIndexes;
}
getWorkerSource(mapId: string, type: string, source: string) {
if (!this.workerSources[mapId])
this.workerSources[mapId] = {};
if (!this.workerSources[mapId][type])
this.workerSources[mapId][type] = {};
if (!this.workerSources[mapId][type][source]) {
// use a wrapped actor so that we can attach a target mapId param
// to any messages invoked by the WorkerSource
const actor = {
send: (type, data, callback) => {
this.actor.send(type, data, callback, mapId);
}
};
this.workerSources[mapId][type][source] = new (this.workerSourceTypes[type] as any)((actor as any), this.getLayerIndex(mapId), this.getAvailableImages(mapId));
}
return this.workerSources[mapId][type][source];
}
getDEMWorkerSource(mapId: string, source: string) {
if (!this.demWorkerSources[mapId])
this.demWorkerSources[mapId] = {};
if (!this.demWorkerSources[mapId][source]) {
this.demWorkerSources[mapId][source] = new RasterDEMTileWorkerSource();
}
return this.demWorkerSources[mapId][source];
}
enforceCacheSizeLimit(mapId: string, limit: number) {
enforceCacheSizeLimit(limit);
}
}
if (isWorker()) {
(self as any).worker = new Worker(self as any);
}