@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
118 lines (98 loc) • 3.54 kB
text/typescript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import { DataTexture, FloatType, LinearFilter, RGFormat } from 'three';
import type { DecodeOptions, DecodeResult } from './ImageFormat';
import type { DecodeMapboxTerrainResult, MessageMap, MessageType } from './mapboxWorker';
import WorkerPool from '../utils/WorkerPool';
import ImageFormat from './ImageFormat';
import { decodeMapboxTerrainImage } from './mapboxWorker';
let workerPool: WorkerPool<MessageType, MessageMap> | null = null;
function createWorker(): Worker {
return new Worker(new URL('./mapboxWorker.js', import.meta.url), {
type: 'module',
name: 'mapbox',
});
}
/**
* Decoder for [Mapbox Terrain](https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/) images.
*/
class MapboxTerrainFormat extends ImageFormat {
public readonly isMapboxTerrainFormat: boolean = true as const;
public override readonly type = 'MapboxTerrainFormat' as const;
private readonly _enableWorkers: boolean = true;
private readonly _workerConcurrency: number | undefined;
/**
* @param options - Decoder options.
*/
public constructor(options?: {
/**
* Enables processing raster data in web workers.
* @defaultValue true
*/
enableWorkers?: boolean;
/**
* The maximum number of workers created by the worker pool.
* If `undefined`, the maximum number of workers will be allowed.
* @defaultValue undefined
*/
workerConcurrency?: number;
}) {
super(true, FloatType);
this._enableWorkers = options?.enableWorkers ?? true;
this._workerConcurrency = options?.workerConcurrency ?? undefined;
}
/**
* Decode a Mapbox Terrain blob into a
* [DataTexture](https://threejs.org/docs/?q=texture#api/en/textures/DataTexture) containing
* the elevation data.
*
* @param blob - the data to decode
* @param options - the decoding options
*/
public async decode(blob: Blob, options?: DecodeOptions): Promise<DecodeResult> {
let result: DecodeMapboxTerrainResult;
if (this._enableWorkers) {
result = await this.getHeightValuesUsingWorker(
blob,
options?.noDataValue,
this._workerConcurrency,
);
} else {
result = await decodeMapboxTerrainImage(blob, options?.noDataValue);
}
const texture = new DataTexture(
new Float32Array(result.data),
result.width,
result.height,
RGFormat,
FloatType,
);
texture.needsUpdate = true;
texture.generateMipmaps = false;
texture.magFilter = LinearFilter;
texture.minFilter = LinearFilter;
return {
texture,
min: result.min,
max: result.max,
};
}
private async getHeightValuesUsingWorker(
blob: Blob,
noData?: number,
concurrency?: number,
): Promise<DecodeMapboxTerrainResult> {
if (workerPool == null) {
workerPool = new WorkerPool({ createWorker, concurrency });
}
const buffer = await blob.arrayBuffer();
const result = await workerPool.queue('DecodeMapboxTerrainMessage', { buffer, noData }, [
buffer,
]);
return result;
}
}
export default MapboxTerrainFormat;