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.
- reflex_resium-0.1.0/LICENSE +21 -0
- reflex_resium-0.1.0/PKG-INFO +217 -0
- reflex_resium-0.1.0/README.md +194 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/__init__.py +111 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/base.py +169 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/base.pyi +169 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/bridge.py +376 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/bridge.pyi +239 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/cesium.py +460 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/components.py +8487 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/components.pyi +11554 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/constants.py +33 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/events.py +192 -0
- reflex_resium-0.1.0/custom_components/reflex_resium/plugin.py +173 -0
- reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/PKG-INFO +217 -0
- reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/SOURCES.txt +22 -0
- reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/dependency_links.txt +1 -0
- reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/requires.txt +1 -0
- reflex_resium-0.1.0/custom_components/reflex_resium.egg-info/top_level.txt +1 -0
- reflex_resium-0.1.0/pyproject.toml +77 -0
- reflex_resium-0.1.0/setup.cfg +4 -0
- reflex_resium-0.1.0/tests/test_cesium.py +72 -0
- reflex_resium-0.1.0/tests/test_package.py +46 -0
- 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
|
+
[](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml)
|
|
27
|
+
[](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml)
|
|
28
|
+
[](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
|
+
[](https://github.com/ecrespo/reflex-resium/actions/workflows/quality.yml)
|
|
4
|
+
[](https://github.com/ecrespo/reflex-resium/actions/workflows/security.yml)
|
|
5
|
+
[](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
|
+
]
|