Search code examples
javascriptreactjsdomgetelementbyidcesiumjs

How can I get an instance of the Cesium viewer from document?


I have tried using

var viewer = document.getElementById("cesiumContainer")

but this doesn't return the actual viewer object. What's the correct approach to get an instance of/reference to the viewer that's been created already. I'm trying to get access to it in a part of my code that is out of scope with the part where I created the viewer, if that wasn't obvious.

The viewer is being created using the resium package for a react app with <Viewer>. The HTML page that renders still wraps the viewer in a div with the id cesiumContainer and from inspecting the source in dev tools it looks the same as it would without react, so I would think it would behave the same in terms of being able to access it. Of course that may not be the case.


Solution

  • This answer focuses on your use of Resium, Cesium and class based React Components.

    If you are using React 16+ you can use Context to achieve your goal.

    1. First create a context that will be used as your singleton reference. The state and React Context are exported.
    CesiumContext.js
    export const state = {viewer: '', setInstance: (ref)=>{state.viewer = ref;}};
    export default CesiumContext;
    
    1. In every component you want to have a reference to the Cesium instance, set the class property contextType to the React Contact reference. (CesiumContext). In the ViewerComponent constructor use createRef(). It will capture the only prop (ref) that will allow the Context state to be set, thereby allowing all other components access to the reference. Note the use of componentDidMount() to set the state of the context to there reference provided by Resium.
    ViewerComponent.js
    export default class ViewerComponent extends React.Component {
      constructor(props) {
        super(props);
        this.ref = createRef();
      }
      componentDidMount() {
        if (this.ref.current && this.ref.current.cesiumElement) {
          this.context.setInstance(this.ref.current.cesiumElement);
        }
      }
      render() {
        return <Viewer ref={this.ref}/>
      }
    }
    ViewerComponent.contextType = CesiumContext;
    
    1. In the App component, render the ContextProvider with the state of the context. Once the ViewComponent renders/mounts, and the Context state is set, then every Child component under the Context element has access to the current Cesium View object. There is a problem created by this structure in that in order for this to happen, the tree must be rerendered. Based upon your app you may have to force the rerendering.
    App.js
          <div className="App">
            <CesiumContext.Provider value={state}>
              <ViewerComponent/>
              <AComponent/>
              <BComponent/>
            </CesiumContext.Provider>
          </div>
    
    1. Finally, in any components requiring access to the shared resource (Cesium View instance in this case), set contextType on the class. In the componentDidMount grab the instance from the context.
    Any Component
    export default class AComponent extends React.Component {
      constructor() {
        super();
        this.viewer = false;
      }
    
      componentDidMount() {
        this.viewer = this.context.viewer;
      }
      render() {
        return <p>{/* do something with this.viewer */}</p>;
      }
    }
    AComponent.contextType = CesiumContext;
    

    A complete solution demonstrates this below. One component AComponent accesses the Cesium.View.shadowed property and 'BComponent` accesses the Cesium.View. allowDataSourcesToSuspendAnimation property.

    React Playground Solution