reflex-resium 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. reflex_resium-0.1.0/LICENSE +21 -0
  2. reflex_resium-0.1.0/PKG-INFO +217 -0
  3. reflex_resium-0.1.0/README.md +194 -0
  4. reflex_resium-0.1.0/custom_components/reflex_resium/__init__.py +111 -0
  5. reflex_resium-0.1.0/custom_components/reflex_resium/base.py +169 -0
  6. reflex_resium-0.1.0/custom_components/reflex_resium/base.pyi +169 -0
  7. reflex_resium-0.1.0/custom_components/reflex_resium/bridge.py +376 -0
  8. reflex_resium-0.1.0/custom_components/reflex_resium/bridge.pyi +239 -0
  9. reflex_resium-0.1.0/custom_components/reflex_resium/cesium.py +460 -0
  10. reflex_resium-0.1.0/custom_components/reflex_resium/components.py +8487 -0
  11. reflex_resium-0.1.0/custom_components/reflex_resium/components.pyi +11554 -0
  12. reflex_resium-0.1.0/custom_components/reflex_resium/constants.py +33 -0
  13. reflex_resium-0.1.0/custom_components/reflex_resium/events.py +192 -0
  14. reflex_resium-0.1.0/custom_components/reflex_resium/plugin.py +173 -0
  15. reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/PKG-INFO +217 -0
  16. reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/SOURCES.txt +22 -0
  17. reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/dependency_links.txt +1 -0
  18. reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/requires.txt +1 -0
  19. reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/top_level.txt +1 -0
  20. reflex_resium-0.1.0/pyproject.toml +77 -0
  21. reflex_resium-0.1.0/setup.cfg +4 -0
  22. reflex_resium-0.1.0/tests/test_cesium.py +72 -0
  23. reflex_resium-0.1.0/tests/test_package.py +46 -0
  24. reflex_resium-0.1.0/tests/test_plugin.py +57 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ernesto Crespo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: reflex-resium
3
+ Version: 0.1.0
4
+ Summary: Resium (React components for Cesium) for Reflex: 3D globes, maps, entities, 3D Tiles and more in pure Python
5
+ Author-email: Ernesto Crespo <ecrespo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ecrespo/reflex-resium
8
+ Project-URL: Source, https://github.com/ecrespo/reflex-resium
9
+ Project-URL: Issues, https://github.com/ecrespo/reflex-resium/issues
10
+ Keywords: reflex,reflex-custom-components,resium,cesium,3d,gis,globe,maps
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering :: GIS
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: reflex>=0.9.11
22
+ Dynamic: license-file
23
+
24
+ # reflex-resium
25
+
26
+ [![Quality](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml/badge.svg)](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml)
27
+ [![Security](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml/badge.svg)](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml)
28
+ [![PyPI](https://img.shields.io/pypi/v/reflex-resium)](https://pypi.org/project/reflex-resium/)
29
+
30
+ [Resium](https://resium.reearth.io) (React components for 🌏 [Cesium](https://cesium.com)) wrapped for [Reflex](https://reflex.dev): build 3D globes and maps — entities, primitives, GeoJSON/KML/CZML, 3D Tiles, terrain, camera flights, post-processing — in pure Python.
31
+
32
+ - **All 97 resium components** (resium 1.26.0 / cesium 1.145.0), generated from resium's TypeScript definitions with prop docs and types.
33
+ - **`rs.Cesium` bridge**: build real Cesium objects (`Cartesian3`, `Color`, imagery providers, materials, enums…) from Python, including state vars and `rx.foreach` items.
34
+ - **Automatic memoization** of Cesium objects so read-only props don't re-create elements on every render.
35
+ - **JSON-safe events**: Cesium callbacks (`on_click`, `on_load`, `on_tick`, `on_ready`…) arrive in your state as dictionaries.
36
+ - **`cesium_events`**: geographic clicks / mouse moves (longitude, latitude, height, picked entity), camera changes and selection.
37
+ - **Imperative helpers**: `fly_to`, `set_view`, `fly_home`, `zoom_to_entity`, `morph_to`, `set_clock`, `get_camera`, `screenshot`.
38
+ - **`ResiumPlugin`**: serves Cesium's static assets locally in dev and copies them into production builds (without it, the jsDelivr CDN is used).
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install reflex-resium # or: uv add reflex-resium
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```python
49
+ import reflex as rx
50
+ import reflex_resium as rs
51
+
52
+
53
+ def index() -> rx.Component:
54
+ return rx.box(
55
+ rs.viewer(
56
+ rs.entity(
57
+ rs.point_graphics(pixel_size=12, color=rs.Cesium.Color.RED),
58
+ rs.entity_description(rx.heading("Tokyo"), rx.text("Hello from Reflex!")),
59
+ name="Tokyo",
60
+ position=rs.cartesian3(139.767052, 35.681167, 100),
61
+ ),
62
+ full=True,
63
+ ),
64
+ position="relative",
65
+ height="100vh",
66
+ )
67
+
68
+
69
+ app = rx.App()
70
+ app.add_page(index)
71
+ ```
72
+
73
+ `rxconfig.py` (recommended):
74
+
75
+ ```python
76
+ import reflex as rx
77
+ from reflex_resium import ResiumPlugin
78
+
79
+ config = rx.Config(
80
+ app_name="my_app",
81
+ plugins=[rx.plugins.RadixThemesPlugin(), ResiumPlugin()],
82
+ )
83
+ ```
84
+
85
+ ## Concepts
86
+
87
+ ### Components
88
+
89
+ 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:
90
+
91
+ ```python
92
+ rs.viewer(
93
+ rs.custom_data_source(rs.entity(...), name="points"),
94
+ rs.camera_fly_to(destination=rs.cartesian3(-66.9, 10.48, 50_000), duration=3),
95
+ rs.globe(enable_lighting=True),
96
+ rs.bloom(enabled=State.bloom),
97
+ full=True,
98
+ )
99
+ ```
100
+
101
+ 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`.
102
+
103
+ ### The `rs.Cesium` bridge
104
+
105
+ ```python
106
+ C = rs.Cesium
107
+ C.Cartesian3.fromDegrees(State.lon, State.lat, 1000)
108
+ C.Color.fromCssColorString("#22d3ee").withAlpha(0.5)
109
+ C.HeightReference.CLAMP_TO_GROUND
110
+ C.OpenStreetMapImageryProvider.new(url="https://tile.openstreetmap.org/") # new X({url})
111
+ C.Material.fromType("Checkerboard", {"repeat": rs.cartesian2(20, 10)})
112
+ rs.js("Array.from({length: 8}, (_, i) => i)") # raw JS escape hatch
113
+ ```
114
+
115
+ 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`.
116
+
117
+ 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.
118
+
119
+ ### Events
120
+
121
+ ```python
122
+ class State(rx.State):
123
+ @rx.event
124
+ def clicked(self, movement: dict, target: dict):
125
+ # movement = {"position": {"x", "y"}, ...}; target = {"id", "name", "kind", "properties"}
126
+ ...
127
+
128
+
129
+ rs.entity(..., on_click=State.clicked)
130
+ rs.geo_json_data_source(data="/data.geojson", on_load=State.loaded) # {"name", "entity_count", ...}
131
+ rs.clock(on_tick=State.tick.throttle(1000)) # throttle high-frequency events
132
+ ```
133
+
134
+ ### Geographic events and imperative camera control
135
+
136
+ ```python
137
+ rs.viewer(
138
+ ...,
139
+ rs.cesium_events(
140
+ viewer_id="main",
141
+ on_left_click=State.on_click, # {"longitude", "latitude", "height", "screen", "picked"}
142
+ on_mouse_move=State.on_move, # throttled (mouse_move_throttle=100 ms)
143
+ on_camera_change=State.on_camera, # {"longitude", "latitude", "height", "heading", "pitch", "roll"}
144
+ on_selected_entity_change=State.on_select,
145
+ ),
146
+ )
147
+
148
+ rx.button("Caracas", on_click=rs.fly_to("main", -66.9, 10.48, 20_000, pitch=-35, duration=3))
149
+ rx.button("Photo", on_click=rs.screenshot("main", State.save_png))
150
+ ```
151
+
152
+ ### Notes and gotchas
153
+
154
+ - **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.
155
+ - **`cesium_widget`** wraps its children with `CesiumWidgetContext`, exposing Cesium's `entities`/`dataSources`, which resium only provides for `viewer`.
156
+ - **`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": {...}}`).
157
+ - **ParticleSystem** advances with the scene clock: it needs `should_animate=True`.
158
+ - In development React `StrictMode` mounts components twice, so load events (`on_load`, `on_ready`) may fire twice; production builds fire once.
159
+
160
+ ## Cesium assets and ion token
161
+
162
+ Cesium needs its `Workers`, `Assets`, `ThirdParty` and `Widgets` folders at runtime (`CESIUM_BASE_URL`):
163
+
164
+ | Setup | Assets served from |
165
+ | --- | --- |
166
+ | `ResiumPlugin()` | `node_modules/cesium/Build/Cesium` via a Vite middleware in dev; copied to `cesium/` in `reflex export` |
167
+ | `ResiumPlugin(base_url="https://my.cdn/cesium/")` | your URL |
168
+ | no plugin | `CESIUM_BASE_URL` env var, else jsDelivr CDN |
169
+
170
+ 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.
171
+
172
+ ## Demo
173
+
174
+ ```bash
175
+ cd resium_demo
176
+ uv pip install -e .. # or pip install -e ..
177
+ reflex run
178
+ ```
179
+
180
+ 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.
181
+
182
+ ## Regenerating the wrappers
183
+
184
+ `custom_components/reflex_resium/components.py` is generated:
185
+
186
+ ```bash
187
+ # in a scratch folder with resium, cesium, react and typescript@5 installed
188
+ node scripts/extract_props.mjs # -> props.json
189
+ python scripts/generate_components.py props.json path/to/resium/src
190
+ ```
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ uv sync # project + locked dev tools (uv.lock)
196
+ uv run ruff check . && uv run ruff format --check . # lint + format
197
+ uv run pytest # tests
198
+ ```
199
+
200
+ CI runs on every push/PR to `develop` and `main`:
201
+
202
+ - **Quality** (`.github/workflows/quality.yml`): Ruff (version locked in `uv.lock`), pytest on Python 3.10–3.13, `uv build` + `twine check` + wheel smoke test, demo compilation.
203
+ - **Security** (`.github/workflows/security.yml`): Bandit, pip-audit, Gitleaks, dependency review and CodeQL (also weekly).
204
+
205
+ ### Releasing
206
+
207
+ Bump `__version__` in `custom_components/reflex_resium/__init__.py`, merge to `main`, then tag it:
208
+
209
+ ```bash
210
+ git tag v0.1.0 && git push origin v0.1.0
211
+ ```
212
+
213
+ `.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.
214
+
215
+ ## License
216
+
217
+ MIT © Ernesto Crespo. Resium and Cesium are licensed under MIT and Apache-2.0 respectively.
@@ -0,0 +1,194 @@
1
+ # reflex-resium
2
+
3
+ [![Quality](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml/badge.svg)](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml)
4
+ [![Security](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml/badge.svg)](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/reflex-resium)](https://pypi.org/project/reflex-resium/)
6
+
7
+ [Resium](https://resium.reearth.io) (React components for 🌏 [Cesium](https://cesium.com)) wrapped for [Reflex](https://reflex.dev): build 3D globes and maps — entities, primitives, GeoJSON/KML/CZML, 3D Tiles, terrain, camera flights, post-processing — in pure Python.
8
+
9
+ - **All 97 resium components** (resium 1.26.0 / cesium 1.145.0), generated from resium's TypeScript definitions with prop docs and types.
10
+ - **`rs.Cesium` bridge**: build real Cesium objects (`Cartesian3`, `Color`, imagery providers, materials, enums…) from Python, including state vars and `rx.foreach` items.
11
+ - **Automatic memoization** of Cesium objects so read-only props don't re-create elements on every render.
12
+ - **JSON-safe events**: Cesium callbacks (`on_click`, `on_load`, `on_tick`, `on_ready`…) arrive in your state as dictionaries.
13
+ - **`cesium_events`**: geographic clicks / mouse moves (longitude, latitude, height, picked entity), camera changes and selection.
14
+ - **Imperative helpers**: `fly_to`, `set_view`, `fly_home`, `zoom_to_entity`, `morph_to`, `set_clock`, `get_camera`, `screenshot`.
15
+ - **`ResiumPlugin`**: serves Cesium's static assets locally in dev and copies them into production builds (without it, the jsDelivr CDN is used).
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install reflex-resium # or: uv add reflex-resium
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ import reflex as rx
27
+ import reflex_resium as rs
28
+
29
+
30
+ def index() -> rx.Component:
31
+ return rx.box(
32
+ rs.viewer(
33
+ rs.entity(
34
+ rs.point_graphics(pixel_size=12, color=rs.Cesium.Color.RED),
35
+ rs.entity_description(rx.heading("Tokyo"), rx.text("Hello from Reflex!")),
36
+ name="Tokyo",
37
+ position=rs.cartesian3(139.767052, 35.681167, 100),
38
+ ),
39
+ full=True,
40
+ ),
41
+ position="relative",
42
+ height="100vh",
43
+ )
44
+
45
+
46
+ app = rx.App()
47
+ app.add_page(index)
48
+ ```
49
+
50
+ `rxconfig.py` (recommended):
51
+
52
+ ```python
53
+ import reflex as rx
54
+ from reflex_resium import ResiumPlugin
55
+
56
+ config = rx.Config(
57
+ app_name="my_app",
58
+ plugins=[rx.plugins.RadixThemesPlugin(), ResiumPlugin()],
59
+ )
60
+ ```
61
+
62
+ ## Concepts
63
+
64
+ ### Components
65
+
66
+ 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:
67
+
68
+ ```python
69
+ rs.viewer(
70
+ rs.custom_data_source(rs.entity(...), name="points"),
71
+ rs.camera_fly_to(destination=rs.cartesian3(-66.9, 10.48, 50_000), duration=3),
72
+ rs.globe(enable_lighting=True),
73
+ rs.bloom(enabled=State.bloom),
74
+ full=True,
75
+ )
76
+ ```
77
+
78
+ 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`.
79
+
80
+ ### The `rs.Cesium` bridge
81
+
82
+ ```python
83
+ C = rs.Cesium
84
+ C.Cartesian3.fromDegrees(State.lon, State.lat, 1000)
85
+ C.Color.fromCssColorString("#22d3ee").withAlpha(0.5)
86
+ C.HeightReference.CLAMP_TO_GROUND
87
+ C.OpenStreetMapImageryProvider.new(url="https://tile.openstreetmap.org/") # new X({url})
88
+ C.Material.fromType("Checkerboard", {"repeat": rs.cartesian2(20, 10)})
89
+ rs.js("Array.from({length: 8}, (_, i) => i)") # raw JS escape hatch
90
+ ```
91
+
92
+ 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`.
93
+
94
+ 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.
95
+
96
+ ### Events
97
+
98
+ ```python
99
+ class State(rx.State):
100
+ @rx.event
101
+ def clicked(self, movement: dict, target: dict):
102
+ # movement = {"position": {"x", "y"}, ...}; target = {"id", "name", "kind", "properties"}
103
+ ...
104
+
105
+
106
+ rs.entity(..., on_click=State.clicked)
107
+ rs.geo_json_data_source(data="/data.geojson", on_load=State.loaded) # {"name", "entity_count", ...}
108
+ rs.clock(on_tick=State.tick.throttle(1000)) # throttle high-frequency events
109
+ ```
110
+
111
+ ### Geographic events and imperative camera control
112
+
113
+ ```python
114
+ rs.viewer(
115
+ ...,
116
+ rs.cesium_events(
117
+ viewer_id="main",
118
+ on_left_click=State.on_click, # {"longitude", "latitude", "height", "screen", "picked"}
119
+ on_mouse_move=State.on_move, # throttled (mouse_move_throttle=100 ms)
120
+ on_camera_change=State.on_camera, # {"longitude", "latitude", "height", "heading", "pitch", "roll"}
121
+ on_selected_entity_change=State.on_select,
122
+ ),
123
+ )
124
+
125
+ rx.button("Caracas", on_click=rs.fly_to("main", -66.9, 10.48, 20_000, pitch=-35, duration=3))
126
+ rx.button("Photo", on_click=rs.screenshot("main", State.save_png))
127
+ ```
128
+
129
+ ### Notes and gotchas
130
+
131
+ - **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.
132
+ - **`cesium_widget`** wraps its children with `CesiumWidgetContext`, exposing Cesium's `entities`/`dataSources`, which resium only provides for `viewer`.
133
+ - **`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": {...}}`).
134
+ - **ParticleSystem** advances with the scene clock: it needs `should_animate=True`.
135
+ - In development React `StrictMode` mounts components twice, so load events (`on_load`, `on_ready`) may fire twice; production builds fire once.
136
+
137
+ ## Cesium assets and ion token
138
+
139
+ Cesium needs its `Workers`, `Assets`, `ThirdParty` and `Widgets` folders at runtime (`CESIUM_BASE_URL`):
140
+
141
+ | Setup | Assets served from |
142
+ | --- | --- |
143
+ | `ResiumPlugin()` | `node_modules/cesium/Build/Cesium` via a Vite middleware in dev; copied to `cesium/` in `reflex export` |
144
+ | `ResiumPlugin(base_url="https://my.cdn/cesium/")` | your URL |
145
+ | no plugin | `CESIUM_BASE_URL` env var, else jsDelivr CDN |
146
+
147
+ 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.
148
+
149
+ ## Demo
150
+
151
+ ```bash
152
+ cd resium_demo
153
+ uv pip install -e .. # or pip install -e ..
154
+ reflex run
155
+ ```
156
+
157
+ 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.
158
+
159
+ ## Regenerating the wrappers
160
+
161
+ `custom_components/reflex_resium/components.py` is generated:
162
+
163
+ ```bash
164
+ # in a scratch folder with resium, cesium, react and typescript@5 installed
165
+ node scripts/extract_props.mjs # -> props.json
166
+ python scripts/generate_components.py props.json path/to/resium/src
167
+ ```
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ uv sync # project + locked dev tools (uv.lock)
173
+ uv run ruff check . && uv run ruff format --check . # lint + format
174
+ uv run pytest # tests
175
+ ```
176
+
177
+ CI runs on every push/PR to `develop` and `main`:
178
+
179
+ - **Quality** (`.github/workflows/quality.yml`): Ruff (version locked in `uv.lock`), pytest on Python 3.10–3.13, `uv build` + `twine check` + wheel smoke test, demo compilation.
180
+ - **Security** (`.github/workflows/security.yml`): Bandit, pip-audit, Gitleaks, dependency review and CodeQL (also weekly).
181
+
182
+ ### Releasing
183
+
184
+ Bump `__version__` in `custom_components/reflex_resium/__init__.py`, merge to `main`, then tag it:
185
+
186
+ ```bash
187
+ git tag v0.1.0 && git push origin v0.1.0
188
+ ```
189
+
190
+ `.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.
191
+
192
+ ## License
193
+
194
+ MIT © Ernesto Crespo. Resium and Cesium are licensed under MIT and Apache-2.0 respectively.
@@ -0,0 +1,111 @@
1
+ """reflex-resium: resium (React components for Cesium) for Reflex.
2
+
3
+ Quick start::
4
+
5
+ import reflex as rx
6
+ import reflex_resium as rs
7
+
8
+ def index():
9
+ return rs.viewer(
10
+ rs.entity(
11
+ name="Tokyo",
12
+ description="Hello, world.",
13
+ position=rs.cartesian3(139.767052, 35.681167, 100),
14
+ point={"pixelSize": 10, "color": rs.Cesium.Color.RED},
15
+ ),
16
+ full=True,
17
+ )
18
+ """
19
+
20
+ from .base import ResiumComponent, resium_setup_code
21
+ from .bridge import (
22
+ CesiumEvents,
23
+ cesium_events,
24
+ fly_home,
25
+ fly_to,
26
+ get_camera,
27
+ morph_to,
28
+ screenshot,
29
+ set_clock,
30
+ set_view,
31
+ zoom_to_entity,
32
+ )
33
+ from .cesium import (
34
+ Cesium,
35
+ CesiumExpr,
36
+ bounding_sphere,
37
+ camera_orientation,
38
+ cartesian2,
39
+ cartesian3,
40
+ cartesian3_array,
41
+ cartesian3_array_heights,
42
+ color,
43
+ distance_display_condition,
44
+ ellipsoid_terrain,
45
+ heading_pitch_range,
46
+ heading_pitch_roll,
47
+ ion_imagery_layer,
48
+ ion_resource,
49
+ js,
50
+ julian_date,
51
+ natural_earth_imagery_layer,
52
+ near_far_scalar,
53
+ osm_imagery_layer,
54
+ rectangle_from_degrees,
55
+ time_interval_collection,
56
+ to_js,
57
+ url_template_imagery_layer,
58
+ world_terrain,
59
+ )
60
+ from .components import * # noqa: F403
61
+ from .components import __all__ as _components_all
62
+ from .constants import CESIUM_VERSION, RESIUM_VERSION
63
+ from .plugin import ResiumPlugin, get_cesium_base_url, get_ion_access_token
64
+
65
+ __version__ = "0.1.0"
66
+
67
+ __all__ = [
68
+ *_components_all,
69
+ "CESIUM_VERSION",
70
+ "RESIUM_VERSION",
71
+ "Cesium",
72
+ "CesiumEvents",
73
+ "CesiumExpr",
74
+ "ResiumComponent",
75
+ "ResiumPlugin",
76
+ "bounding_sphere",
77
+ "camera_orientation",
78
+ "cartesian2",
79
+ "cartesian3",
80
+ "cartesian3_array",
81
+ "cartesian3_array_heights",
82
+ "cesium_events",
83
+ "color",
84
+ "distance_display_condition",
85
+ "ellipsoid_terrain",
86
+ "fly_home",
87
+ "fly_to",
88
+ "get_camera",
89
+ "get_cesium_base_url",
90
+ "get_ion_access_token",
91
+ "heading_pitch_range",
92
+ "heading_pitch_roll",
93
+ "ion_imagery_layer",
94
+ "ion_resource",
95
+ "js",
96
+ "julian_date",
97
+ "morph_to",
98
+ "natural_earth_imagery_layer",
99
+ "near_far_scalar",
100
+ "osm_imagery_layer",
101
+ "rectangle_from_degrees",
102
+ "resium_setup_code",
103
+ "screenshot",
104
+ "set_clock",
105
+ "set_view",
106
+ "time_interval_collection",
107
+ "to_js",
108
+ "url_template_imagery_layer",
109
+ "world_terrain",
110
+ "zoom_to_entity",
111
+ ]