Mejorando la Experiencia del Visor Cesium: Control de FPS, Iluminación y Gestión de Capas Base

En el desarrollo de aplicaciones WebGIS con CesiumJS, la creación de un visor tridimensional es solo el primer paso. Para que una API o SDK sea verdaderamente útil y fácil de manejar, debe ofrecer funcionalidades adicionales que simplifiquen tareas comunes y mejoren la experiencia del desarrollador. Este artículo detalla cómo enriquecer un visor Cesium con capacidades esenciales como el control del rendimiento (FPS), la gestión de la iluminación, la obtención de dimensiones del lienzo y la manipulación de la capa base de imágenes.

  1. Gestión Dinámica de la Tasa de Cuadros (FPS)

La visualización de la tasa de cuadros por segundo (FPS) es crucial para la depuración y optimización del rendimiento en aplicaciones 3D. Cesium ofrece una propiedad para esto, pero integrarla directamente en nuestro SDK permite una interfaz más limpia.

Podemos añadir una propiedad mostrarIndicadorFPS a nuestra clase principal del visor (por ejemplo, VisorGeoespacial) que actúe como un cómodo getter y setter:

// src/core/VisorGeoespacial.ts
/**
* Controla la visibilidad del indicador de FPS en la esquina superior derecha.
* @type {boolean}
*/
public get mostrarIndicadorFPS(): boolean {
 return this.cesiumViewer.scene.debugShowFramesPerSecond;
}

public set mostrarIndicadorFPS(activar: boolean) {
 this.cesiumViewer.scene.debugShowFramesPerSecond = activar;
}

Uso práctico:

const visor = new GeoVisorSDK.VisorGeoespacial("idContenedor");
visor.mostrarIndicadorFPS = true; // Activa el indicador de FPS
console.log(`Estado del FPS: ${visor.mostrarIndicadorFPS ? 'Activado' : 'Desactivado'}`); // Muestra el estado actual

  1. Control Unificado de Iluminación y Sombras

Para dotar al globo terráqueo de un realismo visual con variaciones de luz y sombra, Cesium requiere la activación de dos propiedades distintas: globe.enableLighting y shadows. Unificar esto bajo una única propiedad simplifica enormemente su control.

Implementaremos un setter para una propiedad como habilitarEfectosLumincos que configure ambas opciones simultáneamente:

// src/core/VisorGeoespacial.ts
/**
* Habilita o deshabilita la iluminación del globo terráqueo y la proyección de sombras
* para un mayor realismo visual.
* @type {boolean}
*/
public set habilitarEfectosLumincos(activar: boolean) {
 this.cesiumViewer.scene.globe.enableLighting = activar;
 this.cesiumViewer.shadows = activar;
}

Uso práctico:

visor.habilitarEfectosLumincos = true;  // Activa la iluminación realista y las sombras
visor.habilitarEfectosLumincos = false; // Desactiva estos efectos, lo cual puede mejorar el rendimiento

Nota: La iluminación mejora la percepción del relieve, pero puede demandar más recursos gráficos. En dispositivos con menos potencia, es recomendable mantenerla desactivada.

  1. Obtención de las Dimensiones del Lienzo del Mapa

Conocer el tamaño actual del lienzo de Cesium (ancho y alto) es útil para diversas operaciones, como la adaptación de elementos de UI o la preparación para capturas de pantalla. Podemos exponer esta información a través de un método de nuestra clase.

// src/core/VisorGeoespacial.ts
/**
* Retorna las dimensiones actuales (ancho y alto) del lienzo HTML del visor Cesium.
* @returns {{ancho: number, alto: number}} Objeto con las dimensiones en píxeles.
*/
public obtenerDimensionesLienzo(): { ancho: number; alto: number } {
 const { width, height } = this.cesiumViewer.canvas;
 return { ancho: width, alto: height };
}

Uso práctico:

const dimensiones = visor.obtenerDimensionesLienzo();
console.log(`Dimensiones del mapa: ${dimensiones.ancho}px de ancho, ${dimensiones.alto}px de alto`); // Ej: { ancho: 1920, alto: 1080 }

  1. Captura del Escenario del Mapa a Base64

La funcionalidad de exportar la vista actual del mapa como una imagen Base64 es indispensable para generar reportes, compartir estados o realizar pruebas automatizadas. Es importante asegurar que la escena se renderice completamente antes de la captura para evitar imágenes en negro.

// src/core/VisorGeoespacial.ts
/**
* Captura la vista actual del mapa y la devuelve como una cadena Base64 en el formato especificado.
* Fuerza una renderización de un solo cuadro antes de la captura para asegurar que la imagen no esté vacía.
* @param {string} formato - El formato de imagen a generar ('image/png', 'image/jpeg', etc.). Por defecto es 'image/png'.
* @returns {string} La imagen capturada en formato Base64.
*/
public capturarPantallaBase64(formato: string = 'image/png'): string {
 this.cesiumViewer.render(); // Fuerza una renderización para que el lienzo esté actualizado
 return this.cesiumViewer.scene.canvas.toDataURL(formato);
}

Uso práctico:

const imagenBase64 = visor.capturarPantallaBase64();
const elementoImg = document.createElement('img');
elementoImg.src = imagenBase64;
document.body.appendChild(elementoImg); // Añade la imagen capturada al DOM

  1. Gestión Flexible de la Capa Base de Imágenes

Cambiar la capa base de imágenes en Cesium a menudo implica varios pasos: identificar la capa actual, removerla y luego añadir la nueva capa asegurándose de que se coloque en la parte inferior de la pila de capas. Simplificamos este proceso con una propiedad que abstrae la complejidad.

// src/core/VisorGeoespacial.ts
import * as Cesium from 'cesium'; // Importa Cesium para los tipos de ImageryLayer

/**
* Obtiene o establece la capa base de imágenes del visor.
* La capa base se gestiona internamente para asegurar que siempre esté en la parte inferior de la pila.
* @type {Cesium.ImageryLayer | undefined}
*/
public get capaBaseActual(): Cesium.ImageryLayer | undefined {
 const capas = this.cesiumViewer.imageryLayers;
 if (capas.length > 0) {
   // La capa base es por convención la primera en la colección después de usar lowerToBottom
   return capas.get(0);
 }
 return undefined;
}

public set capaBaseActual(nuevaCapa: Cesium.ImageryLayer) {
 const capas = this.cesiumViewer.imageryLayers;
 const capaExistente = this.capaBaseActual;

 if (capaExistente) {
   // Elimina la capa base actual si existe, sin destruirla (false) para posible reutilización
   capas.remove(capaExistente, false);
 }

 // Añade la nueva capa y la asegura en la parte inferior de la pila
 capas.add(nuevaCapa);
 capas.lowerToBottom(nuevaCapa);
}

Uso práctico:

// Ejemplo: Crear y asignar una nueva capa de TianDiTu como capa base
const proveedorTianditu = new Cesium.WebMapTileServiceImageryProvider({
 url: "http://t0.tianditu.gov.cn/img_w/wmts?tk=SU_CLAVE_DE_APLICACION_AQUI", // ¡Reemplaza con tu clave de TiandiTu!
 layer: "img",
 style: "default",
 format: "tiles",
 tileMatrixSetID: "w",
 maximumLevel: 18,
});
const capaTianditu = new Cesium.ImageryLayer(proveedorTianditu);

// Asigna la nueva capa como capa base con una sola línea de código
visor.capaBaseActual = capaTianditu;

Asegúrate de cumplir con los términos de uso de los servicios de mapas de terceros, como TianDiTu, incluyendo el uso de claves de aplicación si son requeridas.

Ejemplo de Prueba Local con dat.GUI

Para ver estas funcionalidades en acción, puedes adaptar el siguiente ejemplo HTML. Asegúrate de tener las librerías CesiumJS y dat.gui.min.js, así como tu SDK (por ejemplo, compilado como geovisorsdk.umd.js) correctamente enlazadas en las rutas adecuadas.


<html lang="es">
<head>
 <meta charset="UTF-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <title>Demostración de Funcionalidades Extendidas del Visor Geoespacial</title>
 <script src="./Cesium/Cesium.js"></script>
 <link rel="stylesheet" href="./Cesium/Widgets/widgets.css" />
 <!-- Asegúrate de que esta ruta sea correcta para tu SDK -->
 <script src="./lib/geovisorsdk.umd.js"></script>
 <script src="./assets/dat.gui.min.js"></script>
 <style>
   html, body {
     margin: 0;
     padding: 0;
     width: 100%;
     height: 100%;
     overflow: hidden; /* Evita barras de desplazamiento */
   }
   #contenedorCesium {
     width: 100%;
     height: 100%;
     position: relative;
   }
   #panelGUI {
     position: absolute;
     left: 5px;
     top: 5px;
     z-index: 100; /* Asegura que esté por encima del visor */
     display: flex;
     flex-direction: column;
   }
   #captura-previa {
       max-width: 250px;
       border: 1px solid #999;
       margin-top: 10px;
   }
 </style>
</head>
<body>
 <div id="contenedorCesium">
   <div id="panelGUI"></div>
 </div>
 <script>
   // Asumiendo que GeoVisorSDK.VisorGeoespacial es tu clase principal del SDK
   const visor = new GeoVisorSDK.VisorGeoespacial("contenedorCesium");

   inicializarInterfazGUI();

   function inicializarInterfazGUI() {
     const parametrosControl = {
       tituloPanel: "Configuración del Visor",
       mostrarFPS: false,
       iluminacionActiva: false,
       dimensionesLienzo: "Ancho:--, Alto:--",
       obtenerDimensiones: function () {
         const { ancho, alto } = visor.obtenerDimensionesLienzo();
         parametrosControl.dimensionesLienzo = `Ancho:${ancho}, Alto:${alto}`;
         controlDimensiones.updateDisplay();
       },
       generarCaptura: function () {
         const contenedorGUI = document.getElementById("panelGUI");
         let elementoCapturaExistente = document.getElementById("captura-previa");
         if (elementoCapturaExistente) {
           elementoCapturaExistente.remove();
         }

         const imagenBase64 = visor.capturarPantallaBase64();
         const img = document.createElement("img");
         img.id = "captura-previa";
         img.src = imagenBase64;
         contenedorGUI.appendChild(img);
       },
       // Aquí se podría añadir control para la capa base, si es necesario,
       // usando 'visor.capaBaseActual = nuevaCapa;'
     };

     const gui = new dat.GUI({ autoPlace: false });
     const customContainer = document.getElementById("panelGUI");
     customContainer.appendChild(gui.domElement);

     gui.add(parametrosControl, "tituloPanel");

     const controlFPS = gui.add(parametrosControl, "mostrarFPS");
     controlFPS.onFinishChange((valor) => {
       visor.mostrarIndicadorFPS = valor;
     });

     const controlIluminacion = gui.add(parametrosControl, "iluminacionActiva");
     controlIluminacion.onFinishChange((valor) => {
       visor.habilitarEfectosLumincos = valor;
     });

     const controlDimensiones = gui.add(parametrosControl, "dimensionesLienzo");
     gui.add(parametrosControl, "obtenerDimensiones");
     gui.add(parametrosControl, "generarCaptura");
   }
 </script>
</body>
</html>

Etiquetas: CesiumJS WebGIS JavaScript TypeScript 3D Globe

Publicado el 7-21 18:35