I want to develop a desktop application that involves receiving a geographic coordinate every second from a serial port and adding to a map in real-time. The map should look like the below link:
I have written a piece of code to test Folium's ability to perform this task.
import sys
import io
import folium
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QMainWindow, QPushButton
from PyQt5.QtWebEngineWidgets import QWebEngineView
class Window(QMainWindow):
def __init__(self):
super().__init__()
coordinate = (41.8828, 12.4761)
self.m = folium.Map(
zoom_start = 18,
location = coordinate,
control_scale=True,
tiles = None
)
folium.raster_layers.TileLayer(
tiles='http://mt1.google.com/vt/lyrs=m&h1=p1Z&x={x}&y={y}&z={z}',
name='Standard Roadmap',
attr = 'Google Map',
).add_to(self.m)
folium.raster_layers.TileLayer(
tiles='http://mt1.google.com/vt/lyrs=s&h1=p1Z&x={x}&y={y}&z={z}',
name='Satellite Only',
attr = 'Google Map',
).add_to(self.m)
folium.raster_layers.TileLayer(
tiles='http://mt1.google.com/vt/lyrs=y&h1=p1Z&x={x}&y={y}&z={z}',
name='Hybrid',
attr = 'Google Map',
).add_to(self.m)
folium.LayerControl().add_to(self.m)
folium.Marker(coordinate).add_to(self.m)
self.data = io.BytesIO()
self.m.save(self.data, close_file=False)
widget=QWidget()
vbox = QVBoxLayout()
buttun1 = QPushButton("Insert Marker")
buttun1.clicked.connect(self.insert_marker)
self.webView = QWebEngineView()
self.webView.setHtml(self.data.getvalue().decode())
self.webView.setContextMenuPolicy(Qt.NoContextMenu)
vbox.addWidget(buttun1)
vbox.addWidget(self.webView)
widget.setLayout(vbox)
self.setCentralWidget(widget)
self.setWindowTitle("App")
self.setMinimumSize(1000, 600)
self.showMaximized()
def insert_marker(self):
folium.CircleMarker([41.8829, 12.4766],
radius=2,
weight=5,
).add_to(self.m)
self.m.save(self.data, close_file=False)
self.webView.setHtml(self.data.getvalue().decode())
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
In this code, the folium map is embedded in a PyQt5 QWebEngineView, and a circle marker is expected to be added to the map after clicking the button above. However, after click, the map will only be rerendered.
Despite some discussions about the impossibility of seamless updating of a generated folium map, it seems that using ClickForMarker() you can add markers to a rendered map.
Is there a way to get the same functionality without clicking?
You can add the markers by executing javascript through the runJavaScript()
method:
import io
import sys
from jinja2 import Template
import folium
from PyQt5.QtCore import pyqtSignal, QObject, QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView
class CoordinateProvider(QObject):
coordinate_changed = pyqtSignal(float, float)
def __init__(self, parent=None):
super().__init__(parent)
self._timer = QTimer(interval=1000)
self._timer.timeout.connect(self.generate_coordinate)
def start(self):
self._timer.start()
def stop(self):
self._timer.stop()
def generate_coordinate(self):
import random
center_lat, center_lng = 41.8828, 12.4761
x, y = (random.uniform(-0.001, 0.001) for _ in range(2))
latitude = center_lat + x
longitude = center_lng + y
self.coordinate_changed.emit(latitude, longitude)
class Window(QMainWindow):
def __init__(self):
super().__init__()
coordinate = (41.8828, 12.4761)
self.map = folium.Map(
zoom_start=18, location=coordinate, control_scale=True, tiles=None
)
folium.raster_layers.TileLayer(
tiles="http://mt1.google.com/vt/lyrs=m&h1=p1Z&x={x}&y={y}&z={z}",
name="Standard Roadmap",
attr="Google Map",
).add_to(self.map)
folium.raster_layers.TileLayer(
tiles="http://mt1.google.com/vt/lyrs=s&h1=p1Z&x={x}&y={y}&z={z}",
name="Satellite Only",
attr="Google Map",
).add_to(self.map)
folium.raster_layers.TileLayer(
tiles="http://mt1.google.com/vt/lyrs=y&h1=p1Z&x={x}&y={y}&z={z}",
name="Hybrid",
attr="Google Map",
).add_to(self.map)
folium.LayerControl().add_to(self.map)
folium.Marker(coordinate).add_to(self.map)
data = io.BytesIO()
self.map.save(data, close_file=False)
self.map_view = QWebEngineView()
self.map_view.setHtml(data.getvalue().decode())
self.setCentralWidget(self.map_view)
def add_marker(self, latitude, longitude):
js = Template(
"""
L.marker([{{latitude}}, {{longitude}}] )
.addTo({{map}});
L.circleMarker(
[{{latitude}}, {{longitude}}], {
"bubblingMouseEvents": true,
"color": "#3388ff",
"dashArray": null,
"dashOffset": null,
"fill": false,
"fillColor": "#3388ff",
"fillOpacity": 0.2,
"fillRule": "evenodd",
"lineCap": "round",
"lineJoin": "round",
"opacity": 1.0,
"radius": 2,
"stroke": true,
"weight": 5
}
).addTo({{map}});
"""
).render(map=self.map.get_name(), latitude=latitude, longitude=longitude)
self.map_view.page().runJavaScript(js)
def main():
app = QApplication(sys.argv)
window = Window()
window.showMaximized()
provider = CoordinateProvider()
provider.coordinate_changed.connect(window.add_marker)
provider.start()
sys.exit(app.exec())
if __name__ == "__main__":
main()