You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于PySide2+Folium的离线地图创建工具获取矩形边界及导出PNG问题

解决方案

问题原因说明

你调用self.map.get_bounds()返回空值的核心原因是:folium的Python端map对象仅负责生成前端HTML代码,用户在WebView中绘制矩形的操作是在前端JS环境执行的,操作数据没有同步回Python端的map实例,因此Python端无法直接获取绘制结果。

解决步骤

1. 获取绘制矩形的边界坐标

通过QWebChannel实现Python与WebView前端的双向通信,监听Leaflet.Draw的绘制事件,将边界坐标传回Python:

  • 先给项目新增依赖:PySide2.QtWebChannel
  • 在生成的folium HTML中注入JS代码,监听draw:created事件,拿到矩形边界后通过WebChannel传给Python端
  • Python端定义接收坐标的槽函数,存储边界值

2. 导出选中区域为PNG

推荐两种比纯folium导出更优的方案:

方案A:使用contextily拼接瓦片生成PNG(推荐)

不需要依赖浏览器渲染,直接根据拿到的边界坐标和当前地图缩放等级,下载对应OpenStreetMap瓦片拼接成带边界信息的PNG,速度快、生成的图片精度可控,还能自动缓存瓦片方便后续离线使用。

方案B:WebView直接截图

如果需要保留folium地图上的自定义标记、图层,可以直接调用QWebEngineView的grab()方法截图,或者在前端注入html2canvas导出指定区域,不需要额外调用其他工具。

完整修改后示例代码

from PySide2 import QtCore, QtGui, QtWidgets, QtWebChannel
import io
import sys
import folium
from folium.plugins import Draw
from PySide2.QtWebEngineWidgets import QWebEngineView
import contextily as ctx
import matplotlib.pyplot as plt
from shapely.geometry import box

# 定义通信类,接收前端传来的坐标
class JsBridge(QtCore.QObject):
    @QtCore.Slot(float, float, float, float)
    def get_rect_bounds(self, min_lat, min_lng, max_lat, max_lng):
        self.bounds = [min_lat, min_lng, max_lat, max_lng]
        # 拿到坐标后可以直接调用导出PNG的方法
        self.export_png()

    def export_png(self):
        # 这里用contextily方案导出PNG示例
        min_lat, min_lng, max_lat, max_lng = self.bounds
        # 构造边界框
        bbox = box(min_lng, min_lat, max_lng, max_lat)
        fig, ax = plt.subplots(figsize=(10, 10))
        ax.set_xlim(min_lng, max_lng)
        ax.set_ylim(min_lat, max_lat)
        # 加载OpenStreetMap瓦片
        ctx.add_basemap(ax, crs="EPSG:4326", source=ctx.providers.OpenStreetMap.Mapnik)
        plt.axis('off')
        plt.savefig('selected_map.png', bbox_inches='tight', pad_inches=0, dpi=300)
        plt.close()
        print("地图PNG已导出")

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        self.webView = QWebEngineView()
        MainWindow.setCentralWidget(self.webView)

        # 初始化WebChannel和通信桥
        self.channel = QtWebChannel.QWebChannel()
        self.js_bridge = JsBridge()
        self.channel.registerObject("bridge", self.js_bridge)
        self.webView.page().setWebChannel(self.channel)

        # 初始化地图
        coordinate = (55, 7)
        self.map = folium.Map(
            tiles='OpenStreetMap',
            zoom_start=15,
            location=coordinate,
            png_enabled=False
        )

        self.draw = Draw(
            draw_options={
                'polyline': False,
                'rectangle': True,
                'polygon': False,
                'circle': False,
                'marker': False,
                'circlemarker': False},
            edit_options={'remove': True, 'edit': True, 'save': True, 'cancel': False})

        self.draw.add_to(self.map)

        # 注入JS代码:加载WebChannel,监听绘制事件,传回坐标
        js_inject = """
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script>
        var bridge = null;
        new QWebChannel(qt.webChannelTransport, function(channel) {
            bridge = channel.objects.bridge;
        });
        // 监听绘制完成事件
        map.on('draw:created', function(e) {
            var layer = e.layer;
            var bounds = layer.getBounds();
            var min_lat = bounds.getSouth();
            var min_lng = bounds.getWest();
            var max_lat = bounds.getNorth();
            var max_lng = bounds.getEast();
            // 传给Python端
            if(bridge) bridge.get_rect_bounds(min_lat, min_lng, max_lat, max_lng);
        });
        </script>
        """
        self.map.get_root().html.add_child(folium.Element(js_inject))

        data = io.BytesIO()
        self.map.save(data, close_file=False)
        self.webView.setHtml(data.getvalue().decode('utf-8'))

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

def main():
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

依赖安装说明

需要安装额外依赖即可直接运行:

pip install contextily matplotlib shapely

内容的提问来源于stack exchange,提问作者Nikololo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 10:48:04