import ipyleaflet
import solara
import numpy as np
import math
# --- load coordinates ---
coords = np.load("coords.npy") # shape (113, 2) -> [lat, lon]
# --- build lavender markers (fixed, non-reactive) ---
#marker_style = dict(color="#b57edc", fill_color="#b57edc", fill_opacity=0.9, size=1)
markers = [
ipyleaflet.Marker.element(location=(float(lat), float(lon)),fill_color="#b57edc")#**marker_style)
for lat, lon in coords
]
# --- square bounds around centroid (+5% padding on the max span) ---
lats = coords[:, 0]; lons = coords[:, 1]
lat_c = float(lats.mean()); lon_c = float(lons.mean())
lat_span = float(lats.max() - lats.min())
lon_span = float(lons.max() - lons.min())
max_span = max(lat_span, lon_span, 1e-6) # avoid zero-size
pad = 0.05 * max_span
half = max_span / 2 + pad
# square bounds in degrees around centroid
bounds = ((lat_c - half, lon_c - half), (lat_c + half, lon_c + half))
center = (lat_c, lon_c)
# simple zoom estimate to cover the square bounds on load
def estimate_zoom(b):
(s, w), (n, e) = b
lat_span_deg = n - s
lon_span_deg = e - w
mean_lat = (n + s) / 2.0
lat_km = lat_span_deg * 111.32
lon_km = lon_span_deg * 111.32 * math.cos(math.radians(mean_lat))
span_km = max(lat_km, lon_km)
if span_km <= 2: return 14
if span_km <= 5: return 13
if span_km <= 10: return 12
if span_km <= 20: return 11
if span_km <= 40: return 10
if span_km <= 80: return 9
if span_km <= 160: return 8
if span_km <= 320: return 7
if span_km <= 640: return 6
return 5
zoom = estimate_zoom(bounds)
# --- basemap approximating "orange city plan with white roads, blue rivers" ---
# Using CARTO Voyager (no labels) -> white roads, blue rivers; land has a warm tint on many devices.
custom_tiles_url = "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png"
maps = {
"Orange city plan": custom_tiles_url,
"OpenStreetMap.Mapnik": ipyleaflet.basemaps.OpenStreetMap.Mapnik.build_url(),
"OpenTopoMap": ipyleaflet.basemaps.OpenTopoMap.build_url(),
"Esri.WorldTopoMap": ipyleaflet.basemaps.Esri.WorldTopoMap.build_url(),
}
map_name = solara.reactive("Orange city plan")
@solara.component
def Page():
with solara.Column(style={"width": "100%", "height": "500px"}):
solara.Select(label="Map", value=map_name, values=list(maps))
# Isolation prevents overlap on narrow screens
with solara.Column(style={"isolation": "isolate"}):
ipyleaflet.Map.element( # type: ignore
center=center, # fixed
zoom=zoom, # fixed
max_bounds=bounds, # keep panning within the square bounds
max_bounds_viscosity=1.0,
scroll_wheel_zoom=True,
layers=[
ipyleaflet.TileLayer.element(url=maps[map_name.value]),
*markers, # 113 lavender markers
],
)