Source: MagoViewer.js

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

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

    /**
     * Tests the MagoViewer class.
     * draws points on the globe.
     * @function
     * @returns {void}
     * @example
     * const magoViewer = new MagoViewer(viewer);
     * magoViewer.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
     * magoViewer.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
     * @returns {void}
     * @example
     * magoViewer.flyTo(126.978388, 37.566610, 10000)
     */
    flyTo(lon, lat, height) {
        this.viewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(lon, lat, 1000.0),
            orientation: {
                heading: Cesium.Math.toRadians(0.0),
                pitch: Cesium.Math.toRadians(-90.0)
            },
            duration: 0
        })
    }

    /**
     * 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
     * magoViewer.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),
                //distanceDisplayCondition : new Cesium.DistanceDisplayCondition(0, 1000000.0)
            },
            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
     * magoViewer.addPoint(126.978388, 37.566610, "#ff0000")
     */
    addRandomPoint(cssColor = "#ff0000", entitiesCollection = null) {
        let randomX = (Math.random() * 360) - 180;
        let 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
     * magoViewer.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
     * magoViewer.changeGlobeColor("#ff0000")
     */
    changeGlobeColor(cssColor) {
        this.viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString(cssColor);
    }

    /**
     * Creates a grid imagery provider on the globe.
     * @returns {void}
     * @example
     * magoViewer.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);
        //layers.raiseToTop(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
     * magoViewer.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',
            //minimumLevel: 1,
            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
     * magoViewer.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) {
        const model = 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();
    }
}