globle files
Browse files- assets/globe.css +6 -0
- assets/globe.js +184 -0
- assets/globe_head.html +6 -0
- ui/globe.py +135 -0
assets/globe.css
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* assets/globe.css */
|
| 2 |
+
#map {
|
| 3 |
+
width: 100%;
|
| 4 |
+
height: 100%;
|
| 5 |
+
min-height: 100vh;
|
| 6 |
+
}
|
assets/globe.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* assets/globe.js */
|
| 2 |
+
const GLOBE_DEFAULTS = {
|
| 3 |
+
style: "https://tiles.openfreemap.org/styles/liberty",
|
| 4 |
+
center: [0, 20],
|
| 5 |
+
zoom: 1.5,
|
| 6 |
+
minZoom: 0,
|
| 7 |
+
maxZoom: 22,
|
| 8 |
+
bearing: 0,
|
| 9 |
+
pitch: 0,
|
| 10 |
+
minPitch: 0,
|
| 11 |
+
maxPitch: 85,
|
| 12 |
+
projection: "globe",
|
| 13 |
+
attributionControl: true,
|
| 14 |
+
scrollZoom: true,
|
| 15 |
+
dragRotate: true,
|
| 16 |
+
dragPan: true,
|
| 17 |
+
keyboard: true,
|
| 18 |
+
doubleClickZoom: true,
|
| 19 |
+
touchZoomRotate: true,
|
| 20 |
+
interactive: true,
|
| 21 |
+
showGeolocateControl: true,
|
| 22 |
+
useCurrentLocation: false,
|
| 23 |
+
geolocateZoom: 10,
|
| 24 |
+
trackUserLocation: false,
|
| 25 |
+
showUserLocation: true,
|
| 26 |
+
showAccuracyCircle: true,
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
function parseBool(value, fallback) {
|
| 30 |
+
if (value === undefined) return fallback;
|
| 31 |
+
return value === "true" || value === "1";
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
function parseNumber(value, fallback) {
|
| 35 |
+
if (value === undefined || value === "") return fallback;
|
| 36 |
+
const n = Number(value);
|
| 37 |
+
return Number.isFinite(n) ? n : fallback;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
function parseCenter(value, fallback) {
|
| 41 |
+
if (!value) return fallback;
|
| 42 |
+
try {
|
| 43 |
+
const parsed = JSON.parse(value);
|
| 44 |
+
if (Array.isArray(parsed) && parsed.length === 2) return parsed;
|
| 45 |
+
} catch {
|
| 46 |
+
const parts = value.split(",").map((s) => Number(s.trim()));
|
| 47 |
+
if (parts.length === 2 && parts.every(Number.isFinite)) return parts;
|
| 48 |
+
}
|
| 49 |
+
return fallback;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function readOptionsFromElement(el) {
|
| 53 |
+
const d = el.dataset;
|
| 54 |
+
return {
|
| 55 |
+
style: d.style,
|
| 56 |
+
center: parseCenter(d.center, undefined),
|
| 57 |
+
zoom: parseNumber(d.zoom, undefined),
|
| 58 |
+
minZoom: parseNumber(d.minZoom, undefined),
|
| 59 |
+
maxZoom: parseNumber(d.maxZoom, undefined),
|
| 60 |
+
bearing: parseNumber(d.bearing, undefined),
|
| 61 |
+
pitch: parseNumber(d.pitch, undefined),
|
| 62 |
+
minPitch: parseNumber(d.minPitch, undefined),
|
| 63 |
+
maxPitch: parseNumber(d.maxPitch, undefined),
|
| 64 |
+
projection: d.projection,
|
| 65 |
+
attributionControl: parseBool(d.attributionControl, undefined),
|
| 66 |
+
scrollZoom: parseBool(d.scrollZoom, undefined),
|
| 67 |
+
dragRotate: parseBool(d.dragRotate, undefined),
|
| 68 |
+
dragPan: parseBool(d.dragPan, undefined),
|
| 69 |
+
keyboard: parseBool(d.keyboard, undefined),
|
| 70 |
+
doubleClickZoom: parseBool(d.doubleClickZoom, undefined),
|
| 71 |
+
touchZoomRotate: parseBool(d.touchZoomRotate, undefined),
|
| 72 |
+
interactive: parseBool(d.interactive, undefined),
|
| 73 |
+
showGeolocateControl: parseBool(d.showGeolocateControl, undefined),
|
| 74 |
+
useCurrentLocation: parseBool(d.useCurrentLocation, undefined),
|
| 75 |
+
geolocateZoom: parseNumber(d.geolocateZoom, undefined),
|
| 76 |
+
trackUserLocation: parseBool(d.trackUserLocation, undefined),
|
| 77 |
+
showUserLocation: parseBool(d.showUserLocation, undefined),
|
| 78 |
+
showAccuracyCircle: parseBool(d.showAccuracyCircle, undefined),
|
| 79 |
+
};
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
function mergeGlobeOptions(...sources) {
|
| 83 |
+
const merged = { ...GLOBE_DEFAULTS };
|
| 84 |
+
for (const source of sources) {
|
| 85 |
+
if (!source) continue;
|
| 86 |
+
for (const [key, value] of Object.entries(source)) {
|
| 87 |
+
if (value !== undefined) merged[key] = value;
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
return merged;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
function initGlobe(options = {}) {
|
| 94 |
+
if (typeof maplibregl === "undefined") {
|
| 95 |
+
setTimeout(() => initGlobe(options), 50);
|
| 96 |
+
return;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
const container = element.querySelector("#map");
|
| 100 |
+
if (!container || container.dataset.mapInit) return;
|
| 101 |
+
container.dataset.mapInit = "1";
|
| 102 |
+
|
| 103 |
+
const opts = mergeGlobeOptions(readOptionsFromElement(container), options);
|
| 104 |
+
const {
|
| 105 |
+
projection,
|
| 106 |
+
attributionControl,
|
| 107 |
+
scrollZoom,
|
| 108 |
+
dragRotate,
|
| 109 |
+
dragPan,
|
| 110 |
+
keyboard,
|
| 111 |
+
doubleClickZoom,
|
| 112 |
+
touchZoomRotate,
|
| 113 |
+
interactive,
|
| 114 |
+
showGeolocateControl,
|
| 115 |
+
useCurrentLocation,
|
| 116 |
+
geolocateZoom,
|
| 117 |
+
trackUserLocation,
|
| 118 |
+
showUserLocation,
|
| 119 |
+
showAccuracyCircle,
|
| 120 |
+
...mapOptions
|
| 121 |
+
} = opts;
|
| 122 |
+
|
| 123 |
+
const map = new maplibregl.Map({
|
| 124 |
+
container,
|
| 125 |
+
attributionControl,
|
| 126 |
+
scrollZoom,
|
| 127 |
+
dragRotate,
|
| 128 |
+
dragPan,
|
| 129 |
+
keyboard,
|
| 130 |
+
doubleClickZoom,
|
| 131 |
+
touchZoomRotate,
|
| 132 |
+
interactive,
|
| 133 |
+
...mapOptions,
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
let geolocateControl = null;
|
| 137 |
+
|
| 138 |
+
if (showGeolocateControl) {
|
| 139 |
+
geolocateControl = new maplibregl.GeolocateControl({
|
| 140 |
+
positionOptions: { enableHighAccuracy: true },
|
| 141 |
+
trackUserLocation,
|
| 142 |
+
showUserLocation,
|
| 143 |
+
showAccuracyCircle,
|
| 144 |
+
});
|
| 145 |
+
map.addControl(geolocateControl, "top-right");
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
function flyToCurrentLocation() {
|
| 149 |
+
if (!navigator.geolocation) return;
|
| 150 |
+
navigator.geolocation.getCurrentPosition(
|
| 151 |
+
(pos) => {
|
| 152 |
+
map.flyTo({
|
| 153 |
+
center: [pos.coords.longitude, pos.coords.latitude],
|
| 154 |
+
zoom: geolocateZoom,
|
| 155 |
+
essential: true,
|
| 156 |
+
});
|
| 157 |
+
},
|
| 158 |
+
(err) => console.warn("Geolocation failed:", err.message),
|
| 159 |
+
{ enableHighAccuracy: true }
|
| 160 |
+
);
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
map.on("style.load", () => {
|
| 164 |
+
map.setProjection({ type: projection });
|
| 165 |
+
map.resize();
|
| 166 |
+
});
|
| 167 |
+
|
| 168 |
+
map.on("load", () => {
|
| 169 |
+
map.resize();
|
| 170 |
+
if (useCurrentLocation) {
|
| 171 |
+
if (geolocateControl) {
|
| 172 |
+
geolocateControl.trigger();
|
| 173 |
+
} else {
|
| 174 |
+
flyToCurrentLocation();
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
window.addEventListener("resize", () => map.resize());
|
| 180 |
+
|
| 181 |
+
return map;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
initGlobe();
|
assets/globe_head.html
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- assets/globe_head.html -->
|
| 2 |
+
<link
|
| 3 |
+
href="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.css"
|
| 4 |
+
rel="stylesheet"
|
| 5 |
+
/>
|
| 6 |
+
<script src="https://unpkg.com/maplibre-gl@5.24.0/dist/maplibre-gl.js"></script>
|
ui/globe.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ui/globe.py
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
|
| 7 |
+
ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _read_asset(name: str) -> str:
|
| 11 |
+
return (ASSETS_DIR / name).read_text(encoding="utf-8")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _map_data_attrs(
|
| 15 |
+
*,
|
| 16 |
+
style: str = "https://tiles.openfreemap.org/styles/liberty",
|
| 17 |
+
center: tuple[float, float] = (0.0, 20.0),
|
| 18 |
+
zoom: float = 1.5,
|
| 19 |
+
min_zoom: float = 0.0,
|
| 20 |
+
max_zoom: float = 22.0,
|
| 21 |
+
bearing: float = 0.0,
|
| 22 |
+
pitch: float = 0.0,
|
| 23 |
+
min_pitch: float = 0.0,
|
| 24 |
+
max_pitch: float = 85.0,
|
| 25 |
+
projection: str = "globe",
|
| 26 |
+
attribution_control: bool = True,
|
| 27 |
+
scroll_zoom: bool = True,
|
| 28 |
+
drag_rotate: bool = True,
|
| 29 |
+
drag_pan: bool = True,
|
| 30 |
+
keyboard: bool = True,
|
| 31 |
+
double_click_zoom: bool = True,
|
| 32 |
+
touch_zoom_rotate: bool = True,
|
| 33 |
+
interactive: bool = True,
|
| 34 |
+
show_geolocate_control: bool = True,
|
| 35 |
+
use_current_location: bool = False,
|
| 36 |
+
geolocate_zoom: float = 10.0,
|
| 37 |
+
track_user_location: bool = False,
|
| 38 |
+
show_user_location: bool = True,
|
| 39 |
+
show_accuracy_circle: bool = True,
|
| 40 |
+
) -> str:
|
| 41 |
+
attrs = {
|
| 42 |
+
"data-style": style,
|
| 43 |
+
"data-center": json.dumps(list(center)),
|
| 44 |
+
"data-zoom": zoom,
|
| 45 |
+
"data-min-zoom": min_zoom,
|
| 46 |
+
"data-max-zoom": max_zoom,
|
| 47 |
+
"data-bearing": bearing,
|
| 48 |
+
"data-pitch": pitch,
|
| 49 |
+
"data-min-pitch": min_pitch,
|
| 50 |
+
"data-max-pitch": max_pitch,
|
| 51 |
+
"data-projection": projection,
|
| 52 |
+
"data-attribution-control": str(attribution_control).lower(),
|
| 53 |
+
"data-scroll-zoom": str(scroll_zoom).lower(),
|
| 54 |
+
"data-drag-rotate": str(drag_rotate).lower(),
|
| 55 |
+
"data-drag-pan": str(drag_pan).lower(),
|
| 56 |
+
"data-keyboard": str(keyboard).lower(),
|
| 57 |
+
"data-double-click-zoom": str(double_click_zoom).lower(),
|
| 58 |
+
"data-touch-zoom-rotate": str(touch_zoom_rotate).lower(),
|
| 59 |
+
"data-interactive": str(interactive).lower(),
|
| 60 |
+
"data-show-geolocate-control": str(show_geolocate_control).lower(),
|
| 61 |
+
"data-use-current-location": str(use_current_location).lower(),
|
| 62 |
+
"data-geolocate-zoom": geolocate_zoom,
|
| 63 |
+
"data-track-user-location": str(track_user_location).lower(),
|
| 64 |
+
"data-show-user-location": str(show_user_location).lower(),
|
| 65 |
+
"data-show-accuracy-circle": str(show_accuracy_circle).lower(),
|
| 66 |
+
}
|
| 67 |
+
return " ".join(f'{key}="{value}"' for key, value in attrs.items())
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def render_globe_panel(
|
| 71 |
+
*,
|
| 72 |
+
style: str = "https://tiles.openfreemap.org/styles/liberty",
|
| 73 |
+
center: tuple[float, float] = (0.0, 20.0),
|
| 74 |
+
zoom: float = 1.5,
|
| 75 |
+
min_zoom: float = 0.0,
|
| 76 |
+
max_zoom: float = 22.0,
|
| 77 |
+
bearing: float = 0.0,
|
| 78 |
+
pitch: float = 0.0,
|
| 79 |
+
min_pitch: float = 0.0,
|
| 80 |
+
max_pitch: float = 85.0,
|
| 81 |
+
projection: str = "globe",
|
| 82 |
+
attribution_control: bool = True,
|
| 83 |
+
scroll_zoom: bool = True,
|
| 84 |
+
drag_rotate: bool = True,
|
| 85 |
+
drag_pan: bool = True,
|
| 86 |
+
keyboard: bool = True,
|
| 87 |
+
double_click_zoom: bool = True,
|
| 88 |
+
touch_zoom_rotate: bool = True,
|
| 89 |
+
interactive: bool = True,
|
| 90 |
+
show_geolocate_control: bool = True,
|
| 91 |
+
use_current_location: bool = False,
|
| 92 |
+
geolocate_zoom: float = 10.0,
|
| 93 |
+
track_user_location: bool = False,
|
| 94 |
+
show_user_location: bool = True,
|
| 95 |
+
show_accuracy_circle: bool = True,
|
| 96 |
+
):
|
| 97 |
+
data_attrs = _map_data_attrs(
|
| 98 |
+
style=style,
|
| 99 |
+
center=center,
|
| 100 |
+
zoom=zoom,
|
| 101 |
+
min_zoom=min_zoom,
|
| 102 |
+
max_zoom=max_zoom,
|
| 103 |
+
bearing=bearing,
|
| 104 |
+
pitch=pitch,
|
| 105 |
+
min_pitch=min_pitch,
|
| 106 |
+
max_pitch=max_pitch,
|
| 107 |
+
projection=projection,
|
| 108 |
+
attribution_control=attribution_control,
|
| 109 |
+
scroll_zoom=scroll_zoom,
|
| 110 |
+
drag_rotate=drag_rotate,
|
| 111 |
+
drag_pan=drag_pan,
|
| 112 |
+
keyboard=keyboard,
|
| 113 |
+
double_click_zoom=double_click_zoom,
|
| 114 |
+
touch_zoom_rotate=touch_zoom_rotate,
|
| 115 |
+
interactive=interactive,
|
| 116 |
+
show_geolocate_control=show_geolocate_control,
|
| 117 |
+
use_current_location=use_current_location,
|
| 118 |
+
geolocate_zoom=geolocate_zoom,
|
| 119 |
+
track_user_location=track_user_location,
|
| 120 |
+
show_user_location=show_user_location,
|
| 121 |
+
show_accuracy_circle=show_accuracy_circle,
|
| 122 |
+
)
|
| 123 |
+
return gr.HTML(
|
| 124 |
+
value=f'<div id="map" {data_attrs}></div>',
|
| 125 |
+
head=_read_asset("globe_head.html"),
|
| 126 |
+
css_template=_read_asset("globe.css"),
|
| 127 |
+
html_template="${value}",
|
| 128 |
+
js_on_load=_read_asset("globe.js"),
|
| 129 |
+
min_height="100vh",
|
| 130 |
+
show_label=False,
|
| 131 |
+
container=False,
|
| 132 |
+
padding=False,
|
| 133 |
+
scale=1,
|
| 134 |
+
elem_classes=["globe-panel"],
|
| 135 |
+
)
|