cesiumjs-viewer-setup
CesiumJS viewer setup - Viewer, CesiumWidget, widgets, Ion token, Scene configuration, SceneMode, factory helpers, geocoders, platform services. Use when initializing a CesiumJS application, configuring viewer widgets, setting Ion access tokens, creating default terrain or imagery, or bootstrapping a 3D globe.
How do I install this agent skill?
npx skills add https://github.com/cesiumgs/cesiumjs-skills --skill cesiumjs-viewer-setupIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides a comprehensive reference for initializing and configuring CesiumJS viewers, including integration with various platform services like Cesium Ion and Google Maps. It follows standard development practices for documentation and contains no malicious code.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
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
- Set
requestRenderMode: truefor mostly-static apps. Reduces CPU/GPU and battery drain. Callscene.requestRender()after changes. - Use
scene3DOnly: truewhen 2D/Columbus View is not needed. Saves GPU memory per geometry instance. Commonly required alongsiderequestRenderModefor dashboard/low-power scenarios. - Disable unused widgets (
animation: false,timeline: false) to reduce DOM overhead. - Set
msaaSamples: 1on low-power devices. Default4balances quality. - Lower
resolutionScale(e.g.,0.75) on HiDPI displays for better frame rates. - Prefer
Terrain.fromWorldTerrain()overawait createWorldTerrainAsync(); it is non-blocking and exposes error events. - Enable
requestVertexNormals: trueon terrain for proper lighting at negligible cost. - Call
viewer.destroy()when removing from DOM to free WebGL contexts. - 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:
- After adding any tileset, model, GeoJSON/KML/CZML data source, or entity collection:
await viewer.zoomTo(target)orawait viewer.flyTo(target)was called. - For tilesets with unknown local frames, the camera is derived from the bounding sphere (
HeadingPitchRange), not a hand-pickedCartesian3.fromDegrees(...). - If the scenario specifies a heading/pitch, those are passed via
HeadingPitchRangetozoomTo/flyTorather than set oncamera.flyTowith a guessed destination. - A required base imagery layer is actually present (count == 1) when the scenario expects a visible map under the asset.
- When
globe: falseorskyAtmosphere: falseis set in constructor options, no later code accessesviewer.scene.globe.*orviewer.scene.skyAtmosphere.*; both areundefinedand 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
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/cesiumgs/cesiumjs-skills/cesiumjs-viewer-setup">View cesiumjs-viewer-setup on skillZs</a>