Initial commit
This commit is contained in:
220
node_modules/maplibre-gl/src/source/canvas_source.ts
generated
vendored
Normal file
220
node_modules/maplibre-gl/src/source/canvas_source.ts
generated
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
import {ImageSource} from './image_source';
|
||||
|
||||
import {Texture} from '../render/texture';
|
||||
import {Event, ErrorEvent} from '../util/evented';
|
||||
import {ValidationError} from '@maplibre/maplibre-gl-style-spec';
|
||||
|
||||
import type {Map} from '../ui/map';
|
||||
import type {Dispatcher} from '../util/dispatcher';
|
||||
import type {Evented} from '../util/evented';
|
||||
|
||||
/**
|
||||
* Options to add a canvas source type to the map.
|
||||
*/
|
||||
export type CanvasSourceSpecification = {
|
||||
/**
|
||||
* Source type. Must be `"canvas"`.
|
||||
*/
|
||||
type: 'canvas';
|
||||
/**
|
||||
* Four geographical coordinates denoting where to place the corners of the canvas, specified in `[longitude, latitude]` pairs.
|
||||
*/
|
||||
coordinates: [[number, number], [number, number], [number, number], [number, number]];
|
||||
/**
|
||||
* Whether the canvas source is animated. If the canvas is static (i.e. pixels do not need to be re-read on every frame), `animate` should be set to `false` to improve performance.
|
||||
* @defaultValue true
|
||||
*/
|
||||
animate?: boolean;
|
||||
/**
|
||||
* Canvas source from which to read pixels. Can be a string representing the ID of the canvas element, or the `HTMLCanvasElement` itself.
|
||||
*/
|
||||
canvas?: string | HTMLCanvasElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* A data source containing the contents of an HTML canvas. See {@link CanvasSourceSpecification} for detailed documentation of options.
|
||||
*
|
||||
* @group Sources
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // add to map
|
||||
* map.addSource('some id', {
|
||||
* type: 'canvas',
|
||||
* canvas: 'idOfMyHTMLCanvas',
|
||||
* animate: true,
|
||||
* coordinates: [
|
||||
* [-76.54, 39.18],
|
||||
* [-76.52, 39.18],
|
||||
* [-76.52, 39.17],
|
||||
* [-76.54, 39.17]
|
||||
* ]
|
||||
* });
|
||||
*
|
||||
* // update
|
||||
* let mySource = map.getSource('some id');
|
||||
* mySource.setCoordinates([
|
||||
* [-76.54335737228394, 39.18579907229748],
|
||||
* [-76.52803659439087, 39.1838364847587],
|
||||
* [-76.5295386314392, 39.17683392507606],
|
||||
* [-76.54520273208618, 39.17876344106642]
|
||||
* ]);
|
||||
*
|
||||
* map.removeSource('some id'); // remove
|
||||
* ```
|
||||
*/
|
||||
export class CanvasSource extends ImageSource {
|
||||
options: CanvasSourceSpecification;
|
||||
animate: boolean;
|
||||
canvas: HTMLCanvasElement;
|
||||
width: number;
|
||||
height: number;
|
||||
/**
|
||||
* Enables animation. The image will be copied from the canvas to the map on each frame.
|
||||
*/
|
||||
play: () => void;
|
||||
/**
|
||||
* Disables animation. The map will display a static copy of the canvas image.
|
||||
*/
|
||||
pause: () => void;
|
||||
_playing: boolean;
|
||||
|
||||
/** @internal */
|
||||
constructor(id: string, options: CanvasSourceSpecification, dispatcher: Dispatcher, eventedParent: Evented) {
|
||||
super(id, options, dispatcher, eventedParent);
|
||||
|
||||
// We build in some validation here, since canvas sources aren't included in the style spec:
|
||||
if (!options.coordinates) {
|
||||
this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, 'missing required property "coordinates"')));
|
||||
} else if (!Array.isArray(options.coordinates) || options.coordinates.length !== 4 ||
|
||||
options.coordinates.some(c => !Array.isArray(c) || c.length !== 2 || c.some(l => typeof l !== 'number'))) {
|
||||
this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, '"coordinates" property must be an array of 4 longitude/latitude array pairs')));
|
||||
}
|
||||
|
||||
if (options.animate && typeof options.animate !== 'boolean') {
|
||||
this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, 'optional "animate" property must be a boolean value')));
|
||||
}
|
||||
|
||||
if (!options.canvas) {
|
||||
this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, 'missing required property "canvas"')));
|
||||
} else if (typeof options.canvas !== 'string' && !(options.canvas instanceof HTMLCanvasElement)) {
|
||||
this.fire(new ErrorEvent(new ValidationError(`sources.${id}`, null, '"canvas" must be either a string representing the ID of the canvas element from which to read, or an HTMLCanvasElement instance')));
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
this.animate = options.animate !== undefined ? options.animate : true;
|
||||
}
|
||||
|
||||
async load() {
|
||||
this._loaded = true;
|
||||
if (!this.canvas) {
|
||||
this.canvas = (this.options.canvas instanceof HTMLCanvasElement) ?
|
||||
this.options.canvas :
|
||||
document.getElementById(this.options.canvas) as HTMLCanvasElement;
|
||||
// cast to HTMLCanvasElement in else of ternary
|
||||
// should we do a safety check and throw if it's not actually HTMLCanvasElement?
|
||||
}
|
||||
this.width = this.canvas.width;
|
||||
this.height = this.canvas.height;
|
||||
|
||||
if (this._hasInvalidDimensions()) {
|
||||
this.fire(new ErrorEvent(new Error('Canvas dimensions cannot be less than or equal to zero.')));
|
||||
return;
|
||||
}
|
||||
|
||||
this.play = function() {
|
||||
this._playing = true;
|
||||
this.map.triggerRepaint();
|
||||
};
|
||||
|
||||
this.pause = function() {
|
||||
if (this._playing) {
|
||||
this.prepare();
|
||||
this._playing = false;
|
||||
}
|
||||
};
|
||||
|
||||
this._finishLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML `canvas` element.
|
||||
*
|
||||
* @returns The HTML `canvas` element.
|
||||
*/
|
||||
getCanvas(): HTMLCanvasElement {
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
onAdd(map: Map) {
|
||||
this.map = map;
|
||||
this.load();
|
||||
if (this.canvas) {
|
||||
if (this.animate) this.play();
|
||||
}
|
||||
}
|
||||
|
||||
onRemove() {
|
||||
this.pause();
|
||||
}
|
||||
|
||||
prepare() {
|
||||
let resize = false;
|
||||
if (this.canvas.width !== this.width) {
|
||||
this.width = this.canvas.width;
|
||||
resize = true;
|
||||
}
|
||||
if (this.canvas.height !== this.height) {
|
||||
this.height = this.canvas.height;
|
||||
resize = true;
|
||||
}
|
||||
|
||||
if (this._hasInvalidDimensions()) return;
|
||||
|
||||
if (Object.keys(this.tiles).length === 0) return; // not enough data for current position
|
||||
|
||||
const context = this.map.painter.context;
|
||||
const gl = context.gl;
|
||||
|
||||
if (!this.texture) {
|
||||
this.texture = new Texture(context, this.canvas, gl.RGBA, {premultiply: true});
|
||||
this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
|
||||
} else if (resize || this._playing) {
|
||||
this.texture.update(this.canvas, {premultiply: true});
|
||||
}
|
||||
|
||||
let newTilesLoaded = false;
|
||||
for (const w in this.tiles) {
|
||||
const tile = this.tiles[w];
|
||||
if (tile.state !== 'loaded') {
|
||||
tile.state = 'loaded';
|
||||
tile.texture = this.texture;
|
||||
newTilesLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (newTilesLoaded) {
|
||||
this.fire(new Event('data', {dataType: 'source', sourceDataType: 'idle', sourceId: this.id}));
|
||||
}
|
||||
}
|
||||
|
||||
serialize(): CanvasSourceSpecification {
|
||||
return {
|
||||
type: 'canvas',
|
||||
animate: this.animate,
|
||||
canvas: this.options.canvas,
|
||||
coordinates: this.coordinates
|
||||
};
|
||||
}
|
||||
|
||||
hasTransition() {
|
||||
return this._playing;
|
||||
}
|
||||
|
||||
_hasInvalidDimensions() {
|
||||
for (const x of [this.canvas.width, this.canvas.height]) {
|
||||
if (isNaN(x) || x <= 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user