@giro3d/giro3d
Version:
A JS/WebGL framework for 3D geospatial data visualization
142 lines (118 loc) • 4.79 kB
text/typescript
/*
* Copyright (c) 2015-2018, IGN France.
* Copyright (c) 2018-2026, Giro3D team.
* SPDX-License-Identifier: MIT
*/
import { CanvasTexture, Color } 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 { CustomContainsFn, GetImageOptions, ImageResponse } from './ImageSource';
import PromiseUtils from '../utils/PromiseUtils';
import ImageSource, { ImageResult } from './ImageSource';
export default class DebugSource extends ImageSource {
public readonly isDebugSource: boolean = true as const;
public override readonly type = 'DebugSource' as const;
private readonly _delay: () => number;
private readonly _extent: Extent;
private readonly _opacity: number;
private readonly _subdivisions: number;
private readonly _color: Color | ((options: GetImageOptions) => Color);
/**
* @param options - options
*/
public constructor(options: {
/** The extent. */
extent: Extent;
/** The delay before loading the images, in milliseconds. */
delay?: number | (() => number);
/** The opacity of the images. */
opacity?: number;
/** The color of the images. */
color?: Color | ((options: GetImageOptions) => Color);
/** How many images per tile are served. */
subdivisions?: number;
/** The custom function to test if a given extent is contained in this source. */
containsFn?: CustomContainsFn;
}) {
super(options);
const { delay, subdivisions, opacity, extent, color } = options;
if (delay != null) {
if (typeof delay === 'function') {
this._delay = delay;
} else if (typeof delay === 'number') {
this._delay = (): number => delay;
} else {
this._delay = (): number => 0;
}
} else {
this._delay = (): number => 0;
}
this._extent = options.extent;
this._opacity = opacity ?? 1;
this._subdivisions = subdivisions ?? 1;
this._color = color ?? new Color(1, 1, 1);
this._extent = extent;
}
public override adjustExtentAndPixelSize(
requestExtent: Extent,
requestWidth: number,
requestHeight: number,
): GridExtent | null {
// To help visualize the images, we don't adjust their extent at all.
return {
extent: requestExtent,
height: requestHeight,
width: requestWidth,
};
}
private getImage(width: number, height: number, id: string, color: Color): CanvasTexture {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d', { willReadFrequently: true });
if (!context) {
throw new Error('could not acquire 2d context');
}
const prefix = id.substring(0, 10);
context.fillStyle = `#${color.getHexString()}`;
context.globalAlpha = this._opacity ?? 1;
context.fillRect(0, 0, width, height);
context.globalAlpha = 1;
context.strokeStyle = `#${color.getHexString()}`;
context.lineWidth = 16;
context.strokeRect(0, 0, width, height);
context.fillStyle = 'black';
const margin = 20;
context.fillText(prefix, margin, margin);
const texture = new CanvasTexture(canvas);
return texture;
}
public getCrs(): CoordinateSystem {
return this._extent.crs;
}
public getExtent(): Extent {
return this._extent;
}
public getImages(options: GetImageOptions): ImageResponse[] {
const { extent, width, height, signal, id } = options;
const subdivs = this._subdivisions;
const extents = extent.split(subdivs, subdivs);
const requests = [];
const w = Math.round(width / subdivs);
const h = Math.round(height / subdivs);
for (let i = 0; i < extents.length; i++) {
const ex = extents[i];
const imageId = `${id}-${i}`;
const color = typeof this._color === 'function' ? this._color(options) : this._color;
const request = (): Promise<ImageResult> =>
PromiseUtils.delay(this._delay()).then(() => {
signal?.throwIfAborted();
const texture = this.getImage(w, h, imageId, color);
return new ImageResult({ extent: ex, texture, id: imageId });
});
requests.push({ id: imageId, request });
}
return requests;
}
}