Skip to content

dpyeates/react-native-cesium

Repository files navigation

react-native-cesium

Warning

This library is under development and may not work correctly in all cases yet.

Experimental Cesium rendering for React Native using Nitro Modules, with the native Metal/Vulkan renderer powered by Cesium Native.

react-native-cesium example screenshot

Status

  • Platform maturity: under development
  • Supported renderers: Metal (iOS) and Vulkan (Android)
  • Native engine: Cesium Native
  • React Native integration: Nitro host component + imperative hybrid ref API

Requirements

  • React Native new architecture
  • React Native Nitro Modules is a dependency
  • Follow the native build instructions below: additional setup is required by you.
  • A valid Cesium Ion access token and asset IDs for your content (get these from https://ion.cesium.com)
  • Disk and time: building Cesium Native pulls vcpkg dependencies, can use several GB of disk, and often takes a long time on first build.

Installation

First install the Nitro Modules runtime dependency:

yarn add react-native-nitro-modules

or:

npm install react-native-nitro-modules

Then add react-native-cesium:

yarn add react-native-cesium

or:

npm install react-native-cesium

Building Cesium Native locally

The package does not include the required iOS or Android Cesium Native libraries. Generate them locally by compiling Cesium Native with the required toolchains.

System dependencies

These are not installed by yarn add / pod install. Install them on your local machine first.

Shared (both iOS and Android)

Tool Typical install (macOS) Why
CMake brew install cmake Configures and drives the native build
Ninja brew install ninja Required generator on macOS for the bundled build script
Git Xcode includes /usr/bin/git Clones Cesium Native and vcpkg
Python 3 Usually present on macOS; brew install python@3 if needed Some vcpkg/port steps expect a working python3

Optional but recommended for faster or more reliable builds (see Cesium Native developer setup):

  • nasmbrew install nasm (speeds some JPEG-related builds in dependency trees)

Ensure Homebrew’s binary directory is on your PATH when you run the build (typically /opt/homebrew/bin on Apple Silicon or /usr/local/bin on Intel Macs). The build script also tries to prepend common Homebrew paths for nested CMake/vcpkg processes.

Additional iOS specific requirements
  • Xcode or Command Line Tools: xcode-select --install (or install Xcode from the App Store)
  • Needed for Apple clang, SDKs, and xcodebuild (XCFramework assembly)
Additional Android specific requirements
  • Install Android Studio
  • In SDK Manager, install:
    • Android SDK
    • NDK (the project currently uses 27.1.12297006)
  • Ensure Java 17 (JDK) is available (Android Studio bundled JDK is fine)

For Android builds, make sure one of these is set so the NDK can be discovered:

  • ANDROID_NDK_HOME (explicit NDK path), or
  • ANDROID_SDK_ROOT / ANDROID_HOME with an installed NDK under ndk/

If ANDROID_NDK_HOME is unset, the build script will try to auto-detect the latest installed NDK from your SDK directory.

Required Podfile hooks (iOS)

Your app ios/Podfile must require two helpers from this package and call them in pre_install and post_install.

Top of the Podfile (paths assume the default node_modules layout):

require_relative "../node_modules/react-native-cesium/ios/react_native_cesium_ensure_native"
require_relative "../node_modules/react-native-cesium/ios/react_native_cesium_post_install"

pre_install (runs before pods resolve; triggers automatic Cesium Native clone/build when the XCFramework is missing):

pre_install do |installer|
  react_native_cesium_ensure_native
end

post_install (header search paths and simulator arch exclusions):

post_install do |installer|
  react_native_post_install(
    installer,
    config[:reactNativePath],
    :mac_catalyst_enabled => false
  )
  react_native_cesium_post_install(installer)
end

The post-install helper is currently needed to:

  • prepend this package's cpp/ headers before the locally built XCFramework headers
  • exclude x86_64 iOS simulator builds, because the simulator slice is Apple Silicon arm64 only

Android (Gradle automatic ensure-native)

Unlike iOS, you do not need to copy anything into your application’s android/build.gradle (or settings.gradle) so Cesium Native can be cloned and built automatically. React Native already includes this package as an Android library dependency; this repo’s android/build.gradle registers ensureCesiumNativeAndroid and makes preBuild depend on it.

What happens on ./gradlew / Android Studio build:

  1. preBuild runs ensureCesiumNativeAndroid before CMake compiles the JNI/native code.
  2. That task checks for a marker file under the installed package:
    vendor/android/share/cesium-native/cmake/cesium-nativeConfig.cmake.
  3. If the marker exists, the task does nothing.
  4. If the marker is missing, it runs (from the package root, e.g. node_modules/react-native-cesium):
    node scripts/cesium/ensure-native.mjs --android
    which may run update then a CESIUM_BUILD_ONLY=android build, same as a manual run (can take a long time).
  5. If REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1 is set and artifacts are still missing, the build fails fast with an error instead of running the script.

You still need a discoverable Android NDK (expand Additional Android specific requirements under System dependencies above) so that build can succeed.

Building

Automatic (default): assuming you have done the above and have all the required dependencies in place, when the native output is missing, the package tries to build it for you:

  • iOS: add pre_install in your app Podfile (one-time; see Required Podfile hooks) so pod install runs scripts/cesium/ensure-native.mjs --ios, which runs npm run update / yarn run update (if needed) and then CESIUM_BUILD_ONLY=ios build. The first run can take a long time and needs the same system dependencies as a manual build (CMake, Ninja, Xcode, disk space, etc.).
  • Android: no extra hooks in your app; see Android (Gradle automatic ensure-native). Needs a discoverable NDK.

Manual (optional): run the update and build scripts defined in react-native-cesium’s package.json. They are not available as top-level commands in your app; you must run them in the context of the installed package.

From your application project root (where your app’s package.json lives), after yarn add / npm install:

Yarn:

yarn --cwd node_modules/react-native-cesium run update
yarn --cwd node_modules/react-native-cesium run build

npm:

npm run update --prefix node_modules/react-native-cesium
npm run build --prefix node_modules/react-native-cesium

Alternative: cd node_modules/react-native-cesium and run yarn run update / yarn run build (or the equivalent npm run …).

Yarn/npm workspaces: if react-native-cesium is a workspace package in your monorepo, use your tool’s workspace runner, e.g. yarn workspace react-native-cesium run update (exact syntax depends on your workspace layout).

  • update checks out Cesium Native v0.59.0 into vendor/cesium-native under the package (next to its other files; typically ignored in app repos).
  • build runs scripts/cesium/build.mjs (CMake, vcpkg under vendor/vcpkg unless VCPKG_ROOT is set, Ninja on macOS) and writes vendor/ios/CesiumNative.xcframework and/or vendor/android depending on CESIUM_BUILD_ONLY.

Then install pods (iOS):

cd ios && pod install && cd ..

Usage

The public JS surface is exported from react-native-cesium:

  • CesiumView
  • CameraState
  • Quaternion
  • CesiumMetrics
  • CesiumViewProps
  • CesiumViewMethods

Minimal usage:

import React, { useRef } from 'react'
import { StyleSheet, View } from 'react-native'
import { callback } from 'react-native-nitro-modules'
import {
  CesiumView,
  type CameraState,
  type CesiumMetrics,
  type CesiumViewMethods,
} from 'react-native-cesium'

const initialCamera: CameraState = {
  latitude: 46.02,
  longitude: 7.6,
  altitude: 5800,
  heading: 220,
  pitch: -20,
  roll: 0,
  verticalFovDeg: 60,
}

export function GlobeScreen() {
  const cesiumRef = useRef<CesiumViewMethods | null>(null)

  return (
    <View style={styles.container}>
      <CesiumView
        hybridRef={callback((ref: CesiumViewMethods | null) => {
          cesiumRef.current = ref
        })}
        style={styles.fill}
        ionAccessToken="YOUR_CESIUM_ION_ACCESS_TOKEN"
        ionAssetId={1}
        initialCamera={initialCamera}
        pauseRendering={false}
        maximumScreenSpaceError={16}
        maximumSimultaneousTileLoads={8}
        loadingDescendantLimit={20}
        msaaSampleCount={1}
        ionImageryAssetId={2}
        onMetrics={callback((metrics: CesiumMetrics) => {
          console.log('Cesium FPS:', metrics.fps)
        })}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  fill: { flex: 1 },
})

Nitro hybridRef requirement

When you pass a callback ref to CesiumView, wrap it with callback(...) from react-native-nitro-modules. Plain callback functions do not make it across the React Native bridge correctly for this component.

API

CesiumViewProps

CesiumView is a Nitro host component. In addition to standard view props like style, it currently expects these Cesium-specific props:

Consumer-required props (TypeScript)

Prop Type Default Description
ionAccessToken string none Cesium Ion token for authenticated asset/imagery requests. An invalid token usually leaves the globe empty or partially loaded due to 401/403 responses.
ionAssetId number none Main Ion asset to render (tileset/terrain).
initialCamera CameraState none Initial camera used when the view is created. This is only used for initial camera; use setCamera() for runtime moves.
pauseRendering boolean false Pauses the native render loop. Set true to stop rendering and reduce GPU/CPU usage.
maximumScreenSpaceError number 32 Quality/performance trade-off for tile refinement. Lower values are sharper (more work); higher values are faster/blurrier.
maximumSimultaneousTileLoads number 12 Max concurrent tile fetch/decode operations. Raising 8 -> 16 can improve fast camera moves on good networks but may increase memory/bandwidth spikes.
loadingDescendantLimit number 20 Caps descendant tile fan-out during traversal. Lower values like 10 smooth bursts on low-end devices; higher values like 40 can fill detail faster.
msaaSampleCount number 1 Anti-aliasing sample count. iOS uses 1, 2, or 4 (>=4 -> 4, >=2 -> 2, otherwise 1); Android currently renders at 1 (MSAA setting is currently ignored in Vulkan backend).
ionImageryAssetId number 1 Imagery layer to drape over terrain/tiles. Use a satellite imagery asset for a photoreal look, or switch to a streets/map layer for legibility. See your Cesium Ion Asset IDs.

Consumer-optional props (TypeScript)

Prop Type Default Description
onMetrics (metrics: CesiumMetrics) => void undefined Receives throttled runtime stats and credits text.

CameraState

type CameraState = {
  latitude: number
  longitude: number
  altitude: number
  heading: number
  pitch: number
  roll: number
  verticalFovDeg: number
}
Field Type Valid range / format Description
latitude number -90..90 Camera latitude in degrees.
longitude number -180..180 Camera longitude in degrees.
altitude number Meters above ellipsoid Camera height in meters.
heading number Degrees Compass direction the camera faces. Example: 0 faces north, 90 faces east.
pitch number Degrees Tilt angle. Negative values look downward toward terrain; positive values tilt up toward horizon/sky.
roll number Degrees Bank/rotation around forward axis. Positive right bank, negative left bank.
verticalFovDeg number 20..100 (clamped) Vertical field of view in degrees; see Field of view (next section).

Field of view: vertical vs horizontal, full vs half, and Cesium alignment

Use this when matching Skia, custom HUDs, or CesiumJS math to the same frustum as this view.

Concept What this library uses
API field verticalFovDeg is the full vertical aperture in degrees (top to bottom through the view axis), not a half-angle and not the horizontal FOV.
Horizontal FOV Not stored. With viewport width w and height h in pixels and aspect = w / h, the native tile ViewState uses full vertical FOV vfov_rad = radians(verticalFovDeg) and full horizontal FOV hfov_rad = 2 * atan(tan(vfov_rad / 2) * aspect).
Half-angles The projection uses the usual symmetric perspective form: tan(vfov_rad / 2) (and the same pattern horizontally via aspect). Those are half of the full vertical/horizontal FOV, in radians—standard for tan of frustum slopes, not an alternate “FOV definition.”
Cesium Native ViewState GlobeCamera passes that vfov_rad and hfov_rad into Cesium3DTilesSelection::ViewState together with the viewport size—consistent with full-angle vertical/horizontal FOV in radians for culling.
CesiumJS PerspectiveFrustum / Camera.frustum Comparable vertical angle: In CesiumJS, PerspectiveFrustum#fovy is the full vertical FOV in radians (derived from fov and aspectRatio). This package’s verticalFovDeg is always vertical in degrees; use vfov_rad = Cesium.Math.toRadians(verticalFovDeg) (or equivalent) to compare to fovy when the same aspect ratio and symmetric frustum assumptions apply. This repo uses Cesium Native, not CesiumJS—tile culling uses ViewState with vfov_rad / hfov_rad as above, not the JS frustum object.

Quaternion

Used for camera-space view correction with setCameraQuaternion (see below). Components are w, x, y, z (Hamilton convention). Non-unit values are normalized on the native side.

type Quaternion = {
  w: number
  x: number
  y: number
  z: number
}

CesiumViewMethods

Method Signature Description
setCamera (camera: CameraState) => void Applies a new camera state at runtime (position, heading, pitch, roll, vertical FOV). Does not change the stored view-correction quaternion; use setCameraQuaternion when you need to update that.
setCameraQuaternion (camera: CameraState, viewCorrection: Quaternion) => void Same camera fields as setCamera, plus a rotation applied in camera space after heading/pitch/roll. Use this for screen-fixed adjustments (e.g. boresight calibration, aligning a synthetic horizon overlay) without expressing the offset as extra Euler angles.
getCameraState () => Promise<CameraState> Returns the current native camera snapshot (lat/lon/alt, HPR, VFOV). This is the underlying globe attitude; it does not encode the view correction into HPR.
getViewCorrection () => Promise<Quaternion> Returns the smoothed view-correction quaternion currently applied (identity w=1,x=y=z=0 if you have never called setCameraQuaternion).

Threading: setCamera / setCameraQuaternion vs getCameraState / getViewCorrection

Call style Supported / best practice
setCamera / setCameraQuaternion Synchronous native updates. Supported from the UI thread, including Reanimated worklets and useAnimatedReaction when you call through a Nitro hybridRef (same pattern as driving camera demand from shared values). This is the intended path for high-frequency camera updates.
getCameraState / getViewCorrection Async (Promise). Call from the JavaScript thread (e.g. useEffect, handlers, throttled HUD state)—not from worklets—unless you have a clear, tested pattern for async in your runtime.

Avoid assuming main-thread-only vs JS-thread-only labels beyond the above: Nitro invokes the hybrid object on the thread that called the method; use sync setters on the UI/worklet path you already use for gestures, and reserve Promise-based getters for JS.

setCamera vs setCameraQuaternion

  • Use setCamera alone when you only need the classic globe camera.
  • Use setCameraQuaternion when you need both the usual CameraState and a small rotation relative to the uncorrected camera (HUD alignment, horizon line offset in screen space, lens/display calibration, etc.).
  • You may mix the two: calling setCamera updates position and HPR but leaves the last view-correction target unchanged, so a calibration quaternion set earlier continues to apply until you change it with setCameraQuaternion.
  • Prefer setCameraQuaternion on frames where you need to drive both the globe camera and the correction together so the native target stays consistent.

CesiumMetrics

Field Type Description
fps number Smoothed frames-per-second estimate from the native render loop.
tilesRendered number Number of tiles currently rendered in the frame.
tilesLoading number Number of tiles still loading. If this stays high for long periods, reduce load pressure (maximumSimultaneousTileLoads) or check network.
tilesVisited number Number of tiles visited during traversal/culling.
ionTokenConfigured boolean Whether a non-empty Ion token is configured natively. false is a quick signal to check ionAccessToken.
tilesetReady boolean Whether the primary tileset is initialized and ready.
creditsPlainText string Plain-text attribution/credits from Cesium data sources. Display this in your app footer to satisfy attribution requirements.

Example App

The example app in example/App.tsx is the best current integration reference. It shows:

  • creating and storing a Nitro hybridRef
  • driving camera updates with setCamera(...) from a Reanimated useAnimatedReaction (UI-thread / worklet path; see Threading under CesiumViewMethods above)
  • the setCameraQuaternion / getViewCorrection APIs for camera-space HUD alignment (the example does not demonstrate them yet; see CesiumViewMethods above)
  • listening to onMetrics
  • switching imagery layers
  • presenting Cesium attribution from creditsPlainText

Before running the example, copy example/.env_example to example/.env and set CESIUM_ION_ACCESS_TOKEN to your own token. Restart Metro after editing .env so the env transform is reapplied.

The example project currently links this library locally via link:...

Credits

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors