我正在开发一个使用 Shiny for Python 和 Folium 来可视化天气预报的项目。目标是允许用户与滑块交互来选择预报时间,并在地图上显示相应的天气数据。
我遇到的问题: 每次用户使用滑块更改预测时间时,地图都会重置为初始缩放级别和中心位置。理想情况下,我希望地图保留当前的缩放和中心,即使在切换显示图层(即预测时间)时也是如此。
我尝试在应用程序中实现自定义 JavaScript 以捕获缩放和居中更改并在滑块更新后重新应用它们,但它似乎没有按预期工作。缩放和中心始终重置为首次创建地图时定义的初始值。检查 Web 应用程序的源代码后,我的 JavaScript 似乎无法与应用程序的其余部分正确集成。
我的设置: Shiny for Python 用于 Web 应用程序结构。 Folium 用于渲染地图并将天气数据添加为图层。 用于选择不同预测时间步长的滑块。 更改滑块时尝试使用 JavaScript 捕获并恢复地图的视图设置(缩放级别和中心)。
问题: 使用我当前的堆栈更改预测时间时,是否可以在更新中保留缩放级别和地图中心(可能类似于在闪亮中,如何修复(锁定)传单地图视图缩放和中心?,仅在Python中而不是在Python中)在 R 中)?如果是这样,我怎样才能正确地包含 JavaScript 来实现这一目标?
这是仍然存在问题的最短版本:
from shiny import App, render, ui
import folium
# Define the Shiny UI
app_ui = ui.page_fluid(
ui.h2("Dynamic Folium Map with Layer Switching"),
ui.input_slider("layer_slider", "Choose Layer (1 = Marker, 2 = Circle)", min=1, max=2, value=1),
ui.output_text_verbatim("slider_value"),
ui.output_ui("map") # Output container for the map
)
# Define the Shiny server
def server(input, output, session):
# Display the slider value
@output
@render.text
def slider_value():
return f"Layer selected: {input.layer_slider()}"
# Generate and display the map dynamically
@output
@render.ui
def map():
# Create a base map centered on Germany
m = folium.Map(location=[51.1657, 10.4515], zoom_start=6)
# Add layers depending on the slider value
if input.layer_slider() == 1:
folium.Marker([51.1657, 10.4515], popup="Marker in Germany").add_to(m)
elif input.layer_slider() == 2:
folium.Circle([51.1657, 10.4515], radius=50000, color='blue', popup="Circle Layer").add_to(m)
# Return the map as an HTML string
map_html = m._repr_html_()
# Add custom JavaScript to preserve zoom level and map center
custom_js = """
<script>
let mapZoom = 6;
let mapCenter = [51.1657, 10.4515];
// Function to capture map's zoom and center
function captureMapState() {
mapZoom = map.getZoom();
mapCenter = map.getCenter();
}
// Function to restore map's zoom and center
function restoreMapState() {
map.setView(mapCenter, mapZoom);
}
// Capture state after zoom or move events
map.on('zoomend', captureMapState);
map.on('moveend', captureMapState);
// Restore the view after the map is updated
document.addEventListener('DOMContentLoaded', function() {
restoreMapState();
});
</script>
"""
# Embed the map and attach the custom JavaScript
return ui.HTML(f"""
<div style="width:100%; height:500px;">
{map_html}
</div>
{custom_js}
""")
# Create the Shiny app
app = App(app_ui, server)
# Run the app
app.run(port=8000)
我建议使用
ipyleaflet
而不是 folium
,因为它是 ipywidget
,因此支持创建后更新地图对象的方法。
from ipyleaflet import Map, Marker, Circle
from shiny.express import ui, input
from shinywidgets import render_widget
from shiny import reactive, render
def remove_all_layers(m):
for i in range(1, len(m.layers)):
m.remove_layer(m.layers[1])
ui.h2("An ipyleaflet Map")
ui.input_slider('layer_slider', 'Layer Slider', 0, 3, 0, step=1)
@render_widget # <<
def map():
center = (51.1657, 10.4515)
m = Map(center=center, zoom=6)
# marker = Marker(location=center)
# m.add(marker)
return m
@reactive.effect
def add_layer():
m = map.widget
remove_all_layers(m)
if input.layer_slider() == 1:
marker = Marker(
location=[51.1657, 10.4515],
title="Marker in Germany"
)
m.add(marker)
elif input.layer_slider() == 2:
circle = Circle(
location=[51.1657, 10.4515],
radius=50000,
color='blue',
title="Circle Layer"
)
m.add(circle)