python - 使用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 文件后,我在应用程序中显示地图的方式不起作用。我究竟做错了什么?
解决方案
正如在这些问题( 1和2)和官方文档中已经指出的那样:
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()
推荐阅读
- python - 如何让 Python 脚本将 CSV 文件发送到特定文件夹位置
- c# - 如何在 Unity3d 中创建超声波视觉?(蝙蝠视觉)
- r - 创建一个用 runif 和 rnorm 重复的不同序列的向量
- ios - 使用 create-react-app 时,我的 package.json 中“主页”的正确配置是什么?
- twilio - 即使接收方尚未接受呼叫,Twilio-Client 也始终将连接状态返回为出站呼叫打开
- html - 通过 XSL 简单展开 HTML 文件
- azure-devops - 将图像添加到 Azure Devops 仪表板
- android - FragmentContainerView和fragment的区别
- html - 在引导程序中的缩略图下方添加标题时遇到问题
- c# - 如何使请求正文可选