layers_SingleImageElevationLayer.js

import { ElevationLayer } from './ElevationLayer.js'

const radiansToDegrees = 180 / Math.PI;
const halfPI = Math.PI * 0.5;
/**
 * Elevation from a single image (Equidistant Cylindrical).
 * @class
 * @extends ElevationLayer
 */
class SingleImageElevationLayer extends ElevationLayer {

    /**
     * 
     * @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 url of the elevation image
     * @param {Number} properties.min min height relative to sea level
     * @param {Number} properties.max max height relative to sea level
     * @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);

        this.min = properties.min;
        this.max = properties.max;

        this.loaded = false;
        this.pendingRequests = [];

        const canvas = document.createElement('canvas');
        this.context = canvas.getContext('2d');

        const img = new Image;
        const self = this;

        img.onload = function () {
            canvas.height = img.height;
            canvas.width = img.width;
            self.context.drawImage(img, 0, 0); // Or at whatever offset you like
            self.data = self.context.getImageData(0, 0, canvas.width, canvas.height).data;
            self.loaded = true;
            self.pendingRequests.forEach(f => f());
        };
        img.src = properties.url;

        //var pixelData = canvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1).data;
    }




    getElevation(bounds, width, height, geometry, skirtGeometry) {
        const self = this;
        const extendedBounds = bounds.clone();
        extendedBounds.min.x -= (bounds.max.x - bounds.min.x) / (width - 1);
        extendedBounds.max.x += (bounds.max.x - bounds.min.x) / (width - 1);
        extendedBounds.min.y -= (bounds.max.y - bounds.min.y) / (height - 1);
        extendedBounds.max.y += (bounds.max.y - bounds.min.y) / (height - 1);

        const meshGeneration = super._simpleMeshFromElevationAsync;
        const trim = super._trimEdges;

        const extendedWidth = width + 2;
        const extendedHeight = height + 2;
        function request() {
            return new Promise((resolve, reject) => {
                var elevationArray = new Array(extendedWidth * extendedHeight).fill(0);
                for (let x = 0; x < extendedWidth; x++) {
                    for (let y = 0; y < extendedHeight; y++) {
                        let lon = (extendedBounds.min.x + (x * ((extendedBounds.max.x - extendedBounds.min.x) / (width +1))));
                        let lat = -(extendedBounds.min.y + (y * ((extendedBounds.max.y - extendedBounds.min.y) / (height +1))));

                        if (lat > halfPI) {
                            lon += Math.PI;
                            lat = halfPI - (lat - halfPI)
                        } else if (lat < -halfPI) {
                            lon += Math.PI;
                            lat = -halfPI - (lat + halfPI)
                        }
                        if (lon > Math.PI) {
                            lon = -Math.PI + (lon % Math.PI);
                        }
                        else if (lon < -Math.PI) {
                            lon = Math.PI + (lon % Math.PI);
                        }
                        elevationArray[extendedWidth * y + x] = self.getElevationAtLocation(lon * radiansToDegrees, lat * radiansToDegrees);
                    }
                }

                
                if (geometry && skirtGeometry) {

                    meshGeneration(bounds, width, height, elevationArray, geometry, skirtGeometry).then(shift => {
                        resolve({
                            elevationArray: trim(elevationArray, width+2, height+2),
                            shift: shift,
                        });
                    }, error => {
                        reject(error);
                    })

                } else {
                    resolve({
                        elevationArray: trim(elevationArray, width, height),
                        shift: undefined,
                    });
                }
            });
            
        }
        if (this.loaded) {
            return request();
        } else {
            var onLoad;
            const promise = new Promise(resolve => {
                onLoad = () => {
                    request().then(result => {
                        resolve(result);
                    })

                }
            })
            this.pendingRequests.push(onLoad);
            return promise;
        }


    };

    getElevationAtLocation(lon, lat) {
        if (lon < this.bounds.min.x || lat < this.bounds.min.y || lon > this.bounds.max.x || lat > this.bounds.max.y) {
            return 0;
        } else {
            let pixelRedValue = this.billinearInterpolation(
                (lon - this.bounds.min.x) / (this.bounds.max.x - this.bounds.min.x),
                (lat - this.bounds.min.y) / (this.bounds.max.y - this.bounds.min.y)
            );
            pixelRedValue /= 255;
            pixelRedValue *= this.max - this.min;
            pixelRedValue += this.min;
            return pixelRedValue;
        }
    }

    billinearInterpolation(percentageX, percentageY) {

        var x = percentageX * (this.context.canvas.width - 1);
        var y = percentageY * (this.context.canvas.height - 1);


        var floorX = Math.floor(x);
        if (floorX == x) floorX -= 1;
        var floorY = Math.floor(y);
        if (floorY == y) floorY -= 1;
        var ceilX = Math.ceil(x);
        if (ceilX == 0) ceilX += 1;
        var ceilY = Math.ceil(y);
        if (ceilY == 0) ceilY += 1;
        floorX = Math.max(0, floorX);
        floorY = Math.max(0, floorY);

        ceilX = Math.min((this.context.canvas.width - 1), ceilX);
        ceilY = Math.min((this.context.canvas.height - 1), ceilY);

        return ((1 - (x - floorX)) * (1 - (y - floorY)) * this.data[floorY * 4 * this.context.canvas.width + floorX * 4]) +
            ((1 - (ceilX - x)) * (1 - (y - floorY)) * this.data[floorY * 4 * this.context.canvas.width + floorX * 4 + 4]) +
            ((1 - (x - floorX)) * (1 - (ceilY - y)) * this.data[(floorY + 1) * 4 * this.context.canvas.width + floorX * 4]) +
            ((1 - (ceilX - x)) * (1 - (ceilY - y)) * this.data[(floorY + 1) * 4 * this.context.canvas.width + floorX * 4 + 4]);
    }

}

export { SingleImageElevationLayer };