首页 > 解决方案 > 使用folium在python中添加一个大的shapefile来映射

问题描述

我正在使用 python、PyQt5 和 Qt 设计器在我的应用程序中显示一个叶图。由于 Qt 设计器中没有地图小部件,我添加了一个通用小部件,然后将其提升到我的自定义地图小部件。一切正常。这是我推广的小部件的 python 代码:

import io

import folium

from PyQt5 import QtWebEngineWidgets
from PyQt5.QtWidgets import *

class LeafWidget (QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        m = folium.Map(
            location=[40, -120] , zoom_start=10
        )
        self.view = QtWebEngineWidgets.QWebEngineView()
       
        data = io.BytesIO()
       
        m.save(data, close_file=False)
        self.view.setHtml(data.getvalue().decode())
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.view)
        self.show()

这工作正常,我可以在我的应用程序中看到地图。

我也试图在这张地图上显示一个 GIS shapefile。我做了一些研究,似乎我无法将 GIS shapefile (.shp) 直接添加到叶地图中。因此,我尝试先将其转换为 json,然后将 json 添加到地图顶部。我修改了我的代码,将 .shp 文件添加到映射:

import io

import folium
import os.path

from PyQt5 import QtWebEngineWidgets
from PyQt5.QtWidgets  import *
import geopandas as gpd

class LeafWidget (QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        m = folium.Map(
            location=[40, -120] , zoom_start=10
        )
        self.view = QtWebEngineWidgets.QWebEngineView()
        # converting shp to geojson
        shp_file = gpd.read_file('input/2015_loaded_NoCC.shp')
        shp_file.to_file('myshpfile.json', driver='GeoJSON')
        shp = os.path.join('', 'myshpfile.json')
        data = io.BytesIO()
        folium.GeoJson(shp).add_to(m)
        m.save(data, close_file=False)
        self.view.setHtml(data.getvalue().decode())
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.view)
        self.show()

但现在我的地图根本不显示。它只是一个空白空间,控制台或错误日志中没有错误。如果我使用“m.save('map.html')”将地图保存为 HTML 文件,它会保存文件,当我打开它时,它会在地图上显示 json 文件,但由于某种原因,添加 shp-->json 文件后,我在应用程序中显示地图的方式不起作用。我究竟做错了什么?

标签: pythonpyqtpyqt5shapefilefolium

解决方案


正如在这些问题( 12)和官方文档中已经指出的那样:

void QWebEnginePage::setHtml(const QString &html, const QUrl &baseUrl = QUrl())

将此页面的内容设置为 html。baseUrl 是可选的,用于解析文档中的相对 URL,例如引用的图像或样式表。

html 会立即加载;外部对象是异步加载的。

如果 html 中的脚本运行时间超过默认脚本超时时间(当前为 10 秒),例如由于被模态 JavaScript 警报对话框阻止,此方法将在超时后尽快返回,并且将加载任何后续 html异步。

使用此方法时,Web 引擎假定外部资源(例如 JavaScript 程序或样式表)以 UTF-8 编码,除非另有说明。例如,外部脚本的编码可以通过 HTML script 标签的 charset 属性来指定。也可以由 Web 服务器指定编码。

这是一个等效于 setContent(html, "text/html", baseUrl) 的便捷函数。

注意:此方法不会影响页面的会话或全局历史记录。

警告:此函数仅适用于 HTML,对于其他 mime 类型(例如 XHTML 和 SVG),应使用 setContent() 代替。

警告:内容将在通过 IPC 发送到渲染器之前进行百分比编码。这可能会增加其大小。百分比编码内容的最大大小为 2 兆字节减去 30 字节。

(强调我的)

setHtml()不支持大于 2MB 的内容,因此在您的特定情况下有 2 个解决方案:

  • 将叶图保存在 html 文件中:

    import io
    import os
    
    from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
    
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    class LeafWidget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
    
            self.view = QtWebEngineWidgets.QWebEngineView()
    
            shp_filename = os.path.join(CURRENT_DIR, "input", "2015_loaded_NoCC.shp")
            shp_file = gpd.read_file(shp_filename)
            shp_file_json_str = shp_file.to_json()
    
            m = folium.Map(location=[40, -120], zoom_start=10)
            folium.GeoJson(shp_file_json_str).add_to(m)
    
            tmp_file = QtCore.QTemporaryFile("XXXXXX.html", self)
            if tmp_file.open():
                m.save(tmp_file.fileName())
                url = QtCore.QUrl.fromLocalFile(tmp_file.fileName())
                self.view.load(url)
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.view)
    
    
    def main():
        app = QtWidgets.QApplication([])
        w = LeafWidget()
        w.show()
        app.exec_()
    
    
    if __name__ == "__main__":
        main()
    
  • 使用 QWebEngineUrlSchemeHandler 返回 html:

    qfolium.py

    import json
    import io
    
    from PyQt5 import QtCore, QtWebEngineCore, QtWebEngineWidgets
    
    
    class FoliumSchemeHandler(QtWebEngineCore.QWebEngineUrlSchemeHandler):
        def __init__(self, app):
            super().__init__(app)
            self.m_app = app
    
        def requestStarted(self, request):
            url = request.requestUrl()
            name = url.host()
            m = self.m_app.process(name, url.query())
            if m is None:
                request.fail(QtWebEngineCore.QWebEngineUrlRequestJob.UrlNotFound)
                return
            data = io.BytesIO()
            m.save(data, close_file=False)
            raw_html = data.getvalue()
            buf = QtCore.QBuffer(parent=self)
            request.destroyed.connect(buf.deleteLater)
            buf.open(QtCore.QIODevice.WriteOnly)
            buf.write(raw_html)
            buf.seek(0)
            buf.close()
            request.reply(b"text/html", buf)
    
    
    class FoliumApplication(QtCore.QObject):
        scheme = b"folium"
    
        def __init__(self, parent=None):
            super().__init__(parent)
            scheme = QtWebEngineCore.QWebEngineUrlScheme(self.scheme)
            QtWebEngineCore.QWebEngineUrlScheme.registerScheme(scheme)
            self.m_functions = dict()
    
        def init_handler(self, profile=None):
            if profile is None:
                profile = QtWebEngineWidgets.QWebEngineProfile.defaultProfile()
            handler = profile.urlSchemeHandler(self.scheme)
            if handler is not None:
                profile.removeUrlSchemeHandler(handler)
    
            self.m_handler = FoliumSchemeHandler(self)
            profile.installUrlSchemeHandler(self.scheme, self.m_handler)
    
        def register(self, name):
            def decorator(f):
                self.m_functions[name] = f
                return f
    
            return decorator
    
        def process(self, name, query):
            f = self.m_functions.get(name)
            if f is None:
                print("not found")
                return
    
            items = QtCore.QUrlQuery(query).queryItems()
            params_json = dict(items).get("json", None)
            if params_json is not None:
                return f(**json.loads(params_json))
            return f()
    
        def create_url(self, name, params=None):
            url = QtCore.QUrl()
            url.setScheme(self.scheme.decode())
            url.setHost(name)
            if params is not None:
                params_json = json.dumps(params)
                query = QtCore.QUrlQuery()
                query.addQueryItem("json", params_json)
                url.setQuery(query)
            return url
    

    主文件

    import io
    import os
    
    import folium
    
    from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
    import geopandas as gpd
    
    from qfolium import FoliumApplication
    
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    folium_app = FoliumApplication()
    
    
    @folium_app.register("load_shapefile")
    def load_shapefile(latitude, longitude, zoom_start, shp_filename):
        shp_file = gpd.read_file(shp_filename)
        shp_file_json_str = shp_file.to_json()
    
        m = folium.Map(
            location=[latitude, longitude], zoom_start=zoom_start
        )
        folium.GeoJson(shp_file_json_str).add_to(m)
        print(m)
        return m
    
    
    class LeafWidget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            QtWidgets.QWidget.__init__(self, parent)
    
            self.view = QtWebEngineWidgets.QWebEngineView()
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.view)
    
            self.resize(640, 480)
    
            shp_filename = os.path.join(CURRENT_DIR, "input", "2015_loaded_NoCC.shp")
    
            params = {
                "shp_filename": shp_filename,
                "latitude": 40,
                "longitude": -120,
                "zoom_start": 5,
            }
            url = folium_app.create_url("load_shapefile", params=params)
            self.view.load(url)
    
    
    def main():
        app = QtWidgets.QApplication([])
        folium_app.init_handler()
        w = LeafWidget()
        w.show()
        app.exec_()
    
    
    if __name__ == "__main__":
        main()
    

推荐阅读