使用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
main.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来映射的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Folium 将簇标记添加到 Choropleth
使用 Python 地图绘制工具 -- folium 全攻略