---
title: "How do I render directly to the Cesium viewer from a Plugin?"
section: "Web Navigator"
route: /faqcesiumrenderinginplugins
account: {accountId}
bruce_api: https://{accountId}.api.nextspace.host
guardian_api: https://guardian.nextspace.host
---
# How do I render directly to the Cesium viewer from a Plugin?

Not every graphic is an Entity. A coverage overlay, a site boundary, a route line, a heads-up label, a debug grid: these are decoration, they are not records, and they should not be selectable, styled, isolated or hidden by Navigator's own controls.

For those, draw straight onto the Cesium viewer. A Plugin is handed the live `viewer` and the whole `Cesium`namespace, so anything the CesiumJS documentation describes is available. What follows is the part that is specific to Navigator.

## Three rules

**Request a render.** Navigator runs Cesium on demand rather than at a fixed frame rate, so the scene is only redrawn when something asks for it. Call: `viewer.scene.requestRender()`to refresh the Cesium view.

**Dispose everything.** A Plugin's dispose function is the only cleanup that runs. Anything added to `viewer.entities`,`viewer.scene.primitives` or`viewer.dataSources` outlives the Plugin otherwise, and the user has no way to remove it short of reloading.

**Stay out of the register.** Do not add decoration through the Menu Item manager or the visual register. Those exist to map graphics back to Entity records, and a graphic registered there becomes selectable.

## Choosing where to add it

Cesium offers three surfaces and the choice matters more for a Plugin than for an app, because a Plugin shares the scene with everything Navigator has already drawn.

| Surface | When to use it |
| --- | --- |
| viewer.entities | A handful of graphics, described declaratively. Points, labels, billboards, polylines, polygons, models. The easiest surface, and the one to reach for first. These are Cesium entities and have nothing to do with Nextspace Entities. |
| viewer.dataSources | A named group you want to show, hide or remove as one, or content loaded from GeoJSON, KML or CZML. Add a CustomDataSource and put your entities in it, then removing the data source removes the lot. |
| viewer.scene.primitives | Volume, or a custom appearance or shader. Thousands of markers through a billboard or label collection, a ground overlay, geometry you have batched yourself. More work per graphic and much cheaper per graphic. |

**A data source group**

```javascript
/**
 * Background Plugin that draws a boundary and a set of labels as one
 * removable group.
 */
async function Run(params) {
    const { viewer, Cesium } = params.pluginParams;

    const source = new Cesium.CustomDataSource("MyPluginOverlay");
    await viewer.dataSources.add(source);

    const ring = params.plugin.Settings?.boundary;

    source.entities.add({
        polygon: {
            hierarchy: new Cesium.PolygonHierarchy(
                Cesium.Cartesian3.fromDegreesArray(ring)
            ),
            material: Cesium.Color.fromCssColorString("#33B1FF").withAlpha(0.25),
            // Drapes over terrain and tilesets rather than cutting through them.
            classificationType: Cesium.ClassificationType.BOTH
        },
        polyline: {
            positions: Cesium.Cartesian3.fromDegreesArray(ring),
            width: 3,
            material: Cesium.Color.fromCssColorString("#33B1FF"),
            clampToGround: true
        }
    });

    viewer.scene.requestRender();

    return () => {
        viewer.dataSources.remove(source, true);
        viewer.scene.requestRender();
    };
}
```

**Many markers, cheaply**

```javascript
async function Run(params) {
    const { viewer, Cesium, BModels } = params.pluginParams;

    const labels = viewer.scene.primitives.add(new Cesium.LabelCollection());
    const points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection());

    const { entities } = await BModels.Entity.GetList({
        filter: {
            entityTypeId: params.plugin.Settings?.entityTypeId,
            pageSize: 5000
        }
    });

    entities.forEach((entity) => {
        const loc = entity.Bruce?.Location;
        if (!loc) {
            return;
        }
        const pos = Cesium.Cartesian3.fromDegrees(
            loc.longitude, loc.latitude, loc.altitude
        );
        points.add({
            position: pos,
            pixelSize: 8,
            color: Cesium.Color.ORANGE
        });
        labels.add({
            position: pos,
            text: String(entity.Bruce?.Name ? entity.Bruce.Name : ""),
            font: "12px sans-serif",
            distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 400)
        });
    });

    viewer.scene.requestRender();

    return () => {
        viewer.scene.primitives.remove(labels);
        viewer.scene.primitives.remove(points);
        viewer.scene.requestRender();
    };
}
```

**Redrawing on camera move**

```javascript
function Run(params) {
    const { viewer, Cesium, menuItemManager } = params.pluginParams;

    const group = new Cesium.PrimitiveCollection();
    viewer.scene.primitives.add(group);

    /**
     * Rebuilds the overlay for the current camera.
     */
    function redraw() {
        group.removeAll();
        const height = viewer.camera.positionCartographic.height;
        console.log("Rebuilding overlay at " + Math.round(height) + "m");

        // Build primitives into 'group' here.

        viewer.scene.requestRender();
    }

    const unsubscribe = menuItemManager.Monitor.Updated().Subscribe(() => {
        redraw();
    });

    redraw();

    return () => {
        unsubscribe?.();
        viewer.scene.primitives.remove(group);
        viewer.scene.requestRender();
    };
}
```

**A value that changes**

```javascript
function Run(params) {
    const { viewer, Cesium } = params.pluginParams;

    let position = Cesium.Cartesian3.fromDegrees(174.76, -36.85, 20);

    const marker = viewer.entities.add({
        // The second argument declares the value is not constant.
        position: new Cesium.CallbackProperty(() => position, false),
        point: {
            pixelSize: 14,
            color: Cesium.Color.CYAN,
            // Draws over geometry in front of it, so a marker is
            // never lost inside a model.
            disableDepthTestDistance: Number.POSITIVE_INFINITY
        }
    });

    // Moving it is now one assignment plus one render request.
    const timer = setInterval(() => {
        const carto = Cesium.Cartographic.fromCartesian(position);
        position = Cesium.Cartesian3.fromRadians(
            carto.longitude + Cesium.Math.toRadians(0.0001),
            carto.latitude,
            carto.height
        );
        viewer.scene.requestRender();
    }, 100);

    return () => {
        clearInterval(timer);
        viewer.entities.remove(marker);
        viewer.scene.requestRender();
    };
}
```

## Things that will bite

**Do not destroy the viewer, or replace its terrain provider, imagery layers, camera controller or clock.** Navigator owns those, and a Plugin changing one changes the whole scene for every other surface. Where a Plugin genuinely needs a global change, restore the previous value in its dispose function.

**Check the viewer is alive before touching it.** A Plugin can be disposed while an asynchronous read is in flight, and the Project View can be torn down under it. Guard the resume path.

```javascript
// Both checks, because a destroyed viewer throws on nearly every property.
if (!viewer || viewer.isDestroyed()) {
    return;
}
```

**Prefer clamping to guessing a height.**`clampToGround` for lines and `classificationType` for areas let Cesium drape the graphic over whatever is actually there. Computing a height yourself puts the graphic underground as soon as the scene has terrain or a tileset the Plugin did not expect.

**Give collections a stable identity.** Where a Plugin can be loaded more than once, in two Project Views or as both a cursor tool and a background script, name its data source and check for an existing one before adding a second.

---

Urls on this page are resolved for account `{accountId}`.
Site index: https://docs.nextspace.host/llms.txt · whole site in one file: https://docs.nextspace.host/llms-full.txt
Human-readable version of this page: https://docs.nextspace.host/faqcesiumrenderinginplugins
