Geo Fundamentals
wasp-lang/wasp
Generative Engine Optimization for AI search engines (ChatGPT, Claude, Perplexity).
CesiumJS camera control - Camera, flyTo, lookAt, setView, ScreenSpaceCameraController, composable Controller camera controllers (1.144), CameraEventAggregator, flight animation.
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-camera --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/cesiumjs-camera .claude/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .claude/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-cameraType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-camera --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/cesiumjs-camera .agents/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .agents/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-camera --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/cesiumjs-camera .cursor/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .cursor/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/CesiumGS/cesiumjs-skills.git --path skills/cesiumjs-camera--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-camera --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/cesiumjs-camera .gemini/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .gemini/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-cameraInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/cesiumjs-camera .github/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .github/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install CesiumGS/cesiumjs-skills cesiumjs-camera --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/CesiumGS/cesiumjs-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/cesiumjs-camera .opencode/skills/cesiumjs-camera && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "cesiumjs-camera" agent skill from https://github.com/CesiumGS/cesiumjs-skills/tree/main/skills/cesiumjs-camera into .opencode/skills/cesiumjs-camera/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "cesiumjs-camera", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
cesiumjs-cameraCesiumJS camera control - Camera, flyTo, lookAt, setView, ScreenSpaceCameraController, composable Controller camera controllers (1.144), CameraEventAggregator, flight animation.
Cesiumjs Camera is an agent skill from CesiumGS/cesiumjs-skills. CesiumJS camera control - Camera, flyTo, lookAt, setView, ScreenSpaceCameraController, composable Controller camera controllers (1.144), CameraEventAggregator, flight animation. Use when positioning the camera, creating flyTo animations, constraining user navigation, adding custom or asset-inspection camera controllers, tracking entities, or converting between screen and world coordinates.
Its SKILL.md is about 6.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Marketing & SEO. The repository describes itself as: Curated agent skills for CesiumJS development. The licence is Apache-2.0.
8 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 5f4792c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are javascript).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Cesiumjs Camera loads about 6.8k tokens when it runs. Until then it costs about 102 tokens; SKILL.md has 1,964 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from CesiumGS/cesiumjs-skills at commit 5f4792c, republished under its Apache-2.0 licence (© CesiumGS). 1,964 words, ~6,834 tokens.
.claude/skills/cesiumjs-camera/SKILL.md (or your agent's skills folder).Baseline: CesiumJS v1.144 -- ES module imports (
import { ... } from "cesium";)
Access via viewer.camera. The camera has a position (Cartesian3 in world
coords), orientation vectors (direction, up, right), and a frustum.
All angles are radians.
Read-only computed properties: positionWC, positionCartographic,
directionWC, upWC, rightWC, heading (0 = north, clockwise), pitch
(negative = down), roll, transform, viewMatrix, inverseViewMatrix.
Events: moveStart / moveEnd fire when movement begins/ends. changed
fires when the camera moves by more than percentageChanged (default 0.5).
City views are more realistic with 3D buildings. For production skyline, street-level, or urban panorama views, use a tileset that is actually available in the target environment.
Cesium.createOsmBuildingsAsync()and Google Photorealistic 3D Tiles are ion-entitlement-backed; avoid them in public or no-token examples unless the caller explicitly asks for those services. For portable examples, use an OpenStreetMap/ArcGIS basemap, visible markers, public URL-backed 3D Tiles, or a higher-altitude city overview.
Choose altitude and pitch to match the scale of the feature you want to show:
| View type | Altitude (m) | Pitch (deg) | Notes |
|---|---|---|---|
| Tourist / monument standoff | 50 -- 500 | -5 to -20 | Camera at near-ground level, offset laterally from the landmark. Subject fills the frame with sky visible above. Use lookAt with HeadingPitchRange pitch near -10°. Never place the camera directly overhead at this range. |
| Landmark close-up | 500 -- 1,500 | -25 to -35 | Individual buildings/structures fill the frame. Use lookAt with appropriate range. |
| City panoramic / skyline | 800 -- 1,500 | -10 to -20 | For viewing a skyline from across a river or bay. Position camera to the side, face the city. Use an available 3D Tiles source only when the environment provides one. |
| City overview | 2,000 -- 5,000 | -35 to -50 | Urban grid, rivers, and parks clearly visible |
| Metro / regional | 8,000 -- 20,000 | -60 to -90 | Entire metro area or geographic feature |
| Canyon / cliff rim | 50 -- 300 above rim | -15 to -25 | Use steeper pitch to reveal depth below. Near-horizontal (-5) looks flat across terrain. Add wall/rim entity overlays and a river polyline to make canyon structure visible. |
| Country / continent | 500,000 -- 5,000,000 | -90 | Political boundaries, coastlines |
When the prompt says "looking at [city]" or "start at [city]", default to city overview range (2,000-5,000 m) with pitch around -45 to -60 degrees and heading 0 (north). This produces a clear, recognizable view where the urban layout, rivers, and landmarks are identifiable.
Target intent vs. camera position: In
setViewandflyTo, a Cartesiandestinationis the camera's final position, not the point it should look at. For requests such as "fly to Los Angeles" or "show the Eiffel Tower", treat the named coordinates as a target to frame. PreferviewBoundingSpherefor an instant view orflyToBoundingSpherefor an animated flight.
Top-down views (pitch: -90) are best for geographic features (canyons, coastlines, rivers) where overhead perspective reveals the distinctive shape. For cities, prefer an angled view that shows the 3D skyline.
Gimbal lock: Never use
pitch: -Math.PI/2exactly. Use-(Math.PI / 2 - 0.0001)for straight-down views to avoid singularity.
Ground-level views (altitude < 200 m) require 3D Tiles. Without them, CesiumJS shows only sky and flat ground. Suggest a higher-altitude fallback.
Skyline panoramics (across a river/bay): 800-1,500 m, pitch -10 to -20. Add an available 3D Tiles source for a true 3D silhouette; otherwise make the screenshot goal honest by using a higher-altitude map/marker view. Pitch too horizontal (-5) at moderate altitude shows a flat grid, not a skyline.
Public visual eval rule for landmarks: OpenStreetMap imagery alone does not render the Eiffel Tower, Empire State Building, Statue of Liberty, or a skyline as recognizable 3D subjects. When a public/no-token scenario asks for an identifiable landmark or skyline, add an explicit visual surrogate at the target: a tall cylinder/box/polyline tower, colored mast, skyline bar cluster, or large labeled marker. Frame that surrogate so it is inspectable, roughly one-third to two-thirds of the frame height for landmark views. Do not rely on a map label or 10 px point as the subject.
Canyon / cliff rim views: pitch -15 to -25. Near-horizontal pitch (-5 to -8) looks flat across terrain and misses the vertical drop. Always add entity overlays (rim wall polygons, river polyline) so the canyon structure is visible -- without entities the scene shows only a flat basemap regardless of camera angle.
CRITICAL -- Never place the camera directly above a landmark when a standoff / perspective view is intended. Positioning the camera at
Cartesian3.fromDegrees(lon, lat, 1000)pointing straight down (pitch: -Math.PI/2) puts the camera overhead the subject (up_alignment=1) and fails landmark-view checks. Always offset the camera laterally: uselookAtwith a HeadingPitchRange pitch of -10° to -45°, or place thedestination500--2,000 m to the side of the landmark and aim the heading toward it.
Teleports the camera in a single frame -- no animation. Use for initial view,
mode resets, constraint setup. destination: Cartesian3 or Rectangle.
orientation: { heading, pitch, roll } or { direction, up }.
import { Cartesian3, Math as CesiumMath } from "cesium";
// City overview: 3000 m altitude, angled view facing north
viewer.camera.setView({
destination: Cartesian3.fromDegrees(-0.1276, 51.5074, 3000.0),
orientation: {
heading: CesiumMath.toRadians(0.0), // north
pitch: CesiumMath.toRadians(-50.0), // angled down -- shows city layout clearly
roll: 0.0,
},
});import { Cartesian3, Math as CesiumMath } from "cesium";
// Landmark standoff: camera placed south of Eiffel Tower, facing north
// Offset the destination AWAY from the landmark -- do NOT place directly above it
viewer.camera.setView({
destination: Cartesian3.fromDegrees(2.2945, 48.847, 300.0), // ~1.2 km south
orientation: {
heading: CesiumMath.toRadians(0.0), // facing north toward tower
pitch: CesiumMath.toRadians(-20.0), // slightly down -- tower visible above horizon
roll: 0.0,
},
});import { Cartesian3, Math as CesiumMath } from "cesium";
// Canyon rim perspective: slightly above rim, looking down into the canyon
// Pitch of -20 reveals depth; near-horizontal (-5) would look flat across
viewer.camera.setView({
destination: Cartesian3.fromDegrees(-112.14, 36.06, 2400.0),
orientation: {
heading: CesiumMath.toRadians(0.0),
pitch: CesiumMath.toRadians(-20.0), // steeper pitch to show canyon depth
roll: 0.0,
},
});// Top-down geographic view -- use safe pitch to avoid gimbal lock
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(-112.14, 36.06, 50000.0),
orientation: { heading: 0.0, pitch: -(Math.PI / 2 - 0.0001), roll: 0.0 },
});
// Rectangle form (top-down, orientation defaults to north/down)
viewer.camera.setView({
destination: Cesium.Rectangle.fromDegrees(-77.0, 38.0, -72.0, 42.0),
});Smoothly animates the camera. Returns nothing (not a Promise); use complete
callback. Options: destination, orientation, duration (seconds),
complete/cancel, maximumHeight, pitchAdjustHeight, flyOverLongitude.
Use flyTo when destination intentionally describes the camera's final
position. For a named city, landmark, entity, or coordinate that must remain
centered, use flyToBoundingSphere.
import {
BoundingSphere,
Cartesian3,
HeadingPitchRange,
Math as CesiumMath,
} from "cesium";
// Keep the Eiffel Tower centered while approaching from 1500 m altitude
const target = Cartesian3.fromDegrees(2.2945, 48.8584);
const pitch = CesiumMath.toRadians(-35.0);
const desiredHeight = 1500.0;
// HeadingPitchRange.range is the slant distance to the target, not altitude.
const range = desiredHeight / Math.abs(Math.sin(pitch));
viewer.camera.flyToBoundingSphere(new BoundingSphere(target, 0.0), {
offset: new HeadingPitchRange(0.0, pitch, range),
duration: 3,
});import { Cartesian3, Math as CesiumMath } from "cesium";
// Chain flights using the complete callback (flyTo does NOT return a Promise)
viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(-74.0445, 40.6892, 800.0),
orientation: {
heading: CesiumMath.toRadians(0.0),
pitch: CesiumMath.toRadians(-35.0),
roll: 0.0,
},
duration: 3,
complete() {
viewer.camera.flyTo({
destination: Cartesian3.fromDegrees(-73.9857, 40.758, 600.0),
orientation: {
heading: CesiumMath.toRadians(0.0),
pitch: CesiumMath.toRadians(-40.0),
roll: 0.0,
},
duration: 2,
});
},
});Altitude tip for tours: keep each stop at 600 m+ so tiles and imagery load. Below 400 m, expect blurry tiles on fast successive flights.
// Long-distance flight: LA to Tokyo via Europe
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(139.815, 35.714, 20000.0),
duration: 20,
flyOverLongitude: Cesium.Math.toRadians(60.0), // eastward via Europe
pitchAdjustHeight: 1000, // look down at high altitude
});Control in-progress flights with completeFlight() (jumps to end state) and
cancelFlight() (stays at current position).
Fly to the default view. Override with Camera.DEFAULT_VIEW_RECTANGLE.
import { Camera, Rectangle } from "cesium";
Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(-10.0, 35.0, 40.0, 60.0);
viewer.camera.flyHome(2.0); // duration in seconds; omit for autoLimitation:
flyHome()always produces a top-down, north-up view -- no orientation control. Workaround: interceptviewer.homeButton.viewModel .command.beforeExecute, cancel it, and callflyTowith custom orientation.
Positions camera to look at a target from an offset (HeadingPitchRange or
Cartesian3). Locks the camera until lookAtTransform(Matrix4.IDENTITY).
Overhead trap with
lookAt: AHeadingPitchRangepitch of-Math.PI/2positions the camera directly above the target (up_alignment ≈ 1), which fails landmark-view checks. For any perspective or tourist view, use pitch between -10° and -45°. Only use pitch near -90° for intentional top-down map views.
import { Cartesian3, Math as CesiumMath, HeadingPitchRange, Matrix4 } from "cesium";
// View from the south, looking north (heading 0 = facing north = camera is south)
const target = Cartesian3.fromDegrees(2.2945, 48.8584, 300.0);
viewer.camera.lookAt(
target,
new HeadingPitchRange(
CesiumMath.toRadians(0.0), // heading 0 = north-facing
CesiumMath.toRadians(-20.0), // pitch -- 20 deg down (NOT -90; that would be overhead)
1500.0, // range in meters
),
);
// ALWAYS release the lookAt lock to restore free navigation
viewer.camera.lookAtTransform(Matrix4.IDENTITY);import { Cartesian3, Math as CesiumMath, HeadingPitchRange, Matrix4 } from "cesium";
// View from the east, looking west (heading 270 = facing west = camera is east)
const target = Cartesian3.fromDegrees(-73.9857, 40.7484, 200.0);
viewer.camera.lookAt(
target,
new HeadingPitchRange(
CesiumMath.toRadians(270.0), // heading -- west
CesiumMath.toRadians(-25.0), // pitch -- 25 deg down
800.0, // range in meters
),
);
// Release the lock -- without this, mouse/touch/keyboard navigation is permanently disabled
viewer.camera.lookAtTransform(Matrix4.IDENTITY);Cardinal direction reference for lookAt heading:
| To view from... | Camera faces... | Heading (deg) | Heading (rad) |
|---|---|---|---|
| South | North | 0 | 0 |
| West | East | 90 | Math.PI / 2 |
| North | South | 180 | Math.PI |
| East | West | 270 | 3 * Math.PI / 2 |
Heading = direction camera faces. Camera is opposite that direction from the target.
Trap: Every
lookAtcall MUST have a matchinglookAtTransform(Matrix4.IDENTITY). Without the release, mouse/touch/keyboard navigation is permanently disabled. UsesetTimeout,completecallback, or an event to trigger the release. ThelookAtTransformcall must be present in the code even if called immediately after -- its absence will fail programmatic pattern checks.
import { Matrix4 } from "cesium";
// ALWAYS release the lookAt lock when done to restore free navigation
viewer.camera.lookAtTransform(Matrix4.IDENTITY);Set camera position relative to an arbitrary transform matrix.
import { Cartesian3, Transforms, HeadingPitchRange, Math as CesiumMath } from "cesium";
// View in an east-north-up frame centered on a point
const center = Cartesian3.fromDegrees(-75.598, 40.039);
const transform = Transforms.eastNorthUpToFixedFrame(center);
viewer.camera.lookAtTransform(
transform,
new HeadingPitchRange(0.0, CesiumMath.toRadians(-45.0), 5000.0),
);For ICRF (inertial) frame: use Transforms.computeIcrfToFixedMatrix(time) in a
postUpdate listener, apply via lookAtTransform(Matrix4.fromRotationTranslation(icrfToFixed), offset).
Frame the camera around a BoundingSphere. Range is auto-computed when 0.
Prefer these methods when a prompt names a target that must remain visible.
The sphere center is the target; the offset places the camera relative to it.
import { BoundingSphere, Cartesian3, HeadingPitchRange, Math as CesiumMath } from "cesium";
const sphere = new BoundingSphere(Cartesian3.fromDegrees(-117.16, 32.71), 1000.0);
// Animated
viewer.camera.flyToBoundingSphere(sphere, {
offset: new HeadingPitchRange(0.0, CesiumMath.toRadians(-45.0), 0.0),
duration: 2.0,
});
// Instant
viewer.camera.viewBoundingSphere(sphere);Movement (translate position by meters, default defaultMoveAmount = 100 km):
moveForward, moveBackward, moveUp, moveDown, moveLeft, moveRight,
move(direction, amount).
Rotation (orbit around reference frame center, preserves distance, default
defaultRotateAmount = PI/3600 rad): rotateUp, rotateDown, rotateLeft,
rotateRight, rotate(axis, angle).
Look (first-person rotate-in-place, default defaultLookAmount = PI/60 rad):
lookUp, lookDown, lookLeft, lookRight, look(axis, angle),
twistLeft, twistRight.
Zoom (along view direction, default defaultZoomAmount = 100 km):
zoomIn(amount), zoomOut(amount).
// Scale movement speed to altitude for natural feel
const height = viewer.scene.globe.ellipsoid
.cartesianToCartographic(viewer.camera.position).height;
const speed = height / 100.0;
viewer.camera.moveForward(speed);Handles default mouse/touch input. Access via
viewer.scene.screenSpaceCameraController.
When setting up constraints, also call setView so the initial view respects them.
import { Cartesian3, Math as CesiumMath } from "cesium";
const ctrl = viewer.scene.screenSpaceCameraController;
ctrl.minimumZoomDistance = 500; // meters from surface
ctrl.maximumZoomDistance = 50000;
ctrl.maximumTiltAngle = Math.PI / 2; // prevent going below horizon
// Disable specific interactions
ctrl.enableRotate = false;
ctrl.enableTilt = false;
ctrl.enableZoom = false;
ctrl.enableTranslate = false; // 2D / Columbus only
ctrl.enableLook = false;
ctrl.enableInputs = false; // disable everything at once
// Set initial view at city-overview altitude for a clear starting point
viewer.camera.setView({
destination: Cartesian3.fromDegrees(-0.1276, 51.5074, 3000.0),
orientation: {
heading: CesiumMath.toRadians(0.0),
pitch: CesiumMath.toRadians(-50.0),
roll: 0.0,
},
});Gotcha:
maximumZoomDistanceis silently ignored whenenableCollisionDetection = false. Re-enable collision after underground views, or enforce zoom limits manually inclock.onTick.
Other properties: inertiaSpin, inertiaZoom, inertiaTranslate (0 = none,
0.9 = default), enableCollisionDetection (set false to allow camera underground).
import { CameraEventType, KeyboardEventModifier } from "cesium";
const ctrl = viewer.scene.screenSpaceCameraController;
ctrl.rotateEventTypes = CameraEventType.RIGHT_DRAG;
ctrl.tiltEventTypes = {
eventType: CameraEventType.LEFT_DRAG,
modifier: KeyboardEventModifier.CTRL,
};
ctrl.zoomEventTypes = CameraEventType.WHEEL;CameraEventType values: LEFT_DRAG, RIGHT_DRAG, MIDDLE_DRAG, WHEEL,
PINCH. Combine with KeyboardEventModifier: SHIFT, CTRL, ALT.
CesiumJS 1.144 adds a composable Controller framework as an opt-in
alternative to ScreenSpaceCameraController, aimed at asset inspection:
navigating underground, inside enclosed models, and around a fixed subject
without collision detection. ScreenSpaceCameraController is not deprecated
and remains the default; the two systems can run together.
Five controllers ship in 1.144 (the release notes list four; the zoom controller is also public):
| Controller | Motion |
|---|---|
ScreenSpaceMapCameraController | Horizontal pan in the local tangent plane |
ScreenSpaceElevatorCameraController | Vertical pan along the ellipsoid normal |
HybridScreenSpacePanCameraController | Auto-switches between map and elevator pan by angle from nadir |
ScreenSpaceTiltOrbitCameraController | Tilt and orbit around a picked point, with damped easing |
ScreenSpaceZoomCameraController | Zoom toward the pointer with inertia |
Canonical setup: disable the default controller's inputs and collision detection, then add the controllers the use case needs.
import {
HybridScreenSpacePanCameraController,
ScreenSpaceTiltOrbitCameraController,
ScreenSpaceZoomCameraController,
} from "cesium";
const ssc = viewer.scene.screenSpaceCameraController;
ssc.enableInputs = false; // hand input over to the new controllers
ssc.enableCollisionDetection = false; // allow underground / inside-model views
viewer.addController(new HybridScreenSpacePanCameraController());
viewer.addController(new ScreenSpaceTiltOrbitCameraController());
viewer.addController(new ScreenSpaceZoomCameraController());viewer.addController / viewer.removeController (also available on
CesiumWidget) wrap scene.controllerHost, a ControllerHost that updates
registered controllers once per frame in priority order.
Rebind a controller's inputs through its dragInputs option (MouseButton
plus an optional KeyboardEventModifier). Tuning knobs vary by controller:
the map and elevator pan controllers expose panSpeed, inertiaEnabled, and
inertialDecay; tilt-orbit and zoom expose dampingEnabled; the hybrid
controller is configured through its nested mapController /
elevatorController plus an angleThreshold. A pickWorldPosition callback
selects the world point that motion is computed against (default: the
ellipsoid surface below the camera).
import { MouseButton, ScreenSpaceElevatorCameraController } from "cesium";
const elevator = new ScreenSpaceElevatorCameraController({
dragInputs: [{ button: MouseButton.RIGHT }],
});
elevator.pickWorldPosition = (scene, windowPosition, result) =>
scene.pickPosition(windowPosition, result);
viewer.addController(elevator);Write a custom controller by implementing the Controller contract:
connectedCallback(element) / disconnectedCallback(element) for DOM
listeners, firstUpdate(scene, time) for one-time setup, and
update(scene, time) called once per frame.
ScreenSpaceInputBindings.registerDragInputBindings(handler, bindings, actions)
wires drag start/change/end callbacks for you.
Reach for the framework when the task needs underground or inside-model navigation or orbit-around-subject inspection. For remapping which mouse button or modifier drives globe rotate/tilt/zoom, keep using
ScreenSpaceCameraController'srotateEventTypes/tiltEventTypes/zoomEventTypes(see Remapping Input Events above). The official Sandcastle demo id iscamera-controllers.
For inspection-style navigation, prefer the 1.144 Controller framework above.
Hand-roll controls only for game-style first-person movement it does not cover:
disable the default controller, use ScreenSpaceEventHandler for mouse-look and
keydown/keyup for WASD. Apply in clock.onTick. Scale speed to altitude.
import { ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian3 } from "cesium";
const ctrl = viewer.scene.screenSpaceCameraController;
ctrl.enableRotate = ctrl.enableTranslate = ctrl.enableZoom = false;
ctrl.enableTilt = ctrl.enableLook = false;
const canvas = viewer.canvas;
canvas.setAttribute("tabindex", "0");
let looking = false, startPos, mousePos;
const handler = new ScreenSpaceEventHandler(canvas);
handler.setInputAction((m) => { looking = true; startPos = mousePos = Cartesian3.clone(m.position); }, ScreenSpaceEventType.LEFT_DOWN);
handler.setInputAction((m) => { mousePos = m.endPosition; }, ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(() => { looking = false; }, ScreenSpaceEventType.LEFT_UP);
const flags = {};
document.addEventListener("keydown", (e) => { flags[e.code] = true; });
document.addEventListener("keyup", (e) => { flags[e.code] = false; });
viewer.clock.onTick.addEventListener(() => {
const cam = viewer.camera;
if (looking) {
cam.lookRight((mousePos.x - startPos.x) / canvas.clientWidth * 0.05);
cam.lookUp(-(mousePos.y - startPos.y) / canvas.clientHeight * 0.05);
}
const spd = viewer.scene.globe.ellipsoid.cartesianToCartographic(cam.position).height / 100;
if (flags.KeyW) cam.moveForward(spd); if (flags.KeyS) cam.moveBackward(spd);
if (flags.KeyA) cam.moveLeft(spd); if (flags.KeyD) cam.moveRight(spd);
if (flags.KeyQ) cam.moveUp(spd); if (flags.KeyE) cam.moveDown(spd);
});const off = viewer.camera.moveStart.addEventListener(() => console.log("moving"));
viewer.camera.moveEnd.addEventListener(() => console.log("stopped"));
// off(); // call return value to unsubscribe
viewer.camera.percentageChanged = 0.1; // threshold for change detection
viewer.camera.changed.addEventListener((pct) => console.log(`Changed ${(pct*100).toFixed(1)}%`));import { Cartesian2 } from "cesium";
const center = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
const worldPos = viewer.camera.pickEllipsoid(center);
if (worldPos) {
const carto = viewer.scene.globe.ellipsoid.cartesianToCartographic(worldPos);
console.log(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
}// Track an entity (sets camera to follow automatically)
viewer.trackedEntity = viewer.entities.getById("vehicle");
// Customize default tracking offset
import { Camera, HeadingPitchRange, Math as CesiumMath } from "cesium";
Camera.DEFAULT_OFFSET = new HeadingPitchRange(
CesiumMath.toRadians(90.0), CesiumMath.toRadians(-25.0), 500.0,
);
viewer.trackedEntity = undefined; // stop trackingDebug: viewer.scene.primitives.add(new Cesium.DebugCameraPrimitive({ camera: viewer.camera, color: Cesium.Color.YELLOW, updateOnChange: true }));
setView over flyTo with duration: 0 -- avoids tween overhead.heading/pitch/roll every frame -- each computes an
ENU transform. Cache or use direction/up vectors.changed events -- raise percentageChanged (e.g., 0.5).lookAt locks -- lookAtTransform(Matrix4.IDENTITY).maximumHeight for short flights -- prevents zooming to space.enableCollisionDetection = true.| Task | Method | Key detail |
|---|---|---|
| Jump to a city | viewBoundingSphere | Treat city coordinates as the target; use 2,000-5,000 m desired height and pitch -50 |
| Animate to a landmark | flyToBoundingSphere | Target stays centered; use 1,000-2,000 m desired height and pitch -30 to -40 |
| Tourist / monument standoff | lookAt or setView | Camera 500-2,000 m laterally offset from landmark; pitch -10 to -20. Never place camera directly above (pitch -90) at close range. |
| City skyline / panoramic | setView or flyTo | 800-1,500 m, pitch -10 to -20. Position camera across river/bay, face the city. Load OSM Buildings. |
| Overhead / map view | setView or flyTo | pitch -(Math.PI/2 - 0.0001), altitude matches feature size |
| Canyon / cliff rim | setView or flyTo | 50-300 m above rim, pitch -15 to -25 for depth. Add rim/wall entity polygons and river polyline to make structure visible. |
| Lock on a target | lookAt | Must release with lookAtTransform(Matrix4.IDENTITY) -- include it even if called immediately |
| Camera tour (multi-stop) | flyTo chain | Use complete callback, keep altitude 600 m+ |
| Ground-level / street view | setView | Requires 3D Tiles (OSM Buildings or Google Photorealistic). Without them, only sky and flat ground visible. |
| Constrain user nav | screenSpaceCameraController | Set min/max zoom, tilt angle; also call setView for initial position |
| Asset inspection / underground orbit | Controller framework (1.144+) | Disable default inputs + collision, then viewer.addController with hybrid pan, tilt-orbit, and zoom controllers |
© CesiumGS, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/cesiumjs-camera of CesiumGS/cesiumjs-skills.
Open the folder on GitHubat commit 5f4792c
Cesiumjs Camera next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Cesiumjs Camera this skillCesiumGS/cesiumjs-skills | 189 | — | ~6.8k | Automated safety check: Pass | Apache-2.0 | |
| Geo Fundamentalswasp-lang/wasp | 19k | 9 repos | ~861 | Automated safety check: Pass | MIT | |
| Ab Testingcoreyhaines31/marketingskills | 54k | 3 repos | ~2.8k | Automated safety check: Pass | MIT | |
| Hreflang and International SEOAgriciDaniel/claude-seo | 18k | 5 repos | ~3.4k | Automated safety check: Pass | MIT | |
| Referralscoreyhaines31/marketingskills | 54k | 2 repos | ~2.4k | Automated safety check: Pass | MIT | |
| SEO GeoReScienceLab/opc-skills | 1.8k | 4 repos | ~2.1k | Automated safety check: Pass | Apache-2.0 |
wasp-lang/wasp
Generative Engine Optimization for AI search engines (ChatGPT, Claude, Perplexity).
coreyhaines31/marketingskills
When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program.
AgriciDaniel/claude-seo
Audits, validates and generates hreflang tags for multi-language and multi-region sites in HTML, HTTP headers or XML sitemaps, flagging common code and return-tag mistakes.
coreyhaines31/marketingskills
When the user wants to create, optimize, or analyze a referral program, affiliate program, or word-of-mouth strategy.
ReScienceLab/opc-skills
SEO & GEO (Generative Engine Optimization) for websites. An agent skill from ReScienceLab/opc-skills.
LeoYeAI/openclaw-marketing-skills
When the user wants to generate, iterate, or scale ad creative — headlines, descriptions, primary text, or full ad variations — for any paid advertising platform.
CesiumGS/cesiumjs-skills
CesiumJS 3D Tiles - Cesium3DTileset, compressed and CAD-style glTF content, MVTDataProvider, UrlTemplate3DTilesDataProvider, styling, metadata, feature picking, voxels, point clouds, I3S, Gaussian…
CesiumGS/cesiumjs-skills
CesiumJS core utilities and networking - Resource, Color, Event, Request, RequestScheduler, error handling, helper functions, feature detection.
CesiumGS/cesiumjs-skills
CesiumJS materials and post-processing — Material, Fabric JSON, MaterialAppearance, ImageBasedLighting, PostProcessStage, PostProcessStageLibrary, bloom, depth of field, ambient occlusion, FXAA…
CesiumGS/cesiumjs-skills
CesiumJS spatial math - Cartesian3, Cartographic, Matrix4, Quaternion, Transforms, Ellipsoid, BoundingSphere, projections, coordinate conversions.
CesiumGS/cesiumjs-skills
CesiumJS terrain, globe, and environment - TerrainProvider, Globe, sampleTerrain, atmosphere, sky, fog, lighting, shadows, panoramas.
CesiumGS/cesiumjs-skills
CesiumJS viewer setup - Viewer, CesiumWidget, widgets, Ion token, Scene configuration, SceneMode, factory helpers, geocoders, platform services.
Categories
CesiumJS camera control - Camera, flyTo, lookAt, setView, ScreenSpaceCameraController, composable Controller camera controllers (1.144), CameraEventAggregator, flight animation. Cesiumjs Camera is an agent skill from CesiumGS/cesiumjs-skills.144), CameraEventAggregator, flight animation.
Cesiumjs Camera fits situations like: positioning the camera; creating flyTo animations; constraining user navigation; asset-inspection camera controllers.
Run `npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a claude-code`. Or copy the skill folder (skills/cesiumjs-camera in CesiumGS/cesiumjs-skills) into .claude/skills/cesiumjs-camera in your project. Claude Code loads it when a task matches its description.
Run `npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a codex`. Or copy the skill folder (skills/cesiumjs-camera in CesiumGS/cesiumjs-skills) into .agents/skills/cesiumjs-camera in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add CesiumGS/cesiumjs-skills --skill cesiumjs-camera -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/cesiumjs-camera, .gemini/skills/cesiumjs-camera, .github/skills/cesiumjs-camera and .opencode/skills/cesiumjs-camera in your project.
SKILL.md names no scripts, command-line tools or credentials: Cesiumjs Camera is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Cesiumjs Camera is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.8k tokens (SKILL.md is roughly 27k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Cesiumjs Camera: Geo Fundamentals (wasp-lang/wasp, 19k stars), Ab Testing (coreyhaines31/marketingskills, 54k stars), Hreflang and International SEO (AgriciDaniel/claude-seo, 18k stars) and Referrals (coreyhaines31/marketingskills, 54k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
CesiumGS (a GitHub organization) maintains it in CesiumGS/cesiumjs-skills, which has 189 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on September 14, 2026.
Source: CesiumGS/cesiumjs-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.