Skip to main content

reflex-resium

Quality Security PyPI

Resium (React components for 🌏 Cesium) wrapped for Reflex: build 3D globes and maps — entities, primitives, GeoJSON/KML/CZML, 3D Tiles, terrain, camera flights, post-processing — in pure Python.

  • All 97 resium components (resium 1.26.0 / cesium 1.145.0, with @cesium/engine 26.3.0 and @cesium/widgets 16.2.0 pinned for reproducible frontend builds), generated from resium's TypeScript definitions with prop docs and types.
  • rs.Cesium bridge: build real Cesium objects (Cartesian3, Color, imagery providers, materials, enums…) from Python, including state vars and rx.foreach items.
  • Automatic memoization of Cesium objects so read-only props don't re-create elements on every render.
  • JSON-safe events: Cesium callbacks (on_click, on_load, on_tick, on_ready…) arrive in your state as dictionaries.
  • cesium_events: geographic clicks / mouse moves (longitude, latitude, height, picked entity), camera changes and selection.
  • Imperative helpers: fly_to, set_view, fly_home, zoom_to_entity, morph_to, set_clock, get_camera, screenshot.
  • ResiumPlugin: serves Cesium's static assets locally in dev and copies them into production builds (without it, the jsDelivr CDN is used).

Requirements

  • Python 3.11+ (3.11, 3.12 and 3.13 are tested in CI)
  • Reflex 0.10.0 or newer
  • Node.js 24 LTS for building the frontend (reflex run / reflex export)

Installation

pip install reflex-resium     # or: uv add reflex-resium

reflex-resium 0.2.0 targets Reflex 0.10 and drops Python 3.10. Projects that still need Reflex 0.9.x or Python 3.10 should pin reflex-resium==0.1.0.

Quick start

import reflex as rx
import reflex_resium as rs


def index() -> rx.Component:
    return rx.box(
        rs.viewer(
            rs.entity(
                rs.point_graphics(pixel_size=12, color=rs.Cesium.Color.RED),
                rs.entity_description(rx.heading("Tokyo"), rx.text("Hello from Reflex!")),
                name="Tokyo",
                position=rs.cartesian3(139.767052, 35.681167, 100),
            ),
            full=True,
        ),
        position="relative",
        height="100vh",
    )


app = rx.App()
app.add_page(index)

rxconfig.py (recommended):

import reflex as rx
from reflex_resium import ResiumPlugin

config = rx.Config(
    app_name="my_app",
    plugins=[rx.plugins.RadixThemesPlugin(), ResiumPlugin()],
)

Concepts

Components

Every resium component is available as a class (rs.Viewer) and a snake_case factory (rs.viewer). Props are snake_case (pixel_size → pixelSize). Nest them exactly like in resium:

rs.viewer(
    rs.custom_data_source(rs.entity(...), name="points"),
    rs.camera_fly_to(destination=rs.cartesian3(-66.9, 10.48, 50_000), duration=3),
    rs.globe(enable_lighting=True),
    rs.bloom(enabled=State.bloom),
    full=True,
)

Props named style that are Cesium values (Cesium3DTileset, Label, WMTS…) are exposed as cesium_style; root components (viewer, cesium_widget) keep Reflex's style, id and class_name.

The rs.Cesium bridge

C = rs.Cesium
C.Cartesian3.fromDegrees(State.lon, State.lat, 1000)
C.Color.fromCssColorString("#22d3ee").withAlpha(0.5)
C.HeightReference.CLAMP_TO_GROUND
C.OpenStreetMapImageryProvider.new(url="https://tile.openstreetmap.org/")  # new X({url})
C.Material.fromType("Checkerboard", {"repeat": rs.cartesian2(20, 10)})
rs.js("Array.from({length: 8}, (_, i) => i)")  # raw JS escape hatch

Keyword arguments become a camelCase options object. Helpers: cartesian3, cartesian3_array, cartesian3_array_heights, cartesian2, color, rectangle_from_degrees, heading_pitch_roll, heading_pitch_range, camera_orientation, near_far_scalar, distance_display_condition, bounding_sphere, julian_date, time_interval_collection, ion_resource, natural_earth_imagery_layer, osm_imagery_layer, url_template_imagery_layer, ion_imagery_layer, world_terrain, ellipsoid_terrain.

Cesium expressions passed as props are wrapped in useMemo (dependencies = the state vars they use). Use .volatile() to opt out or .memo(*deps) for explicit dependencies.

Events

class State(rx.State):
    @rx.event
    def clicked(self, movement: dict, target: dict):
        # movement = {"position": {"x", "y"}, ...}; target = {"id", "name", "kind", "properties"}
        ...


rs.entity(..., on_click=State.clicked)
rs.geo_json_data_source(data="/data.geojson", on_load=State.loaded)  # {"name", "entity_count", ...}
rs.clock(on_tick=State.tick.throttle(1000))  # throttle high-frequency events

Geographic events and imperative camera control

rs.viewer(
    ...,
    rs.cesium_events(
        viewer_id="main",
        on_left_click=State.on_click,  # {"longitude", "latitude", "height", "screen", "picked"}
        on_mouse_move=State.on_move,  # throttled (mouse_move_throttle=100 ms)
        on_camera_change=State.on_camera,  # {"longitude", "latitude", "height", "heading", "pitch", "roll"}
        on_selected_entity_change=State.on_select,
    ),
)

rx.button("Caracas", on_click=rs.fly_to("main", -66.9, 10.48, 20_000, pitch=-35, duration=3))
rx.button("Photo", on_click=rs.screenshot("main", State.save_png))

Notes and gotchas

  • Camera operations (camera_fly_to, camera_look_at, camera_fly_home, camera_fly_to_bounding_sphere) run in resium on every render. The wrappers memoize them (callbacks ignored in the comparison), so they only run again when a real prop changes.
  • cesium_widget wraps its children with CesiumWidgetContext, exposing Cesium's entities/dataSources, which resium only provides for viewer.
  • entity_description renders into Cesium's InfoBox iframe: Emotion/Radix classes don't apply there, so use rx.el.* with inline styles (custom_attrs={"style": {...}}).
  • ParticleSystem advances with the scene clock: it needs should_animate=True.
  • In development React StrictMode mounts components twice, so load events (on_load, on_ready) may fire twice; production builds fire once.

Cesium assets and ion token

Cesium needs its Workers, Assets, ThirdParty and Widgets folders at runtime (CESIUM_BASE_URL):

Setup Assets served from
ResiumPlugin() node_modules/cesium/Build/Cesium via a Vite middleware in dev; copied to cesium/ in reflex export
ResiumPlugin(base_url="https://my.cdn/cesium/") your URL
no plugin CESIUM_BASE_URL env var, else jsDelivr CDN

Cesium ion assets (world terrain, OSM Buildings, Bing imagery) need a token: ResiumPlugin(ion_access_token="...") or the CESIUM_ION_ACCESS_TOKEN environment variable. Without it Cesium's evaluation token is used and a banner is shown. natural_earth_imagery_layer() works offline with no token.

Demo

cd resium_demo
uv pip install -e ..   # or pip install -e ..
reflex run

Pages: entities & state · entity graphics · primitives & clouds · data sources (GeoJSON, KML, CZML, clustering) · camera & events · scene & post-processing & particles · imagery, glTF models & 3D Tiles · CesiumWidget.

Regenerating the wrappers

custom_components/reflex_resium/components.py is generated:

# in a scratch folder with resium, cesium, react and typescript@5 installed
node scripts/extract_props.mjs                 # -> props.json
python scripts/generate_components.py props.json path/to/resium/src

Development

uv sync                                          # project + locked dev tools (uv.lock)
uv run ruff check . && uv run ruff format --check .  # lint + format
uv run pytest                                    # tests

CI runs on every push/PR to develop and main:

  • Quality (.github/workflows/quality.yml): Ruff (version locked in uv.lock), pytest on Python 3.11–3.13, uv build + twine check + wheel smoke test, demo compilation on Node.js 24.
  • Security (.github/workflows/security.yml): Bandit, pip-audit, Gitleaks, dependency review and CodeQL (also weekly).

All workflows pin actions by commit SHA and run JavaScript actions on the Node 24 runtime (FORCE_JAVASCRIPT_ACTIONS_TO_NODE24).

Releasing

Bump __version__ in custom_components/reflex_resium/__init__.py, merge to main, then tag it:

git tag v0.2.0 && git push origin v0.2.0

.github/workflows/release.yml checks that the tag matches the version, tests and builds the package, publishes it to PyPI through Trusted Publishing (environment pypi) and creates the GitHub Release with the distributions attached.

License

MIT © Ernesto Crespo. Resium and Cesium are licensed under MIT and Apache-2.0 respectively.

Metadata

Release files for reflex-resium 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for reflex-resium 0.2.0
File Size Uploaded Provenance Actions
reflex_resium-0.2.0.tar.gz 117.7 kB With provenance Details for reflex_resium-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for reflex-resium 0.2.0
File Interpreter ABI Platform Actions
reflex_resium-0.2.0-py3-none-any.whl Python 3 none any Details for reflex_resium-0.2.0-py3-none-any.whl

Total release size: 235.8 kB

Release files / reflex_resium-0.2.0.tar.gz

Download URL reflex_resium-0.2.0.tar.gz
Size 117.7 kB
Tags Source
SHA-256 checksum
How to use checksums
adbad157d383be852a30a8c8eabaa0a78d871b5ac64dea5678c5874f12191796
BLAKE2b-256 checksum
How to use checksums
fb89a1ddbdbb16aa2f369160513b4c5673295392434a84bb363b0c9baba0df93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / reflex_resium-0.2.0-py3-none-any.whl

Download URL reflex_resium-0.2.0-py3-none-any.whl
Size 118.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e597d5f5a163ea3bab0274a11046cc9cf34687cd9d6d17f9031021ef8e58dc8
BLAKE2b-256 checksum
How to use checksums
f9ac22b4b6dc3c527d7520625a44875df6ecb8d8df304d5e947f8cfafd507fac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for reflex-resium 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for reflex_resium-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page