UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

122 lines (96 loc) 3.56 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type { BaseMessageMap, SuccessResponse } from '../utils/WorkerPool'; import { createErrorResponse, type Message } from '../utils/WorkerPool'; /** * Utility functions and worker to process mapbox encoded terrain. */ export interface DecodeMapboxTerrainResult { min: number; max: number; width: number; height: number; /** * An array buffer that can be turned into a Float32Array. */ data: ArrayBuffer; } function getPixels(image: ImageBitmap): Uint8ClampedArray { const canvas = new OffscreenCanvas(image.width, image.height); canvas.width = image.width; canvas.height = image.height; const context = canvas.getContext('2d', { willReadFrequently: true }); if (!context) { console.error('could not acquire 2D context on canvas'); throw new Error('could not acquire 2D context on canvas'); } context.drawImage(image, 0, 0); return context.getImageData(0, 0, image.width, image.height).data; } export async function decodeMapboxTerrainImage( blob: Blob, noData?: number, ): Promise<DecodeMapboxTerrainResult> { const image = await createImageBitmap(blob); const pixelData = getPixels(image); return { ...decodeMapboxTerrainBuffer(pixelData, noData), width: image.width, height: image.height, }; } function decodeMapboxTerrainBuffer( pixelData: Uint8ClampedArray, noData?: number, ): Pick<DecodeMapboxTerrainResult, 'data' | 'min' | 'max'> { const stride = pixelData.length % 3 === 0 ? 3 : 4; const length = pixelData.length / stride; const array = new Float32Array(length * 2); // To store the alpha component let k = 0; let min = +Infinity; let max = -Infinity; for (let i = 0; i < pixelData.length; i += stride) { const r = pixelData[i + 0]; const g = pixelData[i + 1]; const b = pixelData[i + 2]; const elevation = -10000 + (r * 256 * 256 + g * 256 + b) * 0.1; array[k * 2 + 0] = elevation; if (noData != null && noData === elevation) { array[k * 2 + 1] = 0; } else { array[k * 2 + 1] = 1; min = Math.min(min, elevation); max = Math.max(max, elevation); } k += 1; } return { data: array.buffer, min, max }; } // Web worker implementation export type DecodeMapboxTerrainMessage = Message<{ buffer: ArrayBuffer; noData?: number }>; export type MessageType = 'DecodeMapboxTerrainMessage'; export interface MessageMap extends BaseMessageMap<MessageType> { DecodeMapboxTerrainMessage: { payload: DecodeMapboxTerrainMessage['payload']; response: DecodeMapboxTerrainResult; }; } onmessage = async function onmessage(ev: MessageEvent<DecodeMapboxTerrainMessage>): Promise<void> { const message = ev.data; try { if (message.type === 'DecodeMapboxTerrainMessage') { const blob = new Blob([message.payload.buffer], { type: 'image/png' }); const result = await decodeMapboxTerrainImage(blob, message.payload.noData); const response: SuccessResponse<DecodeMapboxTerrainResult> = { requestId: message.id, payload: result, }; this.postMessage(response, { transfer: [response.payload.data] }); } } catch (err) { this.postMessage(createErrorResponse(message.id, err)); } };