UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

335 lines (291 loc) 10.4 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { EventDispatcher, FloatType, LinearSRGBColorSpace, SRGBColorSpace, UnsignedByteType, type ColorSpace, type Texture, type TextureDataType, } from 'three'; import type CoordinateSystem from '../core/geographic/CoordinateSystem'; import type Extent from '../core/geographic/Extent'; import type { GridExtent } from '../core/geographic/Extent'; import type MemoryUsage from '../core/MemoryUsage'; import { type GetMemoryUsageContext } from '../core/MemoryUsage'; class ImageResult { public id: string; public zIndex?: number; public texture: Texture; public extent: Extent; public min: number | undefined; public max: number | undefined; /** * @param options - options */ public constructor(options: { /** The unique identifier of this result. */ id: string; /** The texture */ texture: Texture; /** The extent */ extent: Extent; /** The minimum value of this image (if applicable). */ min?: number; /** The maximum value of this image (if applicable). */ max?: number; /** Optional z-index to apply to the image */ zIndex?: number; }) { if (!options.id) { throw new Error('id cannot be null'); } if (options.texture == null) { throw new Error('texture cannot be null'); } if (options.extent == null) { throw new Error('extent cannot be null'); } this.id = options.id; this.zIndex = options.zIndex; this.texture = options.texture; this.extent = options.extent; this.min = options.min; this.max = options.max; } } export type CustomContainsFn = (extent: Extent) => boolean; export interface GetImageOptions { /** The identifier of the node that emitted the request. */ id: string; /** The extent of the request area. */ extent: Extent; /** The pixel width of the request area. */ width: number; /** The pixel height of the request area. */ height: number; /** If `true`, the generated textures must be readable (i.e `DataTextures`). */ createReadableTextures: boolean; /** The optional abort signal. */ signal?: AbortSignal; } export interface ImageResponse { /** * The id of the response, used to deduplicate requests. */ id: string; /** * The request that will generate the image. */ request: (() => Promise<ImageResult>) | (() => ImageResult); } export interface ImageSourceOptions { /** * Should images be flipped vertically during composition ? */ flipY?: boolean; /** * The data type of images generated. * For regular color images, this should be `true`. For images with a high dynamic range, * or images that requires additional processing, this should be `false`. */ is8bit?: boolean; /** * The custom function to test if a given extent is contained in this * source. Note: we assume this function accepts extents in this source's CRS. */ containsFn?: CustomContainsFn; /** * The custom color space of the generated textures. * See https://threejs.org/docs/#manual/en/introduction/Color-management for * more information. If unspecified, the source considers that 8-bit images are in the sRGB * color space, otherwise `NoColorSpace`. */ colorSpace?: ColorSpace; /** * Is this source able to generate images synchronously ? */ synchronous?: boolean; /** * The relative [priority](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#priority) of HTTP requests emitted by this source. * @defaultValue `'auto'` */ requestPriority?: RequestPriority; /** * Should the generated images be considered transparent ? * @defaultValue false */ transparent?: boolean; } export interface ImageSourceEvents { /** * Raised when the source's content has been updated. */ updated: { extent?: Extent }; } /** * Base class for all image sources. The `ImageSource` produces images to be consumed by clients, * such as map layers. */ abstract class ImageSource<Events extends ImageSourceEvents = ImageSourceEvents> extends EventDispatcher<Events & ImageSourceEvents> implements MemoryUsage { public readonly isMemoryUsage = true as const; public readonly isImageSource: boolean = true as const; public readonly type: string; public readonly priority: RequestPriority = 'auto'; private readonly _customColorSpace: ColorSpace | undefined; /** * Gets whether images generated from this source should be flipped vertically. */ public readonly flipY: boolean; public readonly transparent: boolean; /** * Gets the datatype of images generated by this source. */ public datatype: TextureDataType; public readonly containsFn: CustomContainsFn | undefined; /** * If `true`, this source can immediately generate images without any delay. */ public readonly synchronous: boolean = false; /** * @param options - Options. */ public constructor(options: ImageSourceOptions = {}) { super(); this.isImageSource = true; this.type = 'ImageSource'; this.flipY = options.flipY ?? false; this.datatype = (options.is8bit ?? true) ? UnsignedByteType : FloatType; this._customColorSpace = options.colorSpace; this.priority = options.requestPriority ?? 'auto'; this.transparent = options.transparent ?? false; this.containsFn = options.containsFn; this.synchronous = options?.synchronous ?? false; } public getMemoryUsage(_context: GetMemoryUsageContext): void { // Implement this in derived classes to compute the memory usage of the source. } /** * Gets the color space of the textures generated by this source. */ public get colorSpace(): ColorSpace { if (this._customColorSpace != null) { return this._customColorSpace; } // Assume that 8-bit images are in the sRGB color space. // Also note that the final decision related to color space is the // responsibility of the layer rather than the source. return this.datatype === UnsignedByteType ? SRGBColorSpace : LinearSRGBColorSpace; } /** * Returns an adjusted extent, width and height so that request pixels are aligned with source * pixels, and requests do not oversample the source. * * @param requestExtent - The request extent. * @param requestWidth - The width, in pixels, of the request extent. * @param requestHeight - The height, in pixels, of the request extent. * @param margin - The margin, in pixels, around the initial extent. * @returns The adjusted parameters. */ public adjustExtentAndPixelSize( requestExtent: Extent, requestWidth: number, requestHeight: number, // eslint-disable-next-line @typescript-eslint/no-unused-vars margin = 0, ): GridExtent | null { // Default implementation. return null; } /** * Returns the CRS of this source. * * @returns The CRS. */ public abstract getCrs(): CoordinateSystem; /** * Returns the extent of this source expressed in the CRS of the source. Might be `null` if the extent of the source is not known. * * @returns The extent of the source, if any. */ public abstract getExtent(): Extent | null; /** * Raises an event to reload the source. */ public update(extent?: Extent): void { this.dispatchEvent({ type: 'updated', extent }); } /** * Gets whether this source contains the specified extent. If a custom contains function * is provided, it will be used. Otherwise, * {@link intersects} is used. * * This method is mainly used to discard non-relevant requests (i.e don't process regions * that are not relevant to this source). * * @param extent - The extent to test. */ public contains(extent: Extent): boolean { const convertedExtent = extent.clone().as(this.getCrs()); if (this.containsFn) { return this.containsFn(convertedExtent); } return this.intersects(convertedExtent); } /** * Test the intersection between the specified extent and this source's extent. * This method may be overriden to perform special logic. * * @param extent - The extent to test. * @returns `true` if the extent and this source extent intersects, `false` otherwise. */ public intersects(extent: Extent): boolean { const thisExtent = this.getExtent(); if (thisExtent != null) { return thisExtent.intersectsExtent(extent); } // We don't have an extent, so we default to true. return true; } /** * Initializes the source. * * @param options - Options. * @returns A promise that resolves when the source is initialized. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public initialize(options: { /** The target projection. Only useful for sources that are able * to reproject their data on the fly (typically vector sources). */ targetProjection: CoordinateSystem; }): Promise<void> { return Promise.resolve(); } /** * Gets the images for the specified extent and pixel size. * * @param options - The options. * @returns An array containing the functions to generate the images asynchronously. */ public abstract getImages(options: GetImageOptions): ImageResponse[]; /** * Disposes unmanaged resources of this source. */ public dispose(): void { // Implement this in derived classes to cleanup unmanaged resources, // such as cached objects. } } function isImageSource(obj: unknown): obj is ImageSource { return (obj as ImageSource).isImageSource === true; } export default ImageSource; export { ImageResult, isImageSource };