使用folium在python中添加一个大的shapefile来映射

2022-01-12 00:00:00 python pyqt pyqt5 shapefile folium

问题描述

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

I am displaying a folium map in my application using python, PyQt5 and Qt designer. Since there is no map widget in Qt designer, I add a general widget and then promote it to my custom map widget. It all works fine. Here is the python code for my promoted widget:

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()

这很好,我可以在我的应用程序中看到地图.

This works fine and I can see the map in my application.

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

I am also trying to display a GIS shapefile on top of this map. I have done some research and it seems like I cannot add GIS shapefile (.shp) directly to a folium map. So, I try to convert it to json first and then add the json on top of the map. I modified my code as below to add the .shp file to map:

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 文件.我做错了什么?

but now my map doesn't show up at all. It's just an empty space with no errors in the console or error log. If I save the map as an HTML file using "m.save('map.html')" though, it does save the file and when I open it, it displays the json file on the map, but for some reason, the way I am doing it to show the map in my application is not working after adding the shp-->json file. What am I doing wrong?


解决方案

正如这些问题中已经指出的(1和 2) 和 官方文档:

As already pointed out in these questions(1 and 2) and in the official docs:

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

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

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

Sets the content of this page to html. baseUrl is optional and used to resolve relative URLs in the document, such as referenced images or stylesheets.

html 会立即加载;加载外部对象异步.

The html is loaded immediately; external objects are loaded asynchronously.

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

If a script in the html runs longer than the default script timeout (currently 10 seconds), for example due to being blocked by a modal JavaScript alert dialog, this method will return as soon as possible after the timeout and any subsequent html will be loaded asynchronously.

使用此方法时,网络引擎假定外部资源,例如 JavaScript 程序或样式表,被编码在除非另有说明,否则为 UTF-8.例如,一个编码外部脚本可以通过 charset 属性指定HTML 脚本标记.也可以指定编码通过网络服务器.

When using this method, the web engine assumes that external resources, such as JavaScript programs or style sheets, are encoded in UTF-8 unless otherwise specified. For example, the encoding of an external script can be specified through the charset attribute of the HTML script tag. It is also possible for the encoding to be specified by the web server.

这是一个等价于 setContent(html,"text/html", baseUrl).

This is a convenience function equivalent to setContent(html, "text/html", baseUrl).

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

Note: This method will not affect session or global history for the page.

警告:此功能仅适用于 HTML,适用于其他 mime 类型(例如作为 XHTML 和 SVG)应该使用 setContent().

Warning: This function works only for HTML, for other mime types (such as XHTML and SVG) setContent() should be used instead.

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

(强调我的)

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

setHtml() does not support content greater than 2MB, so in your particular case there are 2 solutions:

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

  • Save the folium map in an html file:

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:

  • Use a QWebEngineUrlSchemeHandler to return the 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
    

    ma​​in.py

    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()
    

  • 相关文章