reflex-resium
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/engine26.3.0 and@cesium/widgets16.2.0 pinned for reproducible frontend builds), generated from resium's TypeScript definitions with prop docs and types. rs.Cesiumbridge: build real Cesium objects (Cartesian3,Color, imagery providers, materials, enums…) from Python, including state vars andrx.foreachitems.- 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_widgetwraps its children withCesiumWidgetContext, exposing Cesium'sentities/dataSources, which resium only provides forviewer.entity_descriptionrenders into Cesium's InfoBox iframe: Emotion/Radix classes don't apply there, so userx.el.*with inline styles (custom_attrs={"style": {...}}).- ParticleSystem advances with the scene clock: it needs
should_animate=True. - In development React
StrictModemounts 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 inuv.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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.