Adding Cesium app to Plotly Dash

Hi,

I’m trying to integrate Cesium into my Plotly Dash application but it doesn’t render the Cesium app in Plotly Dash when I run this locally from my Jupyter notebook. My python code is as follows:

import dash
import dash_html_components as html
external_css = ['https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Widgets/widgets.css']

app = dash.Dash(__name__, 
                title='Cesium Test',
                external_stylesheets=external_css)

app.layout = html.Div(id='blah',
                      children=[
                          'Testing...',
                          html.Script(src='https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Cesium.js'),
                          html.Div(id='cesiumContainer'),
                          html.Script('''
          Cesium.Ion.defaultAccessToken = 'any_code_works';
          var viewer = new Cesium.Viewer('cesiumContainer');
                          ''')
                      ])

if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)

Here is a working example (Click on the “Show” button to see it renders the Cesium app):

html.Script tags are not executed by browsers when their code is added dynamically. There’s been some discussion about removing this and other components entirely (see eg here) but for now we’re following the lead of React and keeping it, despite it not working as you might expect.

My suggestion would be:
(1) add Cesium.js to external_scripts
(2) create a dummy clientside callback - based on cesiumContainer so you know it’ll only trigger when the container has been created - that instantiates the viewer. Something like:

app.clientside_callback(
    '''
    function(id) {
        Cesium.Ion.defaultAccessToken = 'any_code_works';
        var viewer = new Cesium.Viewer(id);
        return true;
    }
    ''',
    Output('cesiumContainer', 'data-done'),
    Input('cesiumContainer', 'id')
)

This works! Thanks! Here’s my updated code:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output

external_css = ['https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Widgets/widgets.css']
external_scripts = [{'src':'https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Cesium.js'}]

app = dash.Dash(__name__, 
                title='Cesium Test',
                external_scripts=external_scripts,
                external_stylesheets=external_css)

app.layout = html.Div(id='blah',
                      children=[
                          'Testing...',
                          html.Div(id='cesiumContainer')
                      ])

app.clientside_callback(
    '''
    function(id) {
        Cesium.Ion.defaultAccessToken = "any_code_works";
        var viewer = new Cesium.Viewer(id);
        return true;
    }
    ''',
    Output('cesiumContainer', 'data-done'),
    Input('cesiumContainer', 'id')
)

if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)

@nmill - are you able to pass data into cesium this way? If so, how?

@dcoley yes. I worked with CZML files. You can see working examples here:

https://oc-dash.herokuapp.com

And the source code is here:

Wow that’s awesome. Thanks for sharing. I really like functionality of the intercepts tab. Definitely looking into Cesium now.

Thanks

So cool!

2 Likes

I initially had success with the code above, but after a few tries it seems like it’s taking a long time to load and the cesium view doesn’t appear anymore.

Loading…and no respon. Any suggestion? please

import dash
from dash import html
from dash.dependencies import Input, Output

Adding Cesium app to Plotly Dash - #7 by chriddyp

#https://github.com/mads-hatters/SIADS-591-Orbital-Congestion/tree/main/dashboard

external JavaScript files

external_css = [
https://cesium.com/downloads/cesiumjs/releases/1.84/Build/Cesium/Widgets/widgets.css’]
external_scripts = [
{‘src’: ‘https://cesium.com/downloads/cesiumjs/releases/1.84/Build/Cesium/Cesium.js’}]

app = dash.Dash(name,
title=‘Cesium Exercise’,
external_scripts=external_scripts,
external_stylesheets=external_css)
app.title = “3D Geospatial Solution v.0.0 - Hermawan Apps”
app._favicon = “icon1_hmw.png”

app.layout = html.Div(id=‘blah’, children=[‘3D2 Testing…’,
html.Div(id=‘cesiumContainer’)])

app.clientside_callback(
‘’’
function(id) {
Cesium.Ion.defaultAccessToken = “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkZGE3ZWZmYy0yMGM2LTRkMDItOTRkYi00YTQwYTZhMGEzM2UiLCJpZCI6MzI1NDIsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1OTcxNDg0NTF9.C3Oke_AXRizympHgs5uQw0DMtXFoZuQvfD3WdtdESgA”;
var viewer = new Cesium.Viewer(id);
return true;
}
‘’',
Output(‘cesiumContainer’, ‘data-done’),
Input(‘cesiumContainer’, ‘id’)
)

if name == ‘main’:
app.run(debug=True)

Thank you @alexcjohnson and @nmill this is a great solution and works! But the “var viewer = new Cesium.Viewer(id)” generates a view of the entire globe. I added lines of code to generate a view that flies to specific coordinates, and it worked. However, when I tried to call a specific Cesium tileset, it does not work. Below is the code that doesn’t work in Dash (received error: “Cannot read properties of undefined (reading ‘428f37966fc6dfc9715770e145fa65e3’)”). FYI, when I write the code blow as an html file and read it into a Flask app, it works well, I’m just trying to make it work in a Dash callback. Any help would be greatly appreciated.

external_css = ['https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Widgets/widgets.css']
external_scripts = [{'src':'https://cesium.com/downloads/cesiumjs/releases/1.76/Build/Cesium/Cesium.js'}]

app = dash.Dash(__name__, 
                title='Cesium Test',
                external_scripts=external_scripts,
                external_stylesheets=external_css)

app.layout = html.Div(id='blah', children=[
                          'Testing...',
                          html.Div(id='cesiumContainer')
                      ])

app.clientside_callback(
    '''
    function(id) {
        Cesium.Ion.defaultAccessToken = "ENTER_DEFAULT_TOKEN"


        const viewer = new Cesium.Viewer(id, {
            timeline: false,
            animation: false,
            sceneModePicker: false,
            baseLayerPicker: false,
        }); 


        // Add tileset - everything works except this
        try {
            const tileset = await Cesium.createGooglePhotorealistic3DTileset();
            viewer.scene.primitives.add(tileset);
        } catch (error) {
            console.log(`Error loading Photorealistic 3D Tiles tileset.
            ${error}`);
        }

        viewer.camera.flyTo({
        destination: Cesium.Cartesian3.fromDegrees(-75.1652, 39.9526, 400),
        orientation: {
            heading: Cesium.Math.toRadians(0.0),
            pitch: Cesium.Math.toRadians(-15.0),
        }
        });
        
        return true;
    }
    ''',
    Output('cesiumContainer', 'data-done'),
    Input('cesiumContainer', 'id')
)

if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)