layers_SingleImageImageryLayer.js

import { ImageryLayer } from "./ImageryLayer.js"
import * as THREE from 'three';
const toDegrees = 57.295779513082320876798154814105;
const defaultTexture = generateDefaultTexture();
const uvBounds = new THREE.Box2(new THREE.Vector2(0, 0), new THREE.Vector2(1, 1));
function generateDefaultTexture() {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;

    // Get the context of the canvas
    const context = canvas.getContext('2d');

    context.fillStyle = 'rgb(8,23,54)';
    context.fillRect(0, 0, 1, 1);

    // Create a Three.js texture from the canvas
    const texture = new THREE.Texture(canvas);

    // Set texture parameters if needed
    texture.wrapS = THREE.ClampToEdgeWrapping;
    texture.wrapT = THREE.ClampToEdgeWrapping;
    texture.minFilter = THREE.NearestFilter;
    texture.magFilter = THREE.NearestFilter;
    texture.needsUpdate = true;
    return texture;
}

/**
 * Imagery from a single image (Equidistant Cylindrical only).
 * @class
 * @extends ImageryLayer
 */
class SingleImageImageryLayer extends ImageryLayer {

    /**
     * Create a layer that requests images from a WMS service. 
     * The layer will immediately return the closest available LOD and load a better LOD for the tile when requestable
     * Only EPSG:4326 is supported.
     * @param {Object} properties 
     * @param {String|Number} properties.id layer id should be unique
     * @param {String} properties.name the name can be anything you want and is intended for labeling
     * @param {String} properties.url the image url
     * @param {String} properties.epsg an EPSG code (only epsg:4326 supported)
     * @param {Number} [properties.transparency = 0] the layer's transparency (0 to 1)
     * @param {Number[]} [properties.bounds=[-180, -90, 180, 90]]  min longitude, min latitude, max longitude, max latitude in degrees
     * @param {Boolean} [properties.visible = true] layer will be rendered if true (true by default)
     */
    constructor(properties) {
        super(properties);
        const self = this;

        self.loaded = false;
        self.pendingRequests = [];
        self.reference = properties.epsg;

        self.img = new Image();

        self.img.onload = function () {
            self.loaded = true;
            self.pendingRequests.forEach(f => f());
        };
        self.img.src = properties.url;
        if(properties.bounds){
            self.bounds = new THREE.Box2(new THREE.Vector2(properties.bounds[0]/toDegrees, properties.bounds[1]/toDegrees), new THREE.Vector2(properties.bounds[2]/toDegrees, properties.bounds[3]/toDegrees));
        }
        
        self.userTextureMap = {};
    }

    getMap(tile, callbackSuccess, callbackFailure, width = 128, height = 128) {
        if (!this.bounds || !this.bounds.intersectsBox(tile.bounds)) {
            callbackFailure("bounds don't intersect with layer");
        }
        const self = this;

        const ctx = document.createElement('canvas').getContext('2d');
        ctx.canvas.width = width;
        ctx.canvas.height = height;
        const texture = new THREE.CanvasTexture(ctx.canvas);

        const loadFunction = () => {
            if (!!tile.disposed) {
                return;
            }
            const sx = (tile.bounds.min.x - self.bounds.min.x) * self.img.width / (self.bounds.max.x - self.bounds.min.x); let sy = (tile.bounds.min.y - self.bounds.min.y) * self.img.height / (self.bounds.max.y - self.bounds.min.y);
            const sw = ((tile.bounds.max.x - tile.bounds.min.x) / (self.bounds.max.x - self.bounds.min.x)) * self.img.width;
            const sh = ((tile.bounds.max.y - tile.bounds.min.y) / (self.bounds.max.y - self.bounds.min.y)) * self.img.height;
            sy = self.img.height - (sy + sh)
            ctx.drawImage(self.img, sx, sy, sw, sh, 0, 0, width, height);
            texture.wrapS = THREE.ClampToEdgeWrapping;
            texture.wrapT = THREE.ClampToEdgeWrapping;
            texture.magFilter = THREE.LinearFilter;
            texture.minFilter = THREE.LinearFilter;
            texture.needsUpdate = true;
            return texture;
        }

        if (!self.loaded) {
            self.pendingRequests.push(() => {
                const tex = loadFunction();
                self.userTextureMap[tile] = tex;
                callbackSuccess(tex);
            });
            return {
                texture: defaultTexture,
                uvBounds: uvBounds,
                reference: self.reference
            };
        } else {
            const tex = loadFunction();
            self.userTextureMap[tile] = tex;
            return {
                texture: tex,
                uvBounds: uvBounds,
                reference: self.reference
            }
        }
    };

    detach(user, texture) {
        if (texture == defaultTexture) return;
        if(this.userTextureMap[user]){
            this.userTextureMap[user].dispose();
            delete this.userTextureMap[user];
        }
    }
}

export { SingleImageImageryLayer };