reflex-map3d 0.1.1__py3-none-any.whl
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_map3d/__init__.py +56 -0
- reflex_map3d/_base.py +78 -0
- reflex_map3d/_base.pyi +87 -0
- reflex_map3d/area_selector.jsx +256 -0
- reflex_map3d/area_selector.py +92 -0
- reflex_map3d/area_selector.pyi +119 -0
- reflex_map3d/geo.js +142 -0
- reflex_map3d/icons.jsx +97 -0
- reflex_map3d/models.py +125 -0
- reflex_map3d/overpass.js +117 -0
- reflex_map3d/overpass.py +287 -0
- reflex_map3d/scene.jsx +570 -0
- reflex_map3d/scene.py +165 -0
- reflex_map3d/scene.pyi +164 -0
- reflex_map3d/tooltip.jsx +179 -0
- reflex_map3d/viewer.jsx +434 -0
- reflex_map3d/viewer.py +135 -0
- reflex_map3d/viewer.pyi +142 -0
- reflex_map3d-0.1.1.dist-info/METADATA +300 -0
- reflex_map3d-0.1.1.dist-info/RECORD +23 -0
- reflex_map3d-0.1.1.dist-info/WHEEL +5 -0
- reflex_map3d-0.1.1.dist-info/licenses/LICENSE +21 -0
- reflex_map3d-0.1.1.dist-info/top_level.txt +1 -0
reflex_map3d/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Reflex custom component: map3d.
|
|
2
|
+
|
|
3
|
+
Generate a real-world 3D city from OpenStreetMap data, straight from a Reflex
|
|
4
|
+
app. A port of https://github.com/cartesiancs/map3d.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._base import NPM_DEPENDENCIES
|
|
8
|
+
from .area_selector import (
|
|
9
|
+
DEFAULT_ATTRIBUTION,
|
|
10
|
+
DEFAULT_TILE_URL,
|
|
11
|
+
Map3dAreaSelector,
|
|
12
|
+
map3d_area_selector,
|
|
13
|
+
)
|
|
14
|
+
from .models import BBox, Feature, to_bbox
|
|
15
|
+
from .overpass import (
|
|
16
|
+
DEFAULT_OVERPASS_URL,
|
|
17
|
+
FALLBACK_OVERPASS_URLS,
|
|
18
|
+
OverpassError,
|
|
19
|
+
buildings_query,
|
|
20
|
+
fetch_buildings,
|
|
21
|
+
fetch_buildings_async,
|
|
22
|
+
fetch_roads,
|
|
23
|
+
fetch_roads_async,
|
|
24
|
+
roads_query,
|
|
25
|
+
to_features,
|
|
26
|
+
)
|
|
27
|
+
from .scene import DEFAULT_SCALE, Map3dScene, map3d_scene
|
|
28
|
+
from .viewer import Map3dViewer, map3d_viewer
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"DEFAULT_ATTRIBUTION",
|
|
32
|
+
"DEFAULT_OVERPASS_URL",
|
|
33
|
+
"DEFAULT_SCALE",
|
|
34
|
+
"DEFAULT_TILE_URL",
|
|
35
|
+
"FALLBACK_OVERPASS_URLS",
|
|
36
|
+
"NPM_DEPENDENCIES",
|
|
37
|
+
"BBox",
|
|
38
|
+
"Feature",
|
|
39
|
+
"Map3dAreaSelector",
|
|
40
|
+
"Map3dScene",
|
|
41
|
+
"Map3dViewer",
|
|
42
|
+
"OverpassError",
|
|
43
|
+
"buildings_query",
|
|
44
|
+
"fetch_buildings",
|
|
45
|
+
"fetch_buildings_async",
|
|
46
|
+
"fetch_roads",
|
|
47
|
+
"fetch_roads_async",
|
|
48
|
+
"map3d_area_selector",
|
|
49
|
+
"map3d_scene",
|
|
50
|
+
"map3d_viewer",
|
|
51
|
+
"roads_query",
|
|
52
|
+
"to_bbox",
|
|
53
|
+
"to_features",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
__version__ = "0.1.0"
|
reflex_map3d/_base.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Shared plumbing for the reflex-map3d components.
|
|
2
|
+
|
|
3
|
+
The React side of this package ships as plain ``.js``/``.jsx`` files next to
|
|
4
|
+
this module. :func:`rx.asset` symlinks them into the consuming app's
|
|
5
|
+
``assets/external/`` tree and hands back a ``$/public/...`` specifier that Vite
|
|
6
|
+
compiles, so no npm package has to be published for the port to work.
|
|
7
|
+
|
|
8
|
+
Every file is registered from *this* module on purpose: shared assets are
|
|
9
|
+
grouped by the module that registers them, and the relative imports between
|
|
10
|
+
the frontend files only resolve when they all land in the same directory.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import reflex as rx
|
|
16
|
+
from reflex.components.component import NoSSRComponent
|
|
17
|
+
|
|
18
|
+
__all__ = ["FRONTEND_FILES", "NPM_DEPENDENCIES", "Map3dComponent", "asset_library"]
|
|
19
|
+
|
|
20
|
+
#: npm packages every map3d component pulls in.
|
|
21
|
+
#:
|
|
22
|
+
#: The versions track the ones cartesiancs/map3d builds against, bumped to the
|
|
23
|
+
#: releases that support the React 19 runtime Reflex ships.
|
|
24
|
+
NPM_DEPENDENCIES: list[str] = [
|
|
25
|
+
"three@0.186.0",
|
|
26
|
+
"@react-three/fiber@9.7.0",
|
|
27
|
+
"@react-three/drei@10.7.8",
|
|
28
|
+
"react-leaflet@5.0.0",
|
|
29
|
+
"leaflet@1.9.4",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
#: The frontend files shipped in the wheel, in dependency order.
|
|
33
|
+
FRONTEND_FILES: tuple[str, ...] = (
|
|
34
|
+
"geo.js",
|
|
35
|
+
"overpass.js",
|
|
36
|
+
"icons.jsx",
|
|
37
|
+
"tooltip.jsx",
|
|
38
|
+
"area_selector.jsx",
|
|
39
|
+
"scene.jsx",
|
|
40
|
+
"viewer.jsx",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _register_assets() -> dict[str, str]:
|
|
45
|
+
"""Link every frontend file into the app and map it to its import path.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A mapping of file name to the ``$/public/...`` module specifier.
|
|
49
|
+
"""
|
|
50
|
+
return {name: rx.asset(name, shared=True).importable_path for name in FRONTEND_FILES}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
_ASSETS = _register_assets()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def asset_library(filename: str) -> str:
|
|
57
|
+
"""Resolve a bundled frontend file to an importable module path.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
filename: The frontend file name, as listed in :data:`FRONTEND_FILES`.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The module specifier to use as a component ``library``.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
KeyError: If the file is not one of the shipped frontend files.
|
|
67
|
+
"""
|
|
68
|
+
return _ASSETS[filename]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Map3dComponent(NoSSRComponent):
|
|
72
|
+
"""Base class for every map3d component.
|
|
73
|
+
|
|
74
|
+
three.js, Leaflet and the Overpass fetches all touch ``window``/``document``
|
|
75
|
+
on mount, so the components are imported dynamically on the client only.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
lib_dependencies: list[str] = NPM_DEPENDENCIES
|
reflex_map3d/_base.pyi
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Stub file for custom_components/reflex_map3d/_base.py"""
|
|
2
|
+
|
|
3
|
+
# ------------------- DO NOT EDIT ----------------------
|
|
4
|
+
# This file was generated by `reflex/utils/pyi_generator.py`!
|
|
5
|
+
# ------------------------------------------------------
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
from reflex_components_core.core.breakpoints import Breakpoints
|
|
9
|
+
from reflex_base.event import (
|
|
10
|
+
EventType,
|
|
11
|
+
PointerEventInfo,
|
|
12
|
+
)
|
|
13
|
+
from reflex_base.vars.base import Var
|
|
14
|
+
from reflex.components.component import NoSSRComponent
|
|
15
|
+
|
|
16
|
+
__all__ = ["FRONTEND_FILES", "NPM_DEPENDENCIES", "Map3dComponent", "asset_library"]
|
|
17
|
+
NPM_DEPENDENCIES: list[str]
|
|
18
|
+
FRONTEND_FILES: tuple[str, ...]
|
|
19
|
+
_ASSETS: dict[str, str]
|
|
20
|
+
|
|
21
|
+
def asset_library(filename: str) -> str: ...
|
|
22
|
+
|
|
23
|
+
class Map3dComponent(NoSSRComponent):
|
|
24
|
+
@classmethod
|
|
25
|
+
def create(
|
|
26
|
+
cls,
|
|
27
|
+
*children,
|
|
28
|
+
style: Sequence[Mapping[str, Any]]
|
|
29
|
+
| Mapping[str, Any]
|
|
30
|
+
| Var[Mapping[str, Any]]
|
|
31
|
+
| Breakpoints
|
|
32
|
+
| None = None,
|
|
33
|
+
key: Any | None = None,
|
|
34
|
+
id: Any | None = None,
|
|
35
|
+
ref: Var | None = None,
|
|
36
|
+
class_name: Any | None = None,
|
|
37
|
+
custom_attrs: dict[str, Any | Var] | None = None,
|
|
38
|
+
on_blur: Optional[EventType[()]] = None,
|
|
39
|
+
on_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
40
|
+
on_context_menu: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
41
|
+
on_double_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
42
|
+
on_focus: Optional[EventType[()]] = None,
|
|
43
|
+
on_mount: Optional[EventType[()]] = None,
|
|
44
|
+
on_mouse_down: Optional[EventType[()]] = None,
|
|
45
|
+
on_mouse_enter: Optional[EventType[()]] = None,
|
|
46
|
+
on_mouse_leave: Optional[EventType[()]] = None,
|
|
47
|
+
on_mouse_move: Optional[EventType[()]] = None,
|
|
48
|
+
on_mouse_out: Optional[EventType[()]] = None,
|
|
49
|
+
on_mouse_over: Optional[EventType[()]] = None,
|
|
50
|
+
on_mouse_up: Optional[EventType[()]] = None,
|
|
51
|
+
on_scroll: Optional[EventType[()]] = None,
|
|
52
|
+
on_scroll_end: Optional[EventType[()]] = None,
|
|
53
|
+
on_unmount: Optional[EventType[()]] = None,
|
|
54
|
+
**props,
|
|
55
|
+
) -> "Map3dComponent":
|
|
56
|
+
"""Create the component.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
*children: The children of the component.
|
|
60
|
+
style: The style of the component.
|
|
61
|
+
key: A unique key for the component.
|
|
62
|
+
id: The id for the component.
|
|
63
|
+
ref: The Var to pass as the ref to the component.
|
|
64
|
+
class_name: The class name for the component.
|
|
65
|
+
custom_attrs: Attributes passed directly to the component.
|
|
66
|
+
on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input.
|
|
67
|
+
on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input.
|
|
68
|
+
on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button.
|
|
69
|
+
on_context_menu: Fired when the user right-clicks on an element.
|
|
70
|
+
on_double_click: Fired when the user double-clicks on an element.
|
|
71
|
+
on_mouse_down: Fired when the user presses a mouse button on an element.
|
|
72
|
+
on_mouse_enter: Fired when the mouse pointer enters the element.
|
|
73
|
+
on_mouse_leave: Fired when the mouse pointer leaves the element.
|
|
74
|
+
on_mouse_move: Fired when the mouse pointer moves over the element.
|
|
75
|
+
on_mouse_out: Fired when the mouse pointer moves out of the element.
|
|
76
|
+
on_mouse_over: Fired when the mouse pointer moves onto the element.
|
|
77
|
+
on_mouse_up: Fired when the user releases a mouse button on an element.
|
|
78
|
+
on_scroll: Fired when the user scrolls the element.
|
|
79
|
+
on_scroll_end: Fired when scrolling ends on the element.
|
|
80
|
+
on_mount: Fired when the component is mounted to the page.
|
|
81
|
+
on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh.
|
|
82
|
+
**props: The props of the component.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The component.
|
|
86
|
+
"""
|
|
87
|
+
...
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaflet bounding-box selector, ported from cartesiancs/map3d for Reflex.
|
|
3
|
+
*
|
|
4
|
+
* The user drags a rectangle over an OpenStreetMap tile layer; the selection
|
|
5
|
+
* is reported back as {north, south, east, west} so a Reflex state can drive
|
|
6
|
+
* the 3D scene with it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import { MapContainer, Rectangle, TileLayer, useMapEvents } from "react-leaflet";
|
|
11
|
+
import L from "leaflet";
|
|
12
|
+
import "leaflet/dist/leaflet.css";
|
|
13
|
+
import { CircleMinus, PointerClick } from "./icons.jsx";
|
|
14
|
+
|
|
15
|
+
import { normalizeBBox } from "./geo.js";
|
|
16
|
+
|
|
17
|
+
const DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
|
18
|
+
const DEFAULT_ATTRIBUTION =
|
|
19
|
+
'© <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors';
|
|
20
|
+
|
|
21
|
+
const iconStyle = { width: "14px", height: "14px" };
|
|
22
|
+
|
|
23
|
+
/** Wrap a longitude back into [-180, 180) after a world-wrapping drag. */
|
|
24
|
+
function wrapLng(latlng) {
|
|
25
|
+
const lng = ((((latlng.lng + 180) % 360) + 360) % 360) - 180;
|
|
26
|
+
return new L.LatLng(latlng.lat, lng);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function boundsToBBox(bounds) {
|
|
30
|
+
const ne = bounds.getNorthEast();
|
|
31
|
+
const sw = bounds.getSouthWest();
|
|
32
|
+
return {
|
|
33
|
+
north: ne.lat,
|
|
34
|
+
south: sw.lat,
|
|
35
|
+
east: ne.lng,
|
|
36
|
+
west: sw.lng,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function RectangleSelector({ dragEnabled, drawBounds, rectangleColor, onDraw, onDone }) {
|
|
41
|
+
const [firstPoint, setFirstPoint] = useState(null);
|
|
42
|
+
const lastLatLng = useRef(null);
|
|
43
|
+
|
|
44
|
+
const emit = useCallback(
|
|
45
|
+
(start, end, done) => {
|
|
46
|
+
const raw = new L.LatLngBounds(start, end);
|
|
47
|
+
const wrapped = new L.LatLngBounds(wrapLng(start), wrapLng(end));
|
|
48
|
+
onDraw(raw);
|
|
49
|
+
if (done) onDone(boundsToBBox(wrapped));
|
|
50
|
+
},
|
|
51
|
+
[onDraw, onDone],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const map = useMapEvents({
|
|
55
|
+
mousedown(event) {
|
|
56
|
+
if (!dragEnabled) setFirstPoint(event.latlng);
|
|
57
|
+
},
|
|
58
|
+
mousemove(event) {
|
|
59
|
+
if (!firstPoint) return;
|
|
60
|
+
lastLatLng.current = event.latlng;
|
|
61
|
+
emit(firstPoint, event.latlng, false);
|
|
62
|
+
},
|
|
63
|
+
mouseup(event) {
|
|
64
|
+
if (!firstPoint) return;
|
|
65
|
+
emit(firstPoint, event.latlng, true);
|
|
66
|
+
setFirstPoint(null);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!map) return undefined;
|
|
72
|
+
const container = map.getContainer();
|
|
73
|
+
|
|
74
|
+
const touchStart = (event) => {
|
|
75
|
+
if (dragEnabled || event.touches.length === 0) return;
|
|
76
|
+
setFirstPoint(map.mouseEventToLatLng(event.touches[0]));
|
|
77
|
+
};
|
|
78
|
+
const touchMove = (event) => {
|
|
79
|
+
if (!firstPoint || event.touches.length === 0) return;
|
|
80
|
+
const latlng = map.mouseEventToLatLng(event.touches[0]);
|
|
81
|
+
lastLatLng.current = latlng;
|
|
82
|
+
emit(firstPoint, latlng, false);
|
|
83
|
+
};
|
|
84
|
+
const touchEnd = () => {
|
|
85
|
+
if (!firstPoint) return;
|
|
86
|
+
emit(firstPoint, lastLatLng.current || firstPoint, true);
|
|
87
|
+
setFirstPoint(null);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
container.addEventListener("touchstart", touchStart);
|
|
91
|
+
container.addEventListener("touchmove", touchMove);
|
|
92
|
+
container.addEventListener("touchend", touchEnd);
|
|
93
|
+
return () => {
|
|
94
|
+
container.removeEventListener("touchstart", touchStart);
|
|
95
|
+
container.removeEventListener("touchmove", touchMove);
|
|
96
|
+
container.removeEventListener("touchend", touchEnd);
|
|
97
|
+
};
|
|
98
|
+
}, [map, dragEnabled, firstPoint, emit]);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!map) return;
|
|
102
|
+
if (dragEnabled) map.dragging.enable();
|
|
103
|
+
else map.dragging.disable();
|
|
104
|
+
}, [dragEnabled, map]);
|
|
105
|
+
|
|
106
|
+
return drawBounds ? (
|
|
107
|
+
<Rectangle bounds={drawBounds} pathOptions={{ color: rectangleColor }} />
|
|
108
|
+
) : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const buttonBase = {
|
|
112
|
+
backdropFilter: "blur(8px)",
|
|
113
|
+
border: "none",
|
|
114
|
+
padding: "0.5rem 1rem",
|
|
115
|
+
borderRadius: "8px",
|
|
116
|
+
cursor: "pointer",
|
|
117
|
+
transition: "0.2s",
|
|
118
|
+
alignItems: "center",
|
|
119
|
+
gap: "0.5rem",
|
|
120
|
+
fontSize: "14px",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function Map3dAreaSelector({
|
|
124
|
+
center = [40.8, -73.95],
|
|
125
|
+
zoom = 13,
|
|
126
|
+
tileUrl = DEFAULT_TILE_URL,
|
|
127
|
+
attribution = DEFAULT_ATTRIBUTION,
|
|
128
|
+
minZoom = 2,
|
|
129
|
+
maxZoom = 19,
|
|
130
|
+
rectangleColor = "#1f6feb",
|
|
131
|
+
showControls = true,
|
|
132
|
+
selectLabel = "Select Box",
|
|
133
|
+
dragLabel = "Back to Drag",
|
|
134
|
+
clearLabel = "Remove Box",
|
|
135
|
+
onSelect,
|
|
136
|
+
onClear,
|
|
137
|
+
onModeChange,
|
|
138
|
+
children,
|
|
139
|
+
css,
|
|
140
|
+
style,
|
|
141
|
+
className,
|
|
142
|
+
...rest
|
|
143
|
+
}) {
|
|
144
|
+
const [dragEnabled, setDragEnabled] = useState(true);
|
|
145
|
+
const [drawBounds, setDrawBounds] = useState(null);
|
|
146
|
+
const [hasSelection, setHasSelection] = useState(false);
|
|
147
|
+
|
|
148
|
+
const mapCenter = useMemo(() => {
|
|
149
|
+
if (Array.isArray(center) && center.length >= 2) return [Number(center[0]), Number(center[1])];
|
|
150
|
+
if (center && typeof center === "object") return [Number(center.lat), Number(center.lng)];
|
|
151
|
+
return [40.8, -73.95];
|
|
152
|
+
}, [center]);
|
|
153
|
+
|
|
154
|
+
const handleDone = useCallback(
|
|
155
|
+
(bbox) => {
|
|
156
|
+
setHasSelection(true);
|
|
157
|
+
if (onSelect) onSelect(bbox);
|
|
158
|
+
},
|
|
159
|
+
[onSelect],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const handleToggleDrag = () => {
|
|
163
|
+
const next = !dragEnabled;
|
|
164
|
+
setDragEnabled(next);
|
|
165
|
+
if (onModeChange) onModeChange(next);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleClear = () => {
|
|
169
|
+
setDrawBounds(null);
|
|
170
|
+
setHasSelection(false);
|
|
171
|
+
setDragEnabled(true);
|
|
172
|
+
if (onModeChange) onModeChange(true);
|
|
173
|
+
if (onClear) onClear();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Reflex hands its style props down as a class (emotion consumes `css`), so
|
|
177
|
+
// the wrapper must not pin its own width/height inline: an inline value would
|
|
178
|
+
// beat the class and collapse the component to its parent's size.
|
|
179
|
+
const wrapperStyle = { position: "relative", ...(css || {}), ...(style || {}) };
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className={className} style={wrapperStyle} {...rest}>
|
|
183
|
+
{showControls && (
|
|
184
|
+
<div
|
|
185
|
+
style={{
|
|
186
|
+
position: "absolute",
|
|
187
|
+
zIndex: 999,
|
|
188
|
+
right: "1rem",
|
|
189
|
+
top: "1rem",
|
|
190
|
+
display: "flex",
|
|
191
|
+
justifyContent: "flex-end",
|
|
192
|
+
gap: "0.5rem",
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
onClick={handleClear}
|
|
198
|
+
style={{
|
|
199
|
+
...buttonBase,
|
|
200
|
+
display: !hasSelection || dragEnabled ? "none" : "flex",
|
|
201
|
+
color: "#ffffff",
|
|
202
|
+
backgroundColor: "#ef4444",
|
|
203
|
+
outline: "#ef4444c2 solid 0.1rem",
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
<CircleMinus style={iconStyle} /> {clearLabel}
|
|
207
|
+
</button>
|
|
208
|
+
|
|
209
|
+
<button
|
|
210
|
+
type="button"
|
|
211
|
+
onClick={handleToggleDrag}
|
|
212
|
+
style={{
|
|
213
|
+
...buttonBase,
|
|
214
|
+
display: "flex",
|
|
215
|
+
color: dragEnabled ? "#ffffff" : "#000000",
|
|
216
|
+
backgroundColor: dragEnabled ? "#007bffe8" : "#ffffffd8",
|
|
217
|
+
outline: dragEnabled
|
|
218
|
+
? "#086ad4c2 solid 0.1rem"
|
|
219
|
+
: "rgba(240, 240, 244, 0.51) solid 0.1rem",
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{dragEnabled ? (
|
|
223
|
+
<>
|
|
224
|
+
<PointerClick style={iconStyle} />
|
|
225
|
+
<span>{selectLabel}</span>
|
|
226
|
+
</>
|
|
227
|
+
) : (
|
|
228
|
+
<span>{dragLabel}</span>
|
|
229
|
+
)}
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
<MapContainer
|
|
235
|
+
center={mapCenter}
|
|
236
|
+
zoom={zoom}
|
|
237
|
+
minZoom={minZoom}
|
|
238
|
+
maxZoom={maxZoom}
|
|
239
|
+
style={{ height: "100%", width: "100%" }}
|
|
240
|
+
>
|
|
241
|
+
<TileLayer attribution={attribution} url={tileUrl} />
|
|
242
|
+
<RectangleSelector
|
|
243
|
+
dragEnabled={dragEnabled}
|
|
244
|
+
drawBounds={drawBounds}
|
|
245
|
+
rectangleColor={rectangleColor}
|
|
246
|
+
onDraw={setDrawBounds}
|
|
247
|
+
onDone={handleDone}
|
|
248
|
+
/>
|
|
249
|
+
</MapContainer>
|
|
250
|
+
{children}
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export { normalizeBBox };
|
|
256
|
+
export default Map3dAreaSelector;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""The Leaflet bounding-box selector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import reflex as rx
|
|
8
|
+
|
|
9
|
+
from ._base import Map3dComponent, asset_library
|
|
10
|
+
|
|
11
|
+
__all__ = ["Map3dAreaSelector", "map3d_area_selector"]
|
|
12
|
+
|
|
13
|
+
DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
14
|
+
DEFAULT_ATTRIBUTION = (
|
|
15
|
+
'© <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Map3dAreaSelector(Map3dComponent):
|
|
20
|
+
"""An OpenStreetMap map on which the user drags out a bounding box.
|
|
21
|
+
|
|
22
|
+
Switch the map into "Select Box" mode, drag a rectangle, and the selection
|
|
23
|
+
is pushed to the backend through ``on_select`` as
|
|
24
|
+
``{"north": ..., "south": ..., "east": ..., "west": ...}``.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
library = asset_library("area_selector.jsx")
|
|
28
|
+
|
|
29
|
+
tag = "Map3dAreaSelector"
|
|
30
|
+
|
|
31
|
+
# Where the map opens, as ``[lat, lng]``.
|
|
32
|
+
center: rx.Var[list[float]] = rx.Var.create([40.8, -73.95])
|
|
33
|
+
|
|
34
|
+
# The initial zoom level.
|
|
35
|
+
zoom: rx.Var[int] = rx.Var.create(13)
|
|
36
|
+
|
|
37
|
+
# The XYZ raster tile template to render.
|
|
38
|
+
tile_url: rx.Var[str] = rx.Var.create(DEFAULT_TILE_URL)
|
|
39
|
+
|
|
40
|
+
# The attribution shown in the map corner. Keep it when using OSM tiles.
|
|
41
|
+
attribution: rx.Var[str] = rx.Var.create(DEFAULT_ATTRIBUTION)
|
|
42
|
+
|
|
43
|
+
# The smallest zoom level the user may reach.
|
|
44
|
+
min_zoom: rx.Var[int] = rx.Var.create(2)
|
|
45
|
+
|
|
46
|
+
# The largest zoom level the user may reach.
|
|
47
|
+
max_zoom: rx.Var[int] = rx.Var.create(19)
|
|
48
|
+
|
|
49
|
+
# The stroke colour of the selection rectangle.
|
|
50
|
+
rectangle_color: rx.Var[str] = rx.Var.create("#1f6feb")
|
|
51
|
+
|
|
52
|
+
# Whether to render the built-in mode/clear buttons.
|
|
53
|
+
show_controls: rx.Var[bool] = rx.Var.create(True)
|
|
54
|
+
|
|
55
|
+
# Label of the button that switches into rectangle-drawing mode.
|
|
56
|
+
select_label: rx.Var[str] = rx.Var.create("Select Box")
|
|
57
|
+
|
|
58
|
+
# Label of the button that switches back to panning.
|
|
59
|
+
drag_label: rx.Var[str] = rx.Var.create("Back to Drag")
|
|
60
|
+
|
|
61
|
+
# Label of the button that clears the current selection.
|
|
62
|
+
clear_label: rx.Var[str] = rx.Var.create("Remove Box")
|
|
63
|
+
|
|
64
|
+
# Fired with the bounding box whenever the user finishes a drag.
|
|
65
|
+
on_select: rx.EventHandler[lambda bbox: [bbox]]
|
|
66
|
+
|
|
67
|
+
# Fired when the user clears the current selection.
|
|
68
|
+
on_clear: rx.EventHandler[lambda: []]
|
|
69
|
+
|
|
70
|
+
# Fired with ``True`` for pan mode and ``False`` for draw mode.
|
|
71
|
+
on_mode_change: rx.EventHandler[lambda drag_enabled: [drag_enabled]]
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def create(cls, *children: Any, **props: Any) -> Map3dAreaSelector:
|
|
75
|
+
"""Create the selector, defaulting to a usable size.
|
|
76
|
+
|
|
77
|
+
Leaflet needs a sized container, so a default height is applied unless
|
|
78
|
+
the caller sets one.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
*children: Extra nodes rendered on top of the map.
|
|
82
|
+
**props: The component props.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The component instance.
|
|
86
|
+
"""
|
|
87
|
+
props.setdefault("height", "480px")
|
|
88
|
+
props.setdefault("width", "100%")
|
|
89
|
+
return super().create(*children, **props)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
map3d_area_selector = Map3dAreaSelector.create
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Stub file for custom_components/reflex_map3d/area_selector.py"""
|
|
2
|
+
|
|
3
|
+
# ------------------- DO NOT EDIT ----------------------
|
|
4
|
+
# This file was generated by `reflex/utils/pyi_generator.py`!
|
|
5
|
+
# ------------------------------------------------------
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
from reflex_components_core.core.breakpoints import Breakpoints
|
|
9
|
+
from reflex_base.event import (
|
|
10
|
+
EventType,
|
|
11
|
+
PointerEventInfo,
|
|
12
|
+
)
|
|
13
|
+
from reflex_base.vars.base import Var
|
|
14
|
+
from ._base import Map3dComponent
|
|
15
|
+
|
|
16
|
+
__all__ = ["Map3dAreaSelector", "map3d_area_selector"]
|
|
17
|
+
DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
18
|
+
DEFAULT_ATTRIBUTION = (
|
|
19
|
+
'© <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class Map3dAreaSelector(Map3dComponent):
|
|
23
|
+
@classmethod
|
|
24
|
+
def create(
|
|
25
|
+
cls,
|
|
26
|
+
*children,
|
|
27
|
+
center: Var[list[float]] | list[float] | None = None,
|
|
28
|
+
zoom: Var[int] | int | None = None,
|
|
29
|
+
tile_url: Var[str] | str | None = None,
|
|
30
|
+
attribution: Var[str] | str | None = None,
|
|
31
|
+
min_zoom: Var[int] | int | None = None,
|
|
32
|
+
max_zoom: Var[int] | int | None = None,
|
|
33
|
+
rectangle_color: Var[str] | str | None = None,
|
|
34
|
+
show_controls: Var[bool] | bool | None = None,
|
|
35
|
+
select_label: Var[str] | str | None = None,
|
|
36
|
+
drag_label: Var[str] | str | None = None,
|
|
37
|
+
clear_label: Var[str] | str | None = None,
|
|
38
|
+
style: Sequence[Mapping[str, Any]]
|
|
39
|
+
| Mapping[str, Any]
|
|
40
|
+
| Var[Mapping[str, Any]]
|
|
41
|
+
| Breakpoints
|
|
42
|
+
| None = None,
|
|
43
|
+
key: Any | None = None,
|
|
44
|
+
id: Any | None = None,
|
|
45
|
+
ref: Var | None = None,
|
|
46
|
+
class_name: Any | None = None,
|
|
47
|
+
custom_attrs: dict[str, Any | Var] | None = None,
|
|
48
|
+
on_blur: Optional[EventType[()]] = None,
|
|
49
|
+
on_clear: Optional[EventType[Any]] = None,
|
|
50
|
+
on_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
51
|
+
on_context_menu: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
52
|
+
on_double_click: Optional[EventType[()] | EventType[PointerEventInfo]] = None,
|
|
53
|
+
on_focus: Optional[EventType[()]] = None,
|
|
54
|
+
on_mode_change: Optional[EventType[Any]] = None,
|
|
55
|
+
on_mount: Optional[EventType[()]] = None,
|
|
56
|
+
on_mouse_down: Optional[EventType[()]] = None,
|
|
57
|
+
on_mouse_enter: Optional[EventType[()]] = None,
|
|
58
|
+
on_mouse_leave: Optional[EventType[()]] = None,
|
|
59
|
+
on_mouse_move: Optional[EventType[()]] = None,
|
|
60
|
+
on_mouse_out: Optional[EventType[()]] = None,
|
|
61
|
+
on_mouse_over: Optional[EventType[()]] = None,
|
|
62
|
+
on_mouse_up: Optional[EventType[()]] = None,
|
|
63
|
+
on_scroll: Optional[EventType[()]] = None,
|
|
64
|
+
on_scroll_end: Optional[EventType[()]] = None,
|
|
65
|
+
on_select: Optional[EventType[Any]] = None,
|
|
66
|
+
on_unmount: Optional[EventType[()]] = None,
|
|
67
|
+
**props,
|
|
68
|
+
) -> "Map3dAreaSelector":
|
|
69
|
+
"""Create the selector, defaulting to a usable size.
|
|
70
|
+
|
|
71
|
+
Leaflet needs a sized container, so a default height is applied unless
|
|
72
|
+
the caller sets one.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
*children: Extra nodes rendered on top of the map.
|
|
76
|
+
center: Where the map opens, as ``[lat, lng]``.
|
|
77
|
+
zoom: The initial zoom level.
|
|
78
|
+
tile_url: The XYZ raster tile template to render.
|
|
79
|
+
attribution: The attribution shown in the map corner. Keep it when using OSM tiles.
|
|
80
|
+
min_zoom: The smallest zoom level the user may reach.
|
|
81
|
+
max_zoom: The largest zoom level the user may reach.
|
|
82
|
+
rectangle_color: The stroke colour of the selection rectangle.
|
|
83
|
+
show_controls: Whether to render the built-in mode/clear buttons.
|
|
84
|
+
select_label: Label of the button that switches into rectangle-drawing mode.
|
|
85
|
+
drag_label: Label of the button that switches back to panning.
|
|
86
|
+
clear_label: Label of the button that clears the current selection.
|
|
87
|
+
style: The style of the component.
|
|
88
|
+
key: A unique key for the component.
|
|
89
|
+
id: The id for the component.
|
|
90
|
+
ref: The Var to pass as the ref to the component.
|
|
91
|
+
class_name: The class name for the component.
|
|
92
|
+
custom_attrs: Attributes passed directly to the component.
|
|
93
|
+
on_focus: Fired when the element (or some element inside of it) receives focus. For example, it is called when the user clicks on a text input.
|
|
94
|
+
on_blur: Fired when focus has left the element (or left some element inside of it). For example, it is called when the user clicks outside of a focused text input.
|
|
95
|
+
on_click: Fired when the user clicks on an element. For example, it's called when the user clicks on a button.
|
|
96
|
+
on_context_menu: Fired when the user right-clicks on an element.
|
|
97
|
+
on_double_click: Fired when the user double-clicks on an element.
|
|
98
|
+
on_mouse_down: Fired when the user presses a mouse button on an element.
|
|
99
|
+
on_mouse_enter: Fired when the mouse pointer enters the element.
|
|
100
|
+
on_mouse_leave: Fired when the mouse pointer leaves the element.
|
|
101
|
+
on_mouse_move: Fired when the mouse pointer moves over the element.
|
|
102
|
+
on_mouse_out: Fired when the mouse pointer moves out of the element.
|
|
103
|
+
on_mouse_over: Fired when the mouse pointer moves onto the element.
|
|
104
|
+
on_mouse_up: Fired when the user releases a mouse button on an element.
|
|
105
|
+
on_scroll: Fired when the user scrolls the element.
|
|
106
|
+
on_scroll_end: Fired when scrolling ends on the element.
|
|
107
|
+
on_mount: Fired when the component is mounted to the page.
|
|
108
|
+
on_unmount: Fired when the component is removed from the page. Only called during navigation, not on page refresh.
|
|
109
|
+
on_select: Fired with the bounding box whenever the user finishes a drag.
|
|
110
|
+
on_clear: Fired when the user clears the current selection.
|
|
111
|
+
on_mode_change: Fired with ``True`` for pan mode and ``False`` for draw mode.
|
|
112
|
+
**props: The component props.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The component instance.
|
|
116
|
+
"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
map3d_area_selector = Map3dAreaSelector.create
|