Skip to content

Commit eeaf5e8

Browse files
authored
Add option to wrap values via Leaflet functionality (#276)
* Add option to wrap values via Leaflet functionality * Add example demonstrating wrap longitude * Fix formatting
1 parent e7268e2 commit eeaf5e8

File tree

3 files changed

+83
-5
lines changed

3 files changed

+83
-5
lines changed

examples/test_wrap_longitude.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Test script to demonstrate the wrap_longitude parameter.
3+
4+
Pan the map around the world (past the dateline) and observe how
5+
the longitude values change with wrap_longitude enabled vs disabled.
6+
"""
7+
8+
import folium
9+
import streamlit as st
10+
11+
from streamlit_folium import st_folium
12+
13+
st.set_page_config(layout="wide")
14+
st.title("Test: wrap_longitude parameter")
15+
16+
st.markdown("""
17+
**Instructions:** Pan the map to the left or right past the international dateline
18+
(keep going past 180° or -180°). Watch how the center longitude and bounds change.
19+
20+
- **Without wrapping:** Longitude values can exceed -180/180 (e.g., -200, 250)
21+
- **With wrapping:** Longitude values stay within -180 to 180
22+
""")
23+
24+
col1, col2 = st.columns(2)
25+
26+
with col1:
27+
st.subheader("wrap_longitude=False (default)")
28+
m1 = folium.Map(location=[0, 170], zoom_start=2)
29+
result1 = st_folium(
30+
m1, key="map1", wrap_longitude=False, height=400, use_container_width=True
31+
)
32+
33+
if result1:
34+
st.write("**Center:**", result1.get("center"))
35+
bounds1 = result1.get("bounds")
36+
if bounds1:
37+
sw = bounds1.get("_southWest", {})
38+
ne = bounds1.get("_northEast", {})
39+
st.write(f"**SW lng:** {sw.get('lng')}")
40+
st.write(f"**NE lng:** {ne.get('lng')}")
41+
42+
with col2:
43+
st.subheader("wrap_longitude=True")
44+
m2 = folium.Map(location=[0, 170], zoom_start=2)
45+
result2 = st_folium(
46+
m2, key="map2", wrap_longitude=True, height=400, use_container_width=True
47+
)
48+
49+
if result2:
50+
st.write("**Center:**", result2.get("center"))
51+
bounds2 = result2.get("bounds")
52+
if bounds2:
53+
sw = bounds2.get("_southWest", {})
54+
ne = bounds2.get("_northEast", {})
55+
st.write(f"**SW lng:** {sw.get('lng')}")
56+
st.write(f"**NE lng:** {ne.get('lng')}")

streamlit_folium/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def st_folium(
216216
debug: bool = False,
217217
render: bool = True,
218218
on_change: Callable | None = None,
219+
wrap_longitude: bool = False,
219220
):
220221
"""Display a Folium object in Streamlit, returning data as user interacts
221222
with app.
@@ -268,6 +269,10 @@ def st_folium(
268269
Disabling this may improve performance as you can cache the rendering step.
269270
*Note* if this is disabled and the map is not rendered elsewhere the map
270271
will be missing attributes
272+
wrap_longitude: bool
273+
If True, normalize longitude values to be within -180 to 180 degrees.
274+
This is useful when panning around the world causes longitude values to
275+
exceed the standard bounds.
271276
Returns
272277
-------
273278
dict
@@ -443,6 +448,7 @@ def _on_change():
443448
css_links=css_links,
444449
js_links=js_links,
445450
on_change=_on_change,
451+
wrap_longitude=wrap_longitude,
446452
)
447453

448454

streamlit_folium/frontend/src/index.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type GlobalData = {
2929
last_layer_control: any
3030
height: any
3131
selected_layers: Record<string, { name: string; url: string }>
32+
wrap_longitude: boolean
3233
}
3334

3435
declare global {
@@ -56,18 +57,31 @@ function updateComponentValue(map: any) {
5657
let previous_data = global_data.previous_data
5758
let bounds = map.getBounds()
5859
let zoom = map.getZoom()
60+
let center = map.getCenter()
61+
const wrapLng = global_data.wrap_longitude
62+
63+
// Use Leaflet's built-in wrapLatLng and wrapLatLngBounds if wrapping is enabled
64+
const wrapLatLng = (latlng: any) => {
65+
if (!latlng || !wrapLng) return latlng
66+
return map.wrapLatLng(latlng)
67+
}
68+
const wrapBounds = (b: any) => {
69+
if (!b || !wrapLng) return b
70+
return map.wrapLatLngBounds(b)
71+
}
72+
5973
let _data = {
60-
last_clicked: global_data.lat_lng_clicked,
61-
last_object_clicked: global_data.last_object_clicked,
74+
last_clicked: wrapLatLng(global_data.lat_lng_clicked),
75+
last_object_clicked: wrapLatLng(global_data.last_object_clicked),
6276
last_object_clicked_tooltip: global_data.last_object_clicked_tooltip,
6377
last_object_clicked_popup: global_data.last_object_clicked_popup,
6478
all_drawings: global_data.all_drawings,
6579
last_active_drawing: global_data.last_active_drawing,
66-
bounds: bounds,
80+
bounds: wrapBounds(bounds),
6781
zoom: zoom,
6882
last_circle_radius: global_data.last_circle_radius,
6983
last_circle_polygon: global_data.last_circle_polygon,
70-
center: map.getCenter(),
84+
center: wrapLatLng(center),
7185
selected_layers: Object.values(global_data.selected_layers)
7286
}
7387

@@ -282,6 +296,7 @@ async function onRender(event: Event) {
282296
const return_on_hover: boolean = data.args["return_on_hover"]
283297
const layer_control: string = data.args["layer_control"]
284298
const pixelated: boolean = data.args["pixelated"]
299+
const wrap_longitude: boolean = data.args["wrap_longitude"] ?? false
285300

286301
// load scripts
287302
const loadScripts = async () => {
@@ -417,7 +432,8 @@ async function onRender(event: Event) {
417432
last_feature_group: null,
418433
last_layer_control: null,
419434
selected_layers: {},
420-
height: height
435+
height: height,
436+
wrap_longitude: wrap_longitude
421437
}
422438
if (script.indexOf("map_div2") !== -1) {
423439
parent_div?.classList.remove("single")

0 commit comments

Comments
 (0)