CesiumJS Pins Created Dynamically Not Updating in Real-Time in Viewer

I have encountered a problem while working on a CesiumJS-based map application. I have a custom library that utilizes CesiumJS to create pins on the map. These pins are created successfully, and I am able to obtain latitude and longitude values dynamically by setting up a setPinCreationCallback function. However, despite the pins being created successfully, they are not reflecting in the viewer immediately.

**Pin Manager Class**

    import * as Cesium from "cesium";
import { createPin } from "./utils/PinUtility";
import {
  PinManagerInterface,
  PinOptions,
  PinCreationCallback,
} from "./interfaces/PinManagerInterfaces";
import { ViewerInterface } from "./interfaces/RuasViewerInterfaces";

export class PinManager implements PinManagerInterface {
  private _pinBuilder: Cesium.PinBuilder;
  private _isHandlerAttached: boolean = false;
  private _clickHandler: Cesium.ScreenSpaceEventHandler | null = null;
  private _loadedPins: Cesium.Entity[] = [];
  private _pinCreationCallback: PinCreationCallback | null = null;

  constructor() {
    this._pinBuilder = new Cesium.PinBuilder();
  }

  public enablePinCreation(ruasViewer: ViewerInterface): void {
    if (!this._isHandlerAttached) {
      this._clickHandler = new Cesium.ScreenSpaceEventHandler(
        ruasViewer.getViewer().scene.canvas
      );

      this._clickHandler.setInputAction((movement: any) => {
        this.handlePinCreation(ruasViewer, movement);
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

      this._isHandlerAttached = true;
    }
  }

  public disablePinCreation(): void {
    if (this._isHandlerAttached && this._clickHandler !== null) {
      this._clickHandler.destroy();
      this._isHandlerAttached = false;
    }
  }

  // Method to create pins for an array of locations
  public createPins(ruasViewer: ViewerInterface, locations: PinOptions[]): void {
    // Clear existing pins before adding new ones
    this.clearPins(ruasViewer);
    locations.forEach((location) => {
      const pinEntity = createPin(
        ruasViewer.getViewer(),
        this._pinBuilder,
        location
      );
      this._loadedPins.push(pinEntity);
      ruasViewer.requestRender();
    });
  }

  // Method to clear all pins from the viewer
  public clearPins(ruasViewer: ViewerInterface): void {
    // Remove pins from the viewer's entities collection
    this._loadedPins.forEach((pin) => {
      ruasViewer.getViewer().entities.remove(pin);
    });
    // Clear the internal data structures
    this._loadedPins = [];
  }

  public setPinCreationCallback(callback: PinCreationCallback): void {
    this._pinCreationCallback = callback;
  }

  private notifyPinCreation(latitude: number, longitude: number): void {
    if (this._pinCreationCallback) {
      this._pinCreationCallback(latitude, longitude);
    }
  }

  public handlePinCreation(ruasViewer: ViewerInterface, movement: any): void {
    // Check if movement contains a valid position
    if (movement && movement.position) {
      const windowPosition = movement.position;
      const ray = ruasViewer.getViewer().camera.getPickRay(windowPosition);

      // Ensure ray is defined before proceeding
      if (Cesium.defined(ray)) {
        const clickedPosition = ruasViewer
          .getViewer()
          .scene.globe.pick(ray, ruasViewer.getViewer().scene);

        if (Cesium.defined(clickedPosition)) {
          // Notify the callback function with clickedPosition
          const cartographicPosition =
            Cesium.Cartographic.fromCartesian(clickedPosition);
          const latitude = Cesium.Math.toDegrees(cartographicPosition.latitude);
          const longitude = Cesium.Math.toDegrees(
            cartographicPosition.longitude
          );

          this.notifyPinCreation(latitude, longitude);

          const pinOptions: PinOptions[] = [{ latitude, longitude }];

          // Clear existing pins and create new pins based on pinOptions
          this.createPins(ruasViewer, pinOptions);
          // Request a render update to see the changes
          ruasViewer.getViewer().scene.requestRender();
        }
      }
    } else {
      console.error("Invalid movement or position");
    }
  }
}
**PinsViewer Component:**

// Import necessary modules and dependencies
import React, { useEffect, useRef } from "react";
import { useDispatch } from "react-redux";
import { setViewer } from "../../redux/features/viewer/viewerSlice";
import { useCesiumViewer } from "../../hooks/useCesiumViewer";
import { Location } from "../../interfaces/project.interface";

// Define the props expected by the PinsViewer component
interface PinsViewerProps {
  location?: Location[];
  setLocation?: React.Dispatch<React.SetStateAction<Location>>; // Function to set location state
}

// Create the PinsViewer React functional component
const PinsViewer: React.FC<PinsViewerProps> = ({ location, setLocation }) => {
  // Initialize Redux dispatch
  const dispatch = useDispatch();

  // Create a reference to the viewer container div
  const viewerRef = useRef<HTMLDivElement>(null); // Unique ID for the viewer container

  // Use the useCesiumViewer hook to obtain the viewer and pinManager
  const { viewer, pinManager } = useCesiumViewer("pinsViewer");

  // Set up the effect to manage pins and update the viewer
  useEffect(() => {
    if (viewer && pinManager) {
      if (location?.length && location[0].latitude && location[0].longitude) {
        pinManager.createPins(viewer, location);
        pinManager.enablePinCreation(viewer);
        // Create pins and set up a pin creation callback
        pinManager.setPinCreationCallback(
          (latitude: number, longitude: number) => {
            pinManager.createPins(viewer, [{ latitude, longitude, height: 0 }]);
            if (setLocation) {
              setLocation({ ...location, latitude, longitude, height: 0 });
            }
          },
        );
      } else if (location) {
        // location.latitude is 0
        pinManager.enablePinCreation(viewer);
        pinManager.setPinCreationCallback(
          (latitude: number, longitude: number) => {
            if (setLocation) {
              setLocation({ ...location, latitude, longitude, height: 0 });
            }
          },
        );
      }
    }

    // Dispatch the initialized viewer to Redux state
    dispatch(setViewer(viewer));
  }, [viewer, pinManager, location, setLocation, dispatch]);

  return <div ref={viewerRef} id="pinsViewer"></div>;
};

export default PinsViewer;

Problem: In my PinsViewer component, I’m using the pinManager.setPinCreationCallback function to create pins dynamically when the user clicks on the map. The pins are created successfully, and the latitude and longitude values are obtained correctly. However, these pins are not updating in the viewer in real-time. The viewer only updates when I shake the map or move the Earth manually.

Expected Behavior:

I expect the pins to be updated in the viewer immediately after they are created, without the need for manual interaction like shaking the map or moving the Earth

Attempts to Solve:

  1. Manual Viewer Update: I tried calling viewer.scene.requestRender() after creating pins, but it doesn’t solve the problem.
  2. Checked Internal Implementation: I verified the internal implementation of the createPins function in my library. The pins are created successfully, but the viewer doesn’t reflect the changes instantly.

Question:

Is there a specific method or update function in CesiumJS that I should be using to ensure that the pins created in my library are immediately reflected in the viewer without requiring manual interactions? Additionally, is there any specific configuration or setup I might be missing in my React application, especially related to integrating CesiumJS using webpack?

I’m looking for a solution that enables real-time updates in the viewer as soon as the pins are created.

Any help or guidance on this issue would be greatly appreciated. Thank you!

Cesium & Webpack Web worker Not updating the viewer I found this question which seems similar to my problem.

Hi there,

Maybe I missed it, but would you mind including the createPin and setLocation functions?

You’ll likely want to be using a callbackProperty to update the pin locations.

Hi @Gabby_Getz , This issue is resolved.
this._viewer = new Cesium.Viewer(containerId, {
terrain: Cesium.Terrain.fromWorldTerrain(),
requestRenderMode: false,
maximumRenderTimeChange: Infinity,
…options,
});
I made one change, by changing requestRenderMode from true to false, my issue is fixed. Just wanted to understand how this worked. It would be helpful give you give me any insights.

Thanks for the update @pratiksingh!

Please review our blog post “Improving Performance with Explicit Rendering” for more info on request render mode.