Source: modules/MagoTools.js

import * as Cesium from "cesium";
import {Cartesian3, Color, Entity, Viewer} from "cesium";
import tree from "../assets/lowpoly-tree.glb";

/**
 * MagoTools is a class that creates a viewer to render points on a globe.
 * @class
 * @param {Viewer} viewer - Cesium Viewer instance
 * @example
 * const magoTools = new MagoTools(viewer);
 * magoTools.test();
 */
export class MagoTools {
    constructor(viewer) {
        this.viewer = viewer;
    }

    /**
     * Tests the MagoTools class.
     * draws points on the globe.
     * @function
     * @returns {void}
     * @example
     * const magoTools = new MagoTools(viewer);
     * magoTools.test()
     */
    test() {
        const [lon, lat] = [126.968905, 37.447571];
        this.initPosition(lon, lat, 10000);
        this.changeGlobeColor("#111133");
        this.createGridImageryProvider();
        this.addRandomPoints(1000);
        this.createModel(tree, lon, lat);
    }

    /**
     * Initializes the viewer to render points on a globe.
     * @param lon {number} longitude degrees
     * @param lat {number} latitude degrees
     * @param height {number} height meters
     * @returns {void}
     * @example
     * magoTools.initPosition(126.978388, 37.566610, 10000)
     */
    initPosition(lon = 0, lat = 0, height = 0) {
        this.viewer.camera.setView({
            destination: Cartesian3.fromDegrees(lon, lat, height),
            orientation: {
                heading: Cesium.Math.toRadians(0.0),
                pitch: Cesium.Math.toRadians(-90.0),
            },
        });
    }

    /**
     * Samples random points on the globe.
     * @param lon {number} longitude degrees
     * @param lat {number} latitude degrees
     * @param height {number} height meters
     * @param duration {number} duration seconds
     * @returns {void}
     * @example
     * magoTools.flyTo(126.978388, 37.566610, 10000)
     */
    flyTo(lon, lat, height = 1000, duration = 0) {
        this.viewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(lon, lat, height),
            orientation: {
                heading: Cesium.Math.toRadians(0.0),
                pitch: Cesium.Math.toRadians(-90.0),
            },
            duration: duration,
        });
    }

    /**
     * Samples random points on the globe.
     * @param lon {number} longitude degrees
     * @param lat {number} latitude degrees
     * @param cssColor {String} CSS color string "#ff0000"
     * @param entitiesCollection
     * @returns {void}
     * @example
     * magoTools.addPoint(126.978388, 37.566610, "#ff0000")
     */
    addPoint(lon, lat, cssColor = "#ffcc00", entitiesCollection = null) {
        const entity = new Entity({
            position: Cartesian3.fromDegrees(lon, lat),
            point: {
                pixelSize: 3,
                color: Color.fromCssColorString(cssColor),
            },
            billboard: {
                image: "/src/assets/img.png",
                width: 16,
                height: 16,
                distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0,
                    1000.0),
            },
        });

        if (entitiesCollection) {
            entitiesCollection.add(entity);
        } else {
            this.viewer.entities.add(entity);
        }
    }

    /**
     * Adds a point to the globe.
     * @param cssColor {String} CSS color string "#ff0000"
     * @param entitiesCollection
     * @returns {void}
     * @example
     * magoTools.addPoint(126.978388, 37.566610, "#ff0000")
     */
    addRandomPoint(cssColor = "#ff0000", entitiesCollection = null) {
        const randomX = (Math.random() * 360) - 180;
        const randomY = (Math.random() * 180) - 90;
        this.addPoint(randomX, randomY, cssColor, entitiesCollection);
    }

    /**
     * Samples random points on the globe.
     * @param count {number}
     * @param cssColor {string} CSS color string "#ff0000"
     * @param entitiesCollection
     * @returns {void}
     * @example
     * magoTools.addRandomPoints(1000, "#ffff00")
     */
    addRandomPoints(count, cssColor = "#00ff00", entitiesCollection = null) {
        for (let i = 0; i < count; i++) {
            this.addRandomPoint(cssColor, entitiesCollection);
        }
    }

    /**
     * Changes the color of the globe.
     * @param cssColor {string} CSS color string "#ff0000"
     * @returns {void}
     * @example
     * magoTools.changeGlobeColor("#ff0000")
     */
    changeGlobeColor(cssColor) {
        this.viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString(
            cssColor);
    }

    /**
     * Creates a grid imagery provider on the globe.
     * @returns {void}
     * @example
     * magoTools.createGridImageryProvider()
     */
    createGridImageryProvider(cell = 8) {
        const grid = new Cesium.GridImageryProvider({
            cells: cell,
            color: Cesium.Color.WHITE.withAlpha(0.25),
            glowColor: Cesium.Color.BLACK.withAlpha(0.0),
            glowWidth: 4,
            backgroundColor: Cesium.Color.BLACK.withAlpha(0.05),
            maximumLevel: 5,
        });
        const layers = this.viewer.scene.imageryLayers;
        layers.addImageryProvider(grid);
    }

    /**
     * VWORLD Imagery Layer Provider on the globe.
     * @param type {String} Base, Satellite, White, Midnight
     * @param extension {String} jpeg, png
     * @returns {module:cesium.ImageryLayer}
     * @example
     * magoTools.createVworldImageryLayer('Satellite', true, 'jpeg', '00000000-0000-0000-0000-000000000000')
     */
    createVworldImageryLayerWithoutToken(type, extension) {
        const minLevel = 5;
        const maxLevel = 19;
        const options = {
            url: `https://xdworld.vworld.kr/2d/${type}/service/{TileMatrix}/{TileCol}/{TileRow}.${extension}`,
            layer: "Base",
            style: "default",
            maximumLevel: maxLevel,
            tileMatrixSetID: "EPSG:3857",
            credit: new Cesium.Credit("Vworld Korea"),
        };
        const imageryProvider = new Cesium.WebMapTileServiceImageryProvider(
            options);
        const imageryLayer = new Cesium.ImageryLayer(imageryProvider, {
            show: true,
            minimumTerrainLevel: minLevel,
        });

        const layers = this.viewer.scene.imageryLayers;
        layers.add(imageryLayer);
        return imageryLayer;
    }

    /**
     * VWORLD Imagery Layer Provider on the globe.
     * @param type {String} Base, Satellite, White, Midnight
     * @param hybrid {boolean}
     * @param extension {String} jpeg, png
     * @param vworldKey {String} 00000000-0000-0000-0000-000000000000
     * @returns {module:cesium.ImageryLayer}
     * @example
     * magoTools.createVworldImageryLayer('Satellite', true, 'jpeg', '00000000-0000-0000-0000-000000000000')
     */
    createVworldImageryLayer(type, hybrid, extension, vworldKey) {
        const minLevel = 5;
        const maxLevel = 19;

        const protocol = location.protocol === "https:" ? "https" : "http";
        const options = {
            url: `${protocol}://api.vworld.kr/req/wmts/1.0.0/${vworldKey}/${type}/{TileMatrix}/{TileRow}/{TileCol}.${extension}`,
            layer: "Base",
            style: "default",
            maximumLevel: maxLevel,
            tileMatrixSetID: "default028mm",
        };
        const imageryProvider = new Cesium.WebMapTileServiceImageryProvider(
            options);
        const imageryLayer = new Cesium.ImageryLayer(imageryProvider, {
            show: true,
            minimumTerrainLevel: minLevel,
        });

        const layers = this.viewer.scene.imageryLayers;
        layers.add(imageryLayer);

        if (hybrid) {
            const hybridOptions = {
                url: `${protocol}://api.vworld.kr/req/wmts/1.0.0/${vworldKey}/Hybrid/{TileMatrix}/{TileRow}/{TileCol}.png`,
                layer: "Hybrid",
                style: "default",
                maximumLevel: maxLevel,
                tileMatrixSetID: "default028mm",
            };
            const hybridImageryProvider = new Cesium.WebMapTileServiceImageryProvider(
                hybridOptions);
            const hybridImageryLayer = new Cesium.ImageryLayer(
                hybridImageryProvider,
                {
                    show: true,
                    minimumTerrainLevel: minLevel,
                });
            layers.add(hybridImageryLayer);
        }
        return imageryLayer;
    }

    /**
     * Changes the terrain provider of the
     * @param url {String}
     * @returns {Promise<void>}
     * @example
     * changeTerrain('url')
     */
    async changeTerrain(url) {
        try {
            this.viewer.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
                url, {
                    requestVertexNormals: true,
                });
        } catch (error) {
            console.warn("Failed to load terrain provider:", error);
            this.viewer.terrainProvider = await new Cesium.EllipsoidTerrainProvider();
        }
    }

    /**
     * Creates a 3D model on the globe.
     * @param url {String}
     * @param lon {number}
     * @param lat {number}
     * @returns {void}
     * @example
     * createModel('/assets/lowpoly-tree.glb', 126.978388, 37.566610)
     */
    createModel(url = tree, lon, lat) {
        return this.viewer.entities.add({
            name: "Model",
            position: Cesium.Cartesian3.fromDegrees(lon, lat, 0),
            model: {
                uri: url,
                scale: 1.0,
                heightReference: Cesium.HeightReference.CLAMP_TO_TERRAIN,
            },
        });
    }

    destroy() {
        this.viewer.destroy();
    }
}