How to implement smoothly updating map in Qt Widgets app?
-
I have a Folium map placed in PySide6 QWebEngineView. Map coordinates are updated each second and the map is recentered to the new position. However, this is re-rendering entire map with each update, and it causes "flashing", which is not user friendly.
This is the full minimal example of what I am describing:
import io import sys import folium from PySide6.QtCore import QTimer from PySide6.QtWebEngineWidgets import QWebEngineView from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget class MapWebView(QWebEngineView): def __init__(self, initial_coordinates: tuple[float, float]): super().__init__() self.folium_map = folium.Map( location=initial_coordinates, zoom_start=13, zoom_control=False, attribution_control=False ) self.data = io.BytesIO() self.folium_map.save(self.data, close_file=False) self.setHtml(self.data.getvalue().decode()) def update_map(self, new_coords: tuple[float, float]): self.folium_map = folium.Map( location=new_coords, zoom_start=13, zoom_control=False, attribution_control=False ) self.data = io.BytesIO() self.folium_map.save(self.data, close_file=False) self.setHtml(self.data.getvalue().decode()) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(600, 600) self.setCentralWidget(QWidget()) self.centralWidget().setLayout(QVBoxLayout()) self.map_webview = MapWebView((47.030780, 8.656176)) self.centralWidget().layout().addWidget(self.map_webview) timer = QTimer(self) timer.timeout.connect(self.update_map) timer.start(1000) def update_map(self): current_coords = self.map_webview.folium_map.location new_coords = (current_coords[0] + 0.002, current_coords[1] + 0.002) self.map_webview.update_map(new_coords) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
I need to make the map to reposition by smooth dragging/sliding to new location. Even jump will be fine if flashing does not happen.
Maybe someone could also suggest other, better dynamic map solution in such case.Thanks!
-
I have a Folium map placed in PySide6 QWebEngineView. Map coordinates are updated each second and the map is recentered to the new position. However, this is re-rendering entire map with each update, and it causes "flashing", which is not user friendly.
This is the full minimal example of what I am describing:
import io import sys import folium from PySide6.QtCore import QTimer from PySide6.QtWebEngineWidgets import QWebEngineView from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget class MapWebView(QWebEngineView): def __init__(self, initial_coordinates: tuple[float, float]): super().__init__() self.folium_map = folium.Map( location=initial_coordinates, zoom_start=13, zoom_control=False, attribution_control=False ) self.data = io.BytesIO() self.folium_map.save(self.data, close_file=False) self.setHtml(self.data.getvalue().decode()) def update_map(self, new_coords: tuple[float, float]): self.folium_map = folium.Map( location=new_coords, zoom_start=13, zoom_control=False, attribution_control=False ) self.data = io.BytesIO() self.folium_map.save(self.data, close_file=False) self.setHtml(self.data.getvalue().decode()) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(600, 600) self.setCentralWidget(QWidget()) self.centralWidget().setLayout(QVBoxLayout()) self.map_webview = MapWebView((47.030780, 8.656176)) self.centralWidget().layout().addWidget(self.map_webview) timer = QTimer(self) timer.timeout.connect(self.update_map) timer.start(1000) def update_map(self): current_coords = self.map_webview.folium_map.location new_coords = (current_coords[0] + 0.002, current_coords[1] + 0.002) self.map_webview.update_map(new_coords) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
I need to make the map to reposition by smooth dragging/sliding to new location. Even jump will be fine if flashing does not happen.
Maybe someone could also suggest other, better dynamic map solution in such case.Thanks!
@irakli By calling
setHtml
each second you are telling the Chromium browser engine to parse and render a new DOM from scratch each time. That takes relatively long time and since the HTML emitted by Folium is complex and involves Javacript, the page likely has several intermediate states that are drawn by the browser engine - hence the the flickering.This is the same as constantly pressing F5 on a browser window. Since you are using web technologies, the answer to avoid reloads is same as on the web: have your web view show a simple long-running bundled SPA. It will manage the map view - Folium is a wrapper for https://leafletjs.com/ so you can use that directly. When you want to scroll to a new position, instruct the map to do so via its' Javascript API. You can invoke Javascript from the host Qt app in the context of the web engine via the
QWebEnginePage.runJavaScript
method. -
@irakli By calling
setHtml
each second you are telling the Chromium browser engine to parse and render a new DOM from scratch each time. That takes relatively long time and since the HTML emitted by Folium is complex and involves Javacript, the page likely has several intermediate states that are drawn by the browser engine - hence the the flickering.This is the same as constantly pressing F5 on a browser window. Since you are using web technologies, the answer to avoid reloads is same as on the web: have your web view show a simple long-running bundled SPA. It will manage the map view - Folium is a wrapper for https://leafletjs.com/ so you can use that directly. When you want to scroll to a new position, instruct the map to do so via its' Javascript API. You can invoke Javascript from the host Qt app in the context of the web engine via the
QWebEnginePage.runJavaScript
method. -
@irakli By calling
setHtml
each second you are telling the Chromium browser engine to parse and render a new DOM from scratch each time. That takes relatively long time and since the HTML emitted by Folium is complex and involves Javacript, the page likely has several intermediate states that are drawn by the browser engine - hence the the flickering.This is the same as constantly pressing F5 on a browser window. Since you are using web technologies, the answer to avoid reloads is same as on the web: have your web view show a simple long-running bundled SPA. It will manage the map view - Folium is a wrapper for https://leafletjs.com/ so you can use that directly. When you want to scroll to a new position, instruct the map to do so via its' Javascript API. You can invoke Javascript from the host Qt app in the context of the web engine via the
QWebEnginePage.runJavaScript
method.@IgKh I used this method and it worked as needed:
def update_map(self, new_coords: tuple[float, float]): self.folium_map.location = new_coords # keep it for next calculation because JavaScript doesn't update it map_name = self.folium_map.get_name() js_code = f'{map_name}.setView({list(new_coords)})' # Use `list()` because JS needs `[ ]` instead of `( )` self.page().runJavaScript(js_code)
Thanks for the answer.
-