使用 folium 添加一个大型 shapefile 以在 python 中映射
Add a large shapefile to map in python using folium
我正在使用 python、PyQt5 和 Qt 设计器在我的应用程序中显示 folium 地图。由于 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) 直接添加到 folium 地图。因此,我尝试先将其转换为 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()
但现在我的地图根本没有显示。它只是一个空的 space,控制台或错误日志中没有错误。但是,如果我使用“m.save('map.html')”将地图另存为 HTML 文件,它会保存文件,当我打开它时,它会显示 json 文件在地图上,但由于某种原因,我在应用程序中显示地图的方式在添加 shp-->json 文件后不起作用。我做错了什么?
正如这些问题中已经指出的那样( and ) and in the official docs:
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.
The html is loaded immediately; external objects are loaded
asynchronously.
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.
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.
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.
Warning: This function works only for HTML, for other mime types (such
as XHTML and SVG) setContent() should be used instead.
Warning: The content will be percent encoded before being sent to the
renderer via IPC. This may increase its size. The maximum size of the
percent encoded content is 2 megabytes minus 30 bytes.
(强调我的)
setHtml()
不支持大于 2MB 的内容,因此针对您的具体情况,有 2 种解决方案:
将 folium 地图保存在 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 来 return 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()
我正在使用 python、PyQt5 和 Qt 设计器在我的应用程序中显示 folium 地图。由于 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) 直接添加到 folium 地图。因此,我尝试先将其转换为 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()
但现在我的地图根本没有显示。它只是一个空的 space,控制台或错误日志中没有错误。但是,如果我使用“m.save('map.html')”将地图另存为 HTML 文件,它会保存文件,当我打开它时,它会显示 json 文件在地图上,但由于某种原因,我在应用程序中显示地图的方式在添加 shp-->json 文件后不起作用。我做错了什么?
正如这些问题中已经指出的那样(
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.The html is loaded immediately; external objects are loaded asynchronously.
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.
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.
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.
Warning: This function works only for HTML, for other mime types (such as XHTML and SVG) setContent() should be used instead.
Warning: The content will be percent encoded before being sent to the renderer via IPC. This may increase its size. The maximum size of the percent encoded content is 2 megabytes minus 30 bytes.
(强调我的)
setHtml()
不支持大于 2MB 的内容,因此针对您的具体情况,有 2 种解决方案:
将 folium 地图保存在 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 来 return 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()