SuperAPI AI Directory

cesiumjs-viewer-setup

General & other

View repository

Updated 10-15 17:33

About this skill

CesiumJS Viewer & Scene Setup

Reference for bootstrapping CesiumJS applications: Viewer, CesiumWidget, Ion/GoogleMaps/ITwinPlatform configuration, widgets, factory helpers, geocoder services, viewer mixins, Credits, and related enums.

Quick Start

import { Viewer, ImageryLayer, OpenStreetMapImageryProvider } from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";

const viewer = new Viewer("cesiumContainer", {
  baseLayer: new ImageryLayer(new OpenStreetMapImageryProvider({
    url: "https://tile.openstreetmap.org/",
    maximumLevel: 18,
  })),
  baseLayerPicker: false,
});

Required HTML: <div id="cesiumContainer" style="width:100%;height:100vh"></div>

Use Cesium ion defaults (Terrain.fromWorldTerrain, ImageryLayer.fromWorldImagery, createOsmBuildingsAsync, Google Photorealistic 3D Tiles) only when the target runtime has the required ion or Google entitlement. For public/no-token examples, choose explicit public providers and URL-backed 3D Tiles.

Framing Loaded Content (Critical)

After adding a tileset, model, or data source, you must explicitly frame it. A Viewer constructed with default options starts the camera at a fixed view of Earth. It does NOT auto-zoom to primitives you add. Forgetting this is the most common cause of "I see only gray surface" or "the asset is a speck in the corner" failures.

const tileset = await Cesium3DTileset.fromUrl(url);
viewer.scene.primitives.add(tileset);

// Pick ONE of:
await viewer.zoomTo(tileset);                 // instant fit to bounding sphere
await viewer.flyTo(tileset, { duration: 0 }); // animated; duration 0 = instant

viewer.zoomTo / viewer.flyTo accept Entity, Entity[], EntityCollection, DataSource, ImageryLayer, Cesium3DTileset, VoxelPrimitive, Model, or TimeDynamicPointCloud. They use the target's bounding sphere, which works for tilesets with arbitrary local coordinate systems where computing an ECEF camera position by hand will miss the asset entirely.

When you do need an explicit camera position (e.g., to satisfy a heading/pitch specification), still use viewer.zoomTo(target, headingPitchRange) so the range is derived from the bounding sphere rather than guessed:

import { HeadingPitchRange, Math as CesiumMath } from "cesium";

await viewer.zoomTo(tileset, new HeadingPitchRange(
  CesiumMath.toRadians(45),   // heading
  CesiumMath.toRadians(-30),  // pitch
  /* range omitted -> auto-fit */
));

Ion & Platform Configuration

Cesium Ion

import { Ion } from "cesium";

Ion.defaultAccessToken = "YOUR_TOKEN";  // required for ion assets
Ion.defaultServer = "https://your-ion-server.example.com/"; // optional: self-hosted

IonResource

import { IonResource, Cesium3DTileset } from "cesium";

const resource = await IonResource.fromAssetId(96188);
const tileset = await Cesium3DTileset.fromUrl(resource);
viewer.scene.primitives.add(tileset);
await viewer.zoomTo(tileset);

Google Maps Platform

import { GoogleMaps, createGooglePhotorealistic3DTileset, Viewer, IonGeocodeProviderType } from "cesium";

GoogleMaps.defaultApiKey = "YOUR_GOOGLE_MAPS_API_KEY"; // optional: without key, served via ion

const viewer = new Viewer("cesiumContainer", {
  geocoder: IonGeocodeProviderType.GOOGLE, // required with Google 3D Tiles
});

const tileset = await createGooglePhotorealistic3DTileset({
  onlyUsingWithGoogleGeocoder: true,
});
viewer.scene.primitives.add(tileset);

iTwin Platform (experimental)

import { ITwinPlatform, ITwinData } from "cesium";

ITwinPlatform.defaultAccessToken = "YOUR_ITWIN_TOKEN";
const tileset = await ITwinData.createTilesetForIModel(viewer, "imodel-id");

// 1.140+ (#13208): Reality Data of type GaussianSplat3DTiles is now supported
const splats = await ITwinData.createTilesetForRealityDataId(
  iTwinId,
  realityDataId,
  ITwinPlatform.RealityDataType.GaussianSplat3DTiles,
);
viewer.scene.primitives.add(splats);

Viewer Constructor Options

new Viewer(container, options?) -- container is a DOM element or its string ID.

Widget Toggles

Option Default Purpose
animation true Playback controls
baseLayerPicker true Imagery/terrain switcher
fullscreenButton true Fullscreen toggle
vrButton false WebVR toggle
geocoder IonGeocodeProviderType.DEFAULT Search bar (false to hide)
homeButton true Reset to home view
infoBox true Entity info popup
sceneModePicker true 2D/3D/Columbus toggle
selectionIndicator true Selection reticle
timeline true Time scrubber
navigationHelpButton true Mouse/touch help
projectionPicker false Perspective/ortho toggle

Scene & Rendering

Option Default Purpose
sceneMode SceneMode.SCENE3D Initial scene mode
scene3DOnly false Lock to 3D, saves GPU memory per geometry instance
shadows false Shadow casting
terrainShadows ShadowMode.RECEIVE_ONLY Terrain shadow mode
requestRenderMode false Render only on changes
maximumRenderTimeChange 0.0 Max sim-time delta for render
msaaSamples 4 MSAA (1 to disable)
orderIndependentTranslucency true Translucent ordering
mapMode2D MapMode2D.INFINITE_SCROLL 2D scroll behavior

Pair requestRenderMode: true with scene3DOnly: true for low-power / dashboard apps; both are commonly required together when the scenario calls for GPU savings.

Layers & Terrain

Option Default Purpose
baseLayer ImageryLayer.fromWorldImagery() Base imagery (false for none; needs baseLayerPicker: false)
terrain none Async terrain helper (cannot combine with terrainProvider)
terrainProvider EllipsoidTerrainProvider Sync terrain provider
globe new Globe() false for no globe (space scenes)
skyBox auto (WGS84) false disables sky/sun/moon
skyAtmosphere auto (WGS84) false disables limb glow

Minimal Viewer (No Widgets)

import { Viewer, ImageryLayer, OpenStreetMapImageryProvider } from "cesium";

const viewer = new Viewer("cesiumContainer", {
  baseLayer: new ImageryLayer(new OpenStreetMapImageryProvider({
    url: "https://tile.openstreetmap.org/",
    maximumLevel: 18,
  })),
  animation: false, baseLayerPicker: false, fullscreenButton: false,
  geocoder: false, homeButton: false, infoBox: false,
  sceneModePicker: false, selectionIndicator: false,
  timeline: false, navigationHelpButton: false,
});

CesiumWidget (Lightweight Alternative)

No UI widgets, no Knockout dependency. Suitable for custom UIs or embedding.

import { CesiumWidget, Ion } from "cesium";
Ion.defaultAccessToken = "YOUR_TOKEN";

const widget = new CesiumWidget("cesiumContainer", { shouldAnimate: true });
// Exposes: widget.scene, widget.camera, widget.entities

SceneMode Enum

Value Description
SceneMode.SCENE3D Standard 3D globe (default)
SceneMode.SCENE2D Top-down orthographic map
SceneMode.COLUMBUS_VIEW 2.5D flat map with height
SceneMode.MORPHING Transitioning between modes
import { Viewer, SceneMode } from "cesium";

const viewer = new Viewer("cesiumContainer", { sceneMode: SceneMode.SCENE2D });
viewer.scene.morphTo3D(2.0);          // animated transition
viewer.scene.morphToColumbusView(2.0);

Scene Configuration

const scene = viewer.scene;
scene.globe.depthTestAgainstTerrain = true; // entities interact with terrain
scene.globe.enableLighting = true;          // sun-based lighting

// Key sub-objects
scene.camera;           // Camera
scene.primitives;       // PrimitiveCollection
scene.groundPrimitives; // PrimitiveCollection (ground-clamped)
scene.imageryLayers;    // ImageryLayerCollection
scene.postProcessStages;

scene.requestRender();  // trigger frame in requestRenderMode

Important: never touch scene.globe.* or scene.skyAtmosphere.* when the matching constructor option was set to false. Disabling these in the Viewer options leaves the corresponding property as undefined on the scene, and accessing .enableLighting, .depthTestAgainstTerrain, .show, or any other field throws TypeError: Cannot set properties of undefined. For space scenes, configure once in the constructor and do not mutate those properties afterward (see "Space Scene" below).

Factory Helpers

createOsmBuildingsAsync

import { createOsmBuildingsAsync, Cesium3DTileStyle } from "cesium";

// Default styling (colors from OSM tags)
const tileset = await createOsmBuildingsAsync();
viewer.scene.primitives.add(tileset);

// Custom style
const styled = await createOsmBuildingsAsync({
  style: new Cesium3DTileStyle({
    color: { conditions: [
      ["${feature['building']} === 'hospital'", "color('#0000FF')"],
      [true, "color('#ffffff')"],
    ]},
  }),
});

createGooglePhotorealistic3DTileset

import { createGooglePhotorealistic3DTileset, IonGeocodeProviderType } from "cesium";

// Must use Google geocoder
const viewer = new Viewer("cesiumContainer", { geocoder: IonGeocodeProviderType.GOOGLE });
const tileset = await createGooglePhotorealistic3DTileset({ onlyUsingWithGoogleGeocoder: true });
viewer.scene.primitives.add(tileset);

Terrain.fromWorldTerrain / fromWorldBathymetry

Preferred for the terrain constructor option. Non-blocking with error events.

import { Viewer, Terrain } from "cesium";

// World terrain with normals and water
const viewer = new Viewer("cesiumContainer", {
  terrain: Terrain.fromWorldTerrain({ requestVertexNormals: true, requestWaterMask: true }),
});

// Bathymetry (ocean floor)
const viewer2 = new Viewer("cesiumContainer", {
  terrain: Terrain.fromWorldBathymetry({ requestVertexNormals: true }),
});

Terrain Event Handling

import { Terrain, CesiumTerrainProvider } from "cesium";

const terrain = new Terrain(CesiumTerrainProvider.fromUrl("https://my-terrain.example.com"));
viewer.scene.setTerrain(terrain);

terrain.readyEvent.addEventListener((provider) => {
  viewer.scene.globe.enableLighting = true;
});
terrain.errorEvent.addEventListener((error) => console.error("Terrain failed:", error));

createWorldTerrainAsync / createWorldImageryAsync

Lower-level: return raw providers. Use when you need the provider directly.

import { createWorldTerrainAsync, createWorldImageryAsync, IonWorldImageryStyle } from "cesium";

const terrainProvider = await createWorldTerrainAsync({ requestVertexNormals: true });
viewer.terrainProvider = terrainProvider;

const imageryProvider = await createWorldImageryAsync({ style: IonWorldImageryStyle.AERIAL_WITH_LABELS });

IonWorldImageryStyle: AERIAL (default) | AERIAL_WITH_LABELS | ROAD

Geocoder Configuration

The geocoder option accepts false, an IonGeocodeProviderType, or a GeocoderService[].

IonGeocodeProviderType: DEFAULT | GOOGLE (required with Google tiles) | BING

import { Viewer, CartographicGeocoderService, IonGeocoderService, OpenCageGeocoderService } from "cesium";

// Multiple services (searched in order)
const viewer = new Viewer("cesiumContainer", {
  geocoder: [
    new CartographicGeocoderService(), // accepts "lat, lon" input
    new IonGeocoderService({ scene: viewer.scene }),
  ],
});

Custom GeocoderService

const myGeocoder = {
  async geocode(input, type) {
    // type: GeocodeType.SEARCH or GeocodeType.AUTOCOMPLETE
    const resp = await fetch(`https://api.example.com/search?q=${input}`);
    const data = await resp.json();
    return data.map((item) => ({
      displayName: item.name,
      destination: Cartesian3.fromDegrees(item.lon, item.lat),
    }));
  },
};
const viewer = new Viewer("cesiumContainer", { geocoder: [myGeocoder] });

Viewer Mixins

import { Viewer, viewerDragDropMixin, viewerCesium3DTilesInspectorMixin,
  viewerCesiumInspectorMixin, viewerPerformanceWatchdogMixin, viewerVoxelInspectorMixin } from "cesium";

const viewer = new Viewer("cesiumContainer");

// Drag-and-drop CZML/GeoJSON/KML loading
viewer.extend(viewerDragDropMixin, { dropTarget: "cesiumContainer", clearOnDrop: true });
viewer.dropError.addEventListener((handler, name, error) => console.error(error));

viewer.extend(viewerCesium3DTilesInspectorMixin);    // 3D Tiles debug panel
viewer.extend(viewerCesiumInspectorMixin);            // general scene inspector
viewer.extend(viewerPerformanceWatchdogMixin);        // low-FPS warning
viewer.extend(viewerVoxelInspectorMixin);             // voxel debug panel

Key Viewer Properties & Methods

Property Type
viewer.scene Scene
viewer.camera Camera
viewer.entities EntityCollection
viewer.dataSources DataSourceCollection
viewer.imageryLayers ImageryLayerCollection
viewer.terrainProvider TerrainProvider
viewer.clock / clockViewModel Clock / ClockViewModel
viewer.canvas HTMLCanvasElement
viewer.screenSpaceEventHandler ScreenSpaceEventHandler
viewer.selectedEntity / trackedEntity Entity
viewer.shadows boolean
viewer.resolutionScale number (default 1.0)
await viewer.flyTo(entity, { duration: 3.0, offset: headingPitchRange }); // animated
await viewer.zoomTo(tileset);   // instant; uses bounding sphere
viewer.destroy();               // free all resources

Credit & FrameRateMonitor

import { Credit, FrameRateMonitor } from "cesium";

// Custom credit (showOnScreen = true)
viewer.creditDisplay.addStaticCredit(new Credit("Data by Example Corp", true));

// Monitor frame rate
const monitor = FrameRateMonitor.fromScene(viewer.scene);
monitor.lowFrameRate.addEventListener(() => console.warn("Low FPS"));
monitor.nominalFrameRate.addEventListener(() => console.log("FPS recovered"));

Common Patterns

Production Viewer with Terrain and OSM Buildings

import { Ion, Viewer, Terrain, createOsmBuildingsAsync, Cartesian3, Math as CesiumMath } from "cesium";

Ion.defaultAccessToken = "YOUR_TOKEN";
const viewer = new Viewer("cesiumContainer", {
  terrain: Terrain.fromWorldTerrain(), animation: false, timeline: false,
});

viewer.scene.primitives.add(await createOsmBuildingsAsync());
viewer.scene.camera.flyTo({
  destination: Cartesian3.fromDegrees(-74.019, 40.6912, 750),
  orientation: { heading: CesiumMath.toRadians(20), pitch: CesiumMath.toRadians(-20) },
});

Loading and Framing a Sample 3D Tileset

When the tileset's local coordinate frame is unknown (sample assets, discrete-LOD demos), do NOT hand-compute an ECEF camera position; you will miss the asset and render a blank gray surface. Use viewer.zoomTo with a HeadingPitchRange to derive a fitted view from the tileset's bounding sphere.

import { Cesium3DTileset, HeadingPitchRange, Math as CesiumMath } from "cesium";

const tileset = await Cesium3DTileset.fromUrl(url);
viewer.scene.primitives.add(tileset);

await viewer.zoomTo(
  tileset,
  new HeadingPitchRange(
    CesiumMath.toRadians(45),
    CesiumMath.toRadians(-30),
    // omit range -> Cesium auto-fits the bounding sphere
  ),
);

Space Scene (No Globe)

Disable the globe and atmosphere via constructor options only. The scene's globe and skyAtmosphere properties become undefined, so any later viewer.scene.globe.<anything> = ... or viewer.scene.skyAtmosphere.show = false will throw Cannot set properties of undefined. Configure lighting, depth-test-against-terrain, atmosphere visibility, and similar options in the constructor or skip them entirely for a space scene.

const viewer = new Viewer("cesiumContainer", {
  globe: false,           // viewer.scene.globe will be undefined
  skyAtmosphere: false,   // viewer.scene.skyAtmosphere will be undefined
  baseLayerPicker: false,
  baseLayer: false,       // no imagery layer is needed without a globe
});

// Safe post-construction tweaks (skyBox/sun/moon remain defined):
viewer.scene.skyBox.show = true;  // default star field
viewer.scene.sun.show = false;
viewer.scene.moon.show = false;

// DO NOT do this, throws because globe/skyAtmosphere are undefined:
// viewer.scene.globe.enableLighting = true;
// viewer.scene.skyAtmosphere.show = false;

Explicit Render Mode (Low Power Dashboard)

Pair with scene3DOnly: true when 2D/Columbus View is not needed; this is the canonical low-GPU configuration.

const viewer = new Viewer("cesiumContainer", {
  requestRenderMode: true,
  maximumRenderTimeChange: Infinity,
  scene3DOnly: true,
  animation: false,
  timeline: false,
});
// Call viewer.scene.requestRender() after programmatic changes

Custom Base Layer

import { Viewer, ImageryLayer, OpenStreetMapImageryProvider } from "cesium";

const viewer = new Viewer("cesiumContainer", {
  baseLayerPicker: false,
  baseLayer: new ImageryLayer(new OpenStreetMapImageryProvider({
    url: "https://tile.openstreetmap.org/",
  })),
});

Columbus View with Web Mercator

import { Viewer, SceneMode, WebMercatorProjection } from "cesium";

const viewer = new Viewer("cesiumContainer", {
  sceneMode: SceneMode.COLUMBUS_VIEW, mapProjection: new WebMercatorProjection(),
});

Performance Tips

  1. Set requestRenderMode: true for mostly-static apps. Reduces CPU/GPU and battery drain. Call scene.requestRender() after changes.
  2. Use scene3DOnly: true when 2D/Columbus View is not needed. Saves GPU memory per geometry instance. Commonly required alongside requestRenderMode for dashboard/low-power scenarios.
  3. Disable unused widgets (animation: false, timeline: false) to reduce DOM overhead.
  4. Set msaaSamples: 1 on low-power devices. Default 4 balances quality.
  5. Lower resolutionScale (e.g., 0.75) on HiDPI displays for better frame rates.
  6. Prefer Terrain.fromWorldTerrain() over await createWorldTerrainAsync(); it is non-blocking and exposes error events.
  7. Enable requestVertexNormals: true on terrain for proper lighting at negligible cost.
  8. Call viewer.destroy() when removing from DOM to free WebGL contexts.
  9. Limit imagery layers to 2-3. Each adds a texture lookup per fragment.

Framing Checklist (Avoid Blank / Off-Frame Renders)

Before declaring a viewer-setup task complete, verify:

  1. After adding any tileset, model, GeoJSON/KML/CZML data source, or entity collection: await viewer.zoomTo(target) or await viewer.flyTo(target) was called.
  2. For tilesets with unknown local frames, the camera is derived from the bounding sphere (HeadingPitchRange), not a hand-picked Cartesian3.fromDegrees(...).
  3. If the scenario specifies a heading/pitch, those are passed via HeadingPitchRange to zoomTo/flyTo rather than set on camera.flyTo with a guessed destination.
  4. A required base imagery layer is actually present (count == 1) when the scenario expects a visible map under the asset.
  5. When globe: false or skyAtmosphere: false is set in constructor options, no later code accesses viewer.scene.globe.* or viewer.scene.skyAtmosphere.*; both are undefined and will throw.

See Also

  • cesiumjs-camera -- Camera positioning, flyTo, lookAt, navigation constraints
  • cesiumjs-entities -- Entity API, data sources, GeoJSON/KML/CZML loading
  • cesiumjs-imagery -- Imagery providers, layer management, split-screen
  • cesiumjs-terrain-environment -- Terrain providers, Globe, atmosphere, sky, lighting