Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to implement smoothly updating map in Qt Widgets app?
QtWS25 Last Chance

How to implement smoothly updating map in Qt Widgets app?

Scheduled Pinned Locked Moved Solved General and Desktop
4 Posts 2 Posters 308 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • I Offline
    I Offline
    irakli
    wrote on last edited by
    #1

    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 1 Reply Last reply
    0
    • I irakli

      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 Offline
      I Offline
      IgKh
      wrote on last edited by
      #2

      @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.

      I 2 Replies Last reply
      1
      • I IgKh

        @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.

        I Offline
        I Offline
        irakli
        wrote on last edited by
        #3

        @IgKh Thanks for the answer. I will try this a bit later.

        1 Reply Last reply
        0
        • I IgKh

          @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.

          I Offline
          I Offline
          irakli
          wrote on last edited by
          #4

          @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.

          1 Reply Last reply
          2
          • I irakli has marked this topic as solved on

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved