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

Posted

技术标签:

【中文标题】使用folium在python中添加一个大的shapefile来映射【英文标题】:Add a large shapefile to map in python using folium 【发布时间】:2021-10-10 11:27:00 【问题描述】:

我正在使用 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 文件后,我在应用程序中显示地图的方式不起作用。我做错了什么?

【问题讨论】:

【参考方案1】:

正如在这些问题(1 和 2)和the official docs 中已经指出的那样:

void QWebEnginePage::setHtml(const QString &html, const QUrl &baseUrl = QUrl()) 将此页面的内容设置为 html。 baseUrl 是可选的,用于解析文档中的相对 URL,例如引用的 图片或样式表。

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

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

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

这是一个等价于 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

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

【讨论】:

哇。感谢您的回答。我使用了你的第一个解决方案,因为它更容易让我理解。你太棒了!

以上是关于使用folium在python中添加一个大的shapefile来映射的主要内容,如果未能解决你的问题,请参考以下文章

python folium 库学习

如何使用 Folium 将簇标记添加到 Choropleth

使用 Python 地图绘制工具 -- folium 全攻略

从使用 GIS 库(如 geopandas、folium)的 Python 脚本制作 .exe

找不到 Python 3.6 模块:Folium

Python - Folium 搜索插件不会在 MarkerCluster 组层中搜索弹出文本