reflex-mapgl-maplibre 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_mapgl_maplibre-0.1.0/LICENSE +21 -0
- reflex_mapgl_maplibre-0.1.0/PKG-INFO +223 -0
- reflex_mapgl_maplibre-0.1.0/README.md +188 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/__init__.py +64 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/advanced.py +65 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/advanced.pyi +173 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/base.py +41 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/base.pyi +85 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/controls.py +82 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/controls.pyi +382 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/deckgl_overlay.jsx +11 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/draw_control.jsx +28 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/map.py +173 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/map.pyi +151 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/markers.py +62 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/markers.pyi +196 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/provider.py +21 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/provider.pyi +81 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/py.typed +0 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/sources.py +69 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre/sources.pyi +185 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre.egg-info/PKG-INFO +223 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre.egg-info/SOURCES.txt +34 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre.egg-info/dependency_links.txt +1 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre.egg-info/requires.txt +8 -0
- reflex_mapgl_maplibre-0.1.0/custom_components/reflex_mapgl_maplibre.egg-info/top_level.txt +1 -0
- reflex_mapgl_maplibre-0.1.0/pyproject.toml +66 -0
- reflex_mapgl_maplibre-0.1.0/setup.cfg +4 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_advanced.py +38 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_base.py +36 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_component.py +81 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_controls.py +55 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_demo_pages.py +34 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_map.py +138 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_markers.py +44 -0
- reflex_mapgl_maplibre-0.1.0/tests/test_sources.py +40 -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,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflex-mapgl-maplibre
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reflex wrapper for react-map-gl (maplibre endpoint) — interactive MapLibre GL JS maps in pure Python.
|
|
5
|
+
Author-email: Ernesto Crespo <ecrespo@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ecrespo/reflex-mapgl-maplibre
|
|
8
|
+
Project-URL: Source, https://github.com/ecrespo/reflex-mapgl-maplibre
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/ecrespo/reflex-mapgl-maplibre/issues
|
|
10
|
+
Project-URL: Upstream (react-map-gl), https://github.com/visgl/react-map-gl
|
|
11
|
+
Keywords: reflex,reflex-custom-components,maplibre,react-map-gl,map,gis,geospatial,webgl
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: reflex>=0.9.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build; extra == "dev"
|
|
30
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# reflex-mapgl-maplibre
|
|
37
|
+
|
|
38
|
+
Interactive **MapLibre GL JS** maps for [Reflex](https://reflex.dev), in pure
|
|
39
|
+
Python. A thin, idiomatic wrapper around
|
|
40
|
+
[`react-map-gl`](https://github.com/visgl/react-map-gl) via its
|
|
41
|
+
`react-map-gl/maplibre` endpoint — markers, popups, GeoJSON sources & layers,
|
|
42
|
+
clustering, heatmaps, controls, feature interaction and imperative camera
|
|
43
|
+
control, with **no JavaScript or React required**.
|
|
44
|
+
|
|
45
|
+
> Status: **beta (0.1.0).** Core map, markers/popups, sources/layers, controls,
|
|
46
|
+
> multi-map provider and optional deck.gl/draw overlays are implemented, with a
|
|
47
|
+
> 58-test contract suite (100% wrapper coverage) and a demo app mapped to the
|
|
48
|
+
> upstream examples gallery. See [`specs/`](specs/) for the full design.
|
|
49
|
+
|
|
50
|
+
## Why
|
|
51
|
+
|
|
52
|
+
Reflex has no built-in map component. `react-map-gl/maplibre` is the standard
|
|
53
|
+
React wrapper for MapLibre, but it is unreachable from Python without a wrapper.
|
|
54
|
+
This package exposes it as Reflex components and ships a demo app that reproduces
|
|
55
|
+
the upstream examples.
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv add reflex-mapgl-maplibre # or: pip install reflex-mapgl-maplibre
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
It pulls the npm packages `react-map-gl` and `maplibre-gl` into the compiled
|
|
64
|
+
frontend automatically, and injects `maplibre-gl/dist/maplibre-gl.css` for you.
|
|
65
|
+
|
|
66
|
+
## Quickstart
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import reflex as rx
|
|
70
|
+
import reflex_mapgl_maplibre as mapgl
|
|
71
|
+
|
|
72
|
+
STYLE = "https://demotiles.maplibre.org/style.json" # key-less basemap
|
|
73
|
+
|
|
74
|
+
def index() -> rx.Component:
|
|
75
|
+
return mapgl.map(
|
|
76
|
+
mapgl.navigation_control(position="top-right"),
|
|
77
|
+
mapgl.marker(longitude=-122.4, latitude=37.8, color="#ef4444"),
|
|
78
|
+
initial_view_state={"longitude": -122.4, "latitude": 37.8, "zoom": 11},
|
|
79
|
+
map_style=STYLE,
|
|
80
|
+
style={"width": "100%", "height": "100vh"},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
app = rx.App()
|
|
84
|
+
app.add_page(index)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Components
|
|
88
|
+
|
|
89
|
+
| Factory | Wraps | Purpose |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `mapgl.map(...)` | `Map` (default export) | Root map canvas, camera, style, events |
|
|
92
|
+
| `mapgl.marker(...)` | `Marker` | DOM marker at a lng/lat |
|
|
93
|
+
| `mapgl.popup(...)` | `Popup` | Floating info bubble |
|
|
94
|
+
| `mapgl.source(...)` | `Source` | Data source (GeoJSON, tiles, clustering) |
|
|
95
|
+
| `mapgl.layer(...)` | `Layer` | Style layer (circle/line/fill/symbol/heatmap/…) |
|
|
96
|
+
| `mapgl.navigation_control(...)` | `NavigationControl` | Zoom + compass |
|
|
97
|
+
| `mapgl.geolocate_control(...)` | `GeolocateControl` | Find my location |
|
|
98
|
+
| `mapgl.fullscreen_control(...)` | `FullscreenControl` | Fullscreen toggle |
|
|
99
|
+
| `mapgl.scale_control(...)` | `ScaleControl` | Scale bar |
|
|
100
|
+
| `mapgl.attribution_control(...)` | `AttributionControl` | Attribution box |
|
|
101
|
+
| `mapgl.map_provider(...)` | `MapProvider` | Multi-map / synced maps |
|
|
102
|
+
|
|
103
|
+
Optional, heavier overlays live in `reflex_mapgl_maplibre.advanced`
|
|
104
|
+
(`deckgl_overlay`, `draw_control`) and pull extra npm dependencies on demand.
|
|
105
|
+
|
|
106
|
+
Full contract: [`specs/api/component-api-v1.md`](specs/api/component-api-v1.md).
|
|
107
|
+
|
|
108
|
+
## Imperative camera control
|
|
109
|
+
|
|
110
|
+
`map(...)` returns an instance whose camera helpers produce event specs you can
|
|
111
|
+
attach to any handler. The map needs an `id`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
m = mapgl.map(id="main", initial_view_state={...}, map_style=STYLE)
|
|
115
|
+
|
|
116
|
+
rx.button("Paris", on_click=m.fly_to(2.35, 48.85, zoom=11))
|
|
117
|
+
rx.button("Fit area", on_click=m.fit_bounds([[-10, 36], [30, 60]], padding=40))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`fly_to`, `ease_to`, `jump_to`, `fit_bounds` are forwarded to the MapLibre
|
|
121
|
+
`MapRef` (see Technical Design DD-003).
|
|
122
|
+
|
|
123
|
+
## Examples (demo app)
|
|
124
|
+
|
|
125
|
+
The `mapgl_maplibre_demo/` app maps the upstream examples gallery to one page
|
|
126
|
+
each:
|
|
127
|
+
|
|
128
|
+
| Page | Example |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `/` | Basic map |
|
|
131
|
+
| `/controls` | Controls |
|
|
132
|
+
| `/markers` | Markers & Popups |
|
|
133
|
+
| `/geojson` | GeoJSON sources & layers |
|
|
134
|
+
| `/clusters` | Clustering |
|
|
135
|
+
| `/heatmap` | Heatmap |
|
|
136
|
+
| `/interaction` | Click features → Reflex state |
|
|
137
|
+
| `/animation` | Viewport animation (`fly_to` / `fit_bounds`) |
|
|
138
|
+
| `/side-by-side` | Two maps under a `MapProvider` |
|
|
139
|
+
|
|
140
|
+
Run it:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
uv sync # create .venv with the wrapper + dev tools
|
|
144
|
+
cd mapgl_maplibre_demo
|
|
145
|
+
uv run reflex run # the local wrapper is installed in editable mode
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> Basemaps use key-less styles (MapLibre demotiles, CARTO). For MapTiler styles,
|
|
149
|
+
> pass `map_style="https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY"`.
|
|
150
|
+
|
|
151
|
+
## Development & publishing (Reflex custom component, uv)
|
|
152
|
+
|
|
153
|
+
This repo follows the [Reflex custom-components](https://reflex.dev/docs/custom-components/overview/)
|
|
154
|
+
layout (`custom_components/`, a demo app, and a publishable `pyproject.toml`) and
|
|
155
|
+
uses **uv** as the package manager.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 1. Set up the dev environment (creates .venv, installs the wrapper editable
|
|
159
|
+
# plus the dev group: build, twine, pytest, pytest-cov, ruff).
|
|
160
|
+
uv sync
|
|
161
|
+
|
|
162
|
+
# 2. Quality gates.
|
|
163
|
+
uv run ruff check custom_components tests
|
|
164
|
+
uv run pytest # 58 tests, 100% wrapper coverage
|
|
165
|
+
|
|
166
|
+
# 3. Build the component (regenerates the .pyi type stubs and the dist/ artifacts:
|
|
167
|
+
# .whl + .tar.gz). `python -m reflex` keeps the repo root importable so the
|
|
168
|
+
# stub generator can resolve `custom_components.*`.
|
|
169
|
+
uv run python -m reflex component build
|
|
170
|
+
|
|
171
|
+
# 4. Validate the distribution metadata for PyPI.
|
|
172
|
+
uv run twine check dist/*
|
|
173
|
+
|
|
174
|
+
# 5. Publish (requires a PyPI account + API token; see prerequisites docs).
|
|
175
|
+
uv publish # or: uv run twine upload dist/*
|
|
176
|
+
# TestPyPI first: uv publish --publish-url https://test.pypi.org/legacy/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
> `reflex component build` produces the same `dist/` artifacts; `uv build` works
|
|
180
|
+
> too (both invoke the setuptools backend). The published wheel ships the `.pyi`
|
|
181
|
+
> stubs and a `py.typed` marker (PEP 561) for IDE autocomplete.
|
|
182
|
+
|
|
183
|
+
To list the component on the Reflex gallery after publishing: `reflex component share`.
|
|
184
|
+
|
|
185
|
+
## Project layout
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
reflex-mapgl-maplibre/
|
|
189
|
+
├── custom_components/reflex_mapgl_maplibre/ # the wrapper package
|
|
190
|
+
│ ├── base.py # shared base: library, CSS, NoSSR
|
|
191
|
+
│ ├── map.py # Map root + imperative camera helpers
|
|
192
|
+
│ ├── markers.py # Marker, Popup
|
|
193
|
+
│ ├── sources.py # Source, Layer
|
|
194
|
+
│ ├── controls.py # Navigation/Geolocate/Fullscreen/Scale/Attribution
|
|
195
|
+
│ ├── provider.py # MapProvider
|
|
196
|
+
│ └── advanced.py # deck.gl overlay & draw control (optional deps)
|
|
197
|
+
├── mapgl_maplibre_demo/ # demo Reflex app
|
|
198
|
+
├── specs/ # SDD artifacts (see specs/README.md)
|
|
199
|
+
├── tests/ # compile/contract tests
|
|
200
|
+
└── scripts/create_github_repo.sh
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Design docs (SDD)
|
|
204
|
+
|
|
205
|
+
This project is spec-driven. See [`specs/README.md`](specs/README.md):
|
|
206
|
+
PRD · Component API Spec · Technical Design · Phases · Implementation Plan ·
|
|
207
|
+
Tasks · Upstream Analysis (examples catalog).
|
|
208
|
+
|
|
209
|
+
## Compatibility
|
|
210
|
+
|
|
211
|
+
- Python ≥ 3.10, Reflex ≥ 0.9
|
|
212
|
+
- Frontend: `react-map-gl@^8.1.1`, `maplibre-gl@^5`
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
[MIT](LICENSE). Upstream `react-map-gl` and `maplibre-gl` are MIT-licensed;
|
|
217
|
+
this wrapper is published with attribution.
|
|
218
|
+
|
|
219
|
+
## Credits
|
|
220
|
+
|
|
221
|
+
- [react-map-gl](https://github.com/visgl/react-map-gl) by vis.gl
|
|
222
|
+
- [MapLibre GL JS](https://maplibre.org/)
|
|
223
|
+
- Wrapper by Ernesto Crespo
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# reflex-mapgl-maplibre
|
|
2
|
+
|
|
3
|
+
Interactive **MapLibre GL JS** maps for [Reflex](https://reflex.dev), in pure
|
|
4
|
+
Python. A thin, idiomatic wrapper around
|
|
5
|
+
[`react-map-gl`](https://github.com/visgl/react-map-gl) via its
|
|
6
|
+
`react-map-gl/maplibre` endpoint — markers, popups, GeoJSON sources & layers,
|
|
7
|
+
clustering, heatmaps, controls, feature interaction and imperative camera
|
|
8
|
+
control, with **no JavaScript or React required**.
|
|
9
|
+
|
|
10
|
+
> Status: **beta (0.1.0).** Core map, markers/popups, sources/layers, controls,
|
|
11
|
+
> multi-map provider and optional deck.gl/draw overlays are implemented, with a
|
|
12
|
+
> 58-test contract suite (100% wrapper coverage) and a demo app mapped to the
|
|
13
|
+
> upstream examples gallery. See [`specs/`](specs/) for the full design.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Reflex has no built-in map component. `react-map-gl/maplibre` is the standard
|
|
18
|
+
React wrapper for MapLibre, but it is unreachable from Python without a wrapper.
|
|
19
|
+
This package exposes it as Reflex components and ships a demo app that reproduces
|
|
20
|
+
the upstream examples.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv add reflex-mapgl-maplibre # or: pip install reflex-mapgl-maplibre
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It pulls the npm packages `react-map-gl` and `maplibre-gl` into the compiled
|
|
29
|
+
frontend automatically, and injects `maplibre-gl/dist/maplibre-gl.css` for you.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import reflex as rx
|
|
35
|
+
import reflex_mapgl_maplibre as mapgl
|
|
36
|
+
|
|
37
|
+
STYLE = "https://demotiles.maplibre.org/style.json" # key-less basemap
|
|
38
|
+
|
|
39
|
+
def index() -> rx.Component:
|
|
40
|
+
return mapgl.map(
|
|
41
|
+
mapgl.navigation_control(position="top-right"),
|
|
42
|
+
mapgl.marker(longitude=-122.4, latitude=37.8, color="#ef4444"),
|
|
43
|
+
initial_view_state={"longitude": -122.4, "latitude": 37.8, "zoom": 11},
|
|
44
|
+
map_style=STYLE,
|
|
45
|
+
style={"width": "100%", "height": "100vh"},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
app = rx.App()
|
|
49
|
+
app.add_page(index)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Components
|
|
53
|
+
|
|
54
|
+
| Factory | Wraps | Purpose |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `mapgl.map(...)` | `Map` (default export) | Root map canvas, camera, style, events |
|
|
57
|
+
| `mapgl.marker(...)` | `Marker` | DOM marker at a lng/lat |
|
|
58
|
+
| `mapgl.popup(...)` | `Popup` | Floating info bubble |
|
|
59
|
+
| `mapgl.source(...)` | `Source` | Data source (GeoJSON, tiles, clustering) |
|
|
60
|
+
| `mapgl.layer(...)` | `Layer` | Style layer (circle/line/fill/symbol/heatmap/…) |
|
|
61
|
+
| `mapgl.navigation_control(...)` | `NavigationControl` | Zoom + compass |
|
|
62
|
+
| `mapgl.geolocate_control(...)` | `GeolocateControl` | Find my location |
|
|
63
|
+
| `mapgl.fullscreen_control(...)` | `FullscreenControl` | Fullscreen toggle |
|
|
64
|
+
| `mapgl.scale_control(...)` | `ScaleControl` | Scale bar |
|
|
65
|
+
| `mapgl.attribution_control(...)` | `AttributionControl` | Attribution box |
|
|
66
|
+
| `mapgl.map_provider(...)` | `MapProvider` | Multi-map / synced maps |
|
|
67
|
+
|
|
68
|
+
Optional, heavier overlays live in `reflex_mapgl_maplibre.advanced`
|
|
69
|
+
(`deckgl_overlay`, `draw_control`) and pull extra npm dependencies on demand.
|
|
70
|
+
|
|
71
|
+
Full contract: [`specs/api/component-api-v1.md`](specs/api/component-api-v1.md).
|
|
72
|
+
|
|
73
|
+
## Imperative camera control
|
|
74
|
+
|
|
75
|
+
`map(...)` returns an instance whose camera helpers produce event specs you can
|
|
76
|
+
attach to any handler. The map needs an `id`:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
m = mapgl.map(id="main", initial_view_state={...}, map_style=STYLE)
|
|
80
|
+
|
|
81
|
+
rx.button("Paris", on_click=m.fly_to(2.35, 48.85, zoom=11))
|
|
82
|
+
rx.button("Fit area", on_click=m.fit_bounds([[-10, 36], [30, 60]], padding=40))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`fly_to`, `ease_to`, `jump_to`, `fit_bounds` are forwarded to the MapLibre
|
|
86
|
+
`MapRef` (see Technical Design DD-003).
|
|
87
|
+
|
|
88
|
+
## Examples (demo app)
|
|
89
|
+
|
|
90
|
+
The `mapgl_maplibre_demo/` app maps the upstream examples gallery to one page
|
|
91
|
+
each:
|
|
92
|
+
|
|
93
|
+
| Page | Example |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `/` | Basic map |
|
|
96
|
+
| `/controls` | Controls |
|
|
97
|
+
| `/markers` | Markers & Popups |
|
|
98
|
+
| `/geojson` | GeoJSON sources & layers |
|
|
99
|
+
| `/clusters` | Clustering |
|
|
100
|
+
| `/heatmap` | Heatmap |
|
|
101
|
+
| `/interaction` | Click features → Reflex state |
|
|
102
|
+
| `/animation` | Viewport animation (`fly_to` / `fit_bounds`) |
|
|
103
|
+
| `/side-by-side` | Two maps under a `MapProvider` |
|
|
104
|
+
|
|
105
|
+
Run it:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv sync # create .venv with the wrapper + dev tools
|
|
109
|
+
cd mapgl_maplibre_demo
|
|
110
|
+
uv run reflex run # the local wrapper is installed in editable mode
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> Basemaps use key-less styles (MapLibre demotiles, CARTO). For MapTiler styles,
|
|
114
|
+
> pass `map_style="https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY"`.
|
|
115
|
+
|
|
116
|
+
## Development & publishing (Reflex custom component, uv)
|
|
117
|
+
|
|
118
|
+
This repo follows the [Reflex custom-components](https://reflex.dev/docs/custom-components/overview/)
|
|
119
|
+
layout (`custom_components/`, a demo app, and a publishable `pyproject.toml`) and
|
|
120
|
+
uses **uv** as the package manager.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# 1. Set up the dev environment (creates .venv, installs the wrapper editable
|
|
124
|
+
# plus the dev group: build, twine, pytest, pytest-cov, ruff).
|
|
125
|
+
uv sync
|
|
126
|
+
|
|
127
|
+
# 2. Quality gates.
|
|
128
|
+
uv run ruff check custom_components tests
|
|
129
|
+
uv run pytest # 58 tests, 100% wrapper coverage
|
|
130
|
+
|
|
131
|
+
# 3. Build the component (regenerates the .pyi type stubs and the dist/ artifacts:
|
|
132
|
+
# .whl + .tar.gz). `python -m reflex` keeps the repo root importable so the
|
|
133
|
+
# stub generator can resolve `custom_components.*`.
|
|
134
|
+
uv run python -m reflex component build
|
|
135
|
+
|
|
136
|
+
# 4. Validate the distribution metadata for PyPI.
|
|
137
|
+
uv run twine check dist/*
|
|
138
|
+
|
|
139
|
+
# 5. Publish (requires a PyPI account + API token; see prerequisites docs).
|
|
140
|
+
uv publish # or: uv run twine upload dist/*
|
|
141
|
+
# TestPyPI first: uv publish --publish-url https://test.pypi.org/legacy/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
> `reflex component build` produces the same `dist/` artifacts; `uv build` works
|
|
145
|
+
> too (both invoke the setuptools backend). The published wheel ships the `.pyi`
|
|
146
|
+
> stubs and a `py.typed` marker (PEP 561) for IDE autocomplete.
|
|
147
|
+
|
|
148
|
+
To list the component on the Reflex gallery after publishing: `reflex component share`.
|
|
149
|
+
|
|
150
|
+
## Project layout
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
reflex-mapgl-maplibre/
|
|
154
|
+
├── custom_components/reflex_mapgl_maplibre/ # the wrapper package
|
|
155
|
+
│ ├── base.py # shared base: library, CSS, NoSSR
|
|
156
|
+
│ ├── map.py # Map root + imperative camera helpers
|
|
157
|
+
│ ├── markers.py # Marker, Popup
|
|
158
|
+
│ ├── sources.py # Source, Layer
|
|
159
|
+
│ ├── controls.py # Navigation/Geolocate/Fullscreen/Scale/Attribution
|
|
160
|
+
│ ├── provider.py # MapProvider
|
|
161
|
+
│ └── advanced.py # deck.gl overlay & draw control (optional deps)
|
|
162
|
+
├── mapgl_maplibre_demo/ # demo Reflex app
|
|
163
|
+
├── specs/ # SDD artifacts (see specs/README.md)
|
|
164
|
+
├── tests/ # compile/contract tests
|
|
165
|
+
└── scripts/create_github_repo.sh
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Design docs (SDD)
|
|
169
|
+
|
|
170
|
+
This project is spec-driven. See [`specs/README.md`](specs/README.md):
|
|
171
|
+
PRD · Component API Spec · Technical Design · Phases · Implementation Plan ·
|
|
172
|
+
Tasks · Upstream Analysis (examples catalog).
|
|
173
|
+
|
|
174
|
+
## Compatibility
|
|
175
|
+
|
|
176
|
+
- Python ≥ 3.10, Reflex ≥ 0.9
|
|
177
|
+
- Frontend: `react-map-gl@^8.1.1`, `maplibre-gl@^5`
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
[MIT](LICENSE). Upstream `react-map-gl` and `maplibre-gl` are MIT-licensed;
|
|
182
|
+
this wrapper is published with attribution.
|
|
183
|
+
|
|
184
|
+
## Credits
|
|
185
|
+
|
|
186
|
+
- [react-map-gl](https://github.com/visgl/react-map-gl) by vis.gl
|
|
187
|
+
- [MapLibre GL JS](https://maplibre.org/)
|
|
188
|
+
- Wrapper by Ernesto Crespo
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""reflex-mapgl-maplibre.
|
|
2
|
+
|
|
3
|
+
Reflex component library wrapping ``react-map-gl`` (``react-map-gl/maplibre``
|
|
4
|
+
endpoint) to bring the MapLibre GL JS WebGL map engine to pure-Python Reflex apps.
|
|
5
|
+
|
|
6
|
+
Public API
|
|
7
|
+
----------
|
|
8
|
+
>>> import reflex_mapgl_maplibre as mapgl
|
|
9
|
+
>>> mapgl.map(
|
|
10
|
+
... mapgl.navigation_control(position="top-right"),
|
|
11
|
+
... mapgl.marker(longitude=-122.4, latitude=37.8, color="#ef4444"),
|
|
12
|
+
... initial_view_state={"longitude": -122.4, "latitude": 37.8, "zoom": 11},
|
|
13
|
+
... map_style="https://demotiles.maplibre.org/style.json",
|
|
14
|
+
... style={"width": "100%", "height": "100vh"},
|
|
15
|
+
... )
|
|
16
|
+
|
|
17
|
+
Advanced (optional, heavy deps) live in ``reflex_mapgl_maplibre.advanced``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .controls import (
|
|
21
|
+
AttributionControl,
|
|
22
|
+
FullscreenControl,
|
|
23
|
+
GeolocateControl,
|
|
24
|
+
NavigationControl,
|
|
25
|
+
ScaleControl,
|
|
26
|
+
attribution_control,
|
|
27
|
+
fullscreen_control,
|
|
28
|
+
geolocate_control,
|
|
29
|
+
navigation_control,
|
|
30
|
+
scale_control,
|
|
31
|
+
)
|
|
32
|
+
from .map import Map, map
|
|
33
|
+
from .markers import Marker, Popup, marker, popup
|
|
34
|
+
from .provider import MapProvider, map_provider
|
|
35
|
+
from .sources import Layer, Source, layer, source
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# factories (idiomatic, snake_case)
|
|
41
|
+
"map",
|
|
42
|
+
"marker",
|
|
43
|
+
"popup",
|
|
44
|
+
"source",
|
|
45
|
+
"layer",
|
|
46
|
+
"navigation_control",
|
|
47
|
+
"geolocate_control",
|
|
48
|
+
"fullscreen_control",
|
|
49
|
+
"scale_control",
|
|
50
|
+
"attribution_control",
|
|
51
|
+
"map_provider",
|
|
52
|
+
# classes (for typing / subclassing)
|
|
53
|
+
"Map",
|
|
54
|
+
"Marker",
|
|
55
|
+
"Popup",
|
|
56
|
+
"Source",
|
|
57
|
+
"Layer",
|
|
58
|
+
"NavigationControl",
|
|
59
|
+
"GeolocateControl",
|
|
60
|
+
"FullscreenControl",
|
|
61
|
+
"ScaleControl",
|
|
62
|
+
"AttributionControl",
|
|
63
|
+
"MapProvider",
|
|
64
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Optional, heavyweight overlays kept out of the core package (DD-006).
|
|
2
|
+
|
|
3
|
+
These wrap local JSX bridges (``deckgl_overlay.jsx``, ``draw_control.jsx``) that
|
|
4
|
+
use ``react-map-gl/maplibre``'s ``useControl`` hook. They pull large optional npm
|
|
5
|
+
dependencies, so importing this module is opt-in:
|
|
6
|
+
|
|
7
|
+
from reflex_mapgl_maplibre.advanced import deckgl_overlay, draw_control
|
|
8
|
+
|
|
9
|
+
Install the matching extras in your project before using them:
|
|
10
|
+
|
|
11
|
+
* ``deckgl_overlay`` → ``@deck.gl/mapbox`` (and a deck.gl layers package)
|
|
12
|
+
* ``draw_control`` → ``@mapbox/mapbox-gl-draw``
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import reflex as rx
|
|
18
|
+
from reflex.components.component import NoSSRComponent
|
|
19
|
+
|
|
20
|
+
# Copy the local JSX bridges into the compiled frontend's public dir.
|
|
21
|
+
_DECK_PATH = rx.asset("./deckgl_overlay.jsx", shared=True)
|
|
22
|
+
_DRAW_PATH = rx.asset("./draw_control.jsx", shared=True)
|
|
23
|
+
|
|
24
|
+
DECK_PKG = "@deck.gl/mapbox@^9.0.0"
|
|
25
|
+
DRAW_PKG = "@mapbox/mapbox-gl-draw@^1.4.3"
|
|
26
|
+
DRAW_CSS = "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DeckGLOverlay(NoSSRComponent):
|
|
30
|
+
"""deck.gl overlay rendered on top of the map (returns null in the DOM)."""
|
|
31
|
+
|
|
32
|
+
library = f"$/public{_DECK_PATH}"
|
|
33
|
+
tag = "DeckGLOverlay"
|
|
34
|
+
is_default = False
|
|
35
|
+
lib_dependencies: list[str] = [DECK_PKG]
|
|
36
|
+
|
|
37
|
+
# deck.gl layer instances are constructed in the page module and passed in.
|
|
38
|
+
layers: rx.Var[list]
|
|
39
|
+
get_tooltip: rx.Var
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DrawControl(NoSSRComponent):
|
|
43
|
+
"""Polygon/line/point draw control via @mapbox/mapbox-gl-draw."""
|
|
44
|
+
|
|
45
|
+
library = f"$/public{_DRAW_PATH}"
|
|
46
|
+
tag = "DrawControl"
|
|
47
|
+
is_default = False
|
|
48
|
+
lib_dependencies: list[str] = [DRAW_PKG]
|
|
49
|
+
|
|
50
|
+
position: rx.Var[str]
|
|
51
|
+
display_controls_default: rx.Var[bool]
|
|
52
|
+
controls: rx.Var[dict]
|
|
53
|
+
default_mode: rx.Var[str]
|
|
54
|
+
|
|
55
|
+
on_create: rx.EventHandler[rx.event.no_args_event_spec]
|
|
56
|
+
on_update: rx.EventHandler[rx.event.no_args_event_spec]
|
|
57
|
+
on_delete: rx.EventHandler[rx.event.no_args_event_spec]
|
|
58
|
+
|
|
59
|
+
def add_imports(self) -> dict:
|
|
60
|
+
"""Inject the draw control stylesheet."""
|
|
61
|
+
return {"": DRAW_CSS}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
deckgl_overlay = DeckGLOverlay.create
|
|
65
|
+
draw_control = DrawControl.create
|