[Python] Folium map displayed with PyQT5 flashes at every update

Discussão em 'Python' iniciado por Stack, Julho 9, 2025.

  1. Stack

    Stack Membro Participativo

    In my map visualizer with PyQT5 using Folium I have a data_provider for testing purposes. When it updates ship coordinates and heading, the map needs to update which causes flashing.

    import io
    import sys
    import folium

    from PyQt5.QtGui import QRegion
    from PyQt5.QtCore import QRect, QTimer
    from PyQt5 import QtWidgets, QtWebEngineWidgets


    class Map(QtWidgets.QWidget):
    """
    Updates the map's location and heading based on parameters.
    Places a circular map inside a frame with a circular alarm mask.
    """
    triggered = False

    def __init__(self) -> None:
    super().__init__()
    self.map = None
    self.init_ui()

    def init_ui(self) -> None:
    # Create layout
    self.layout = QtWidgets.QVBoxLayout(self)
    self.setLayout(self.layout)

    # Create a parent frame for the circular map
    self.frame = QtWidgets.QWidget(self)
    self.frame.setFixedSize(310, 310)
    self.frame.setStyleSheet(
    "border: 5px solid black; border-radius: 155px;")
    self.frame.setGeometry(145, 10, 310, 310)
    self.layout.addWidget(self.frame)

    # Add the WebEngineView for the map inside the frame
    self.map_view = QtWebEngineWidgets.QWebEngineView(self.frame)
    self.map_view.setFixedSize(300, 300)
    self.map_view.setGeometry(5, 5, 300, 300)

    # Apply a circular mask to the map view
    circular_region = QRegion(QRect(0, 0, self.map_view.width(),
    self.map_view.height()), QRegion.Ellipse)
    self.map_view.setMask(circular_region)

    # Create buttons and input fields
    self.control_layout = QtWidgets.QHBoxLayout()
    self.layout.addLayout(self.control_layout)

    self.lat_input = QtWidgets.QLineEdit(self)
    self.lat_input.setPlaceholderText("Latitude")
    self.control_layout.addWidget(self.lat_input)

    self.lon_input = QtWidgets.QLineEdit(self)
    self.lon_input.setPlaceholderText("Longitude")
    self.control_layout.addWidget(self.lon_input)

    self.heading_input = QtWidgets.QLineEdit(self)
    self.heading_input.setPlaceholderText("Heading (degrees)")
    self.control_layout.addWidget(self.heading_input)

    self.update_button = QtWidgets.QPushButton("Update Location", self)
    self.update_button.clicked.connect(self.update_location)
    self.control_layout.addWidget(self.update_button)

    self.update_button = QtWidgets.QPushButton("Test", self)
    self.update_button.clicked.connect(self.data_provider)
    self.control_layout.addWidget(self.update_button)

    self.trigger_button = QtWidgets.QPushButton("Trigger Frame Color", self)
    self.trigger_button.clicked.connect(self.trigger_frame)
    self.control_layout.addWidget(self.trigger_button)

    self.example_continous_data = [
    (40.851, 29.041, 311, [(40.85851, 29.04981, 1)]),
    (40.852, 29.042, 312, [(40.85852, 29.04982, 2)]),
    (40.853, 29.043, 313, [(40.85853, 29.04983, 3)]),
    (40.854, 29.044, 314, [(40.85854, 29.04984, 4)]),
    (40.855, 29.045, 315, [(40.85855, 29.04985, 5)]),
    (40.856, 29.046, 316, [(40.85856, 29.04986, 6)]),
    (40.857, 29.047, 317, [(40.85857, 29.04987, 7)]),
    (40.858, 29.048, 318, [(40.85858, 29.04988, 8)]),
    (40.859, 29.049, 319, [(40.85859, 29.04989, 9)])
    ]
    self.data_index = 0

    def data_provider(self) -> None:
    """Update the map with the next data point every second."""
    if self.data_index < len(self.example_continous_data):
    latitude, longitude, degree, dangerous_areas = \
    self.example_continous_data[self.data_index]
    self.create_map(latitude, longitude, degree, dangerous_areas)
    self.data_index += 1
    else:
    self.data_index = 0
    self.timer2 = QTimer()
    self.timer2.timeout.connect(self.data_provider)
    self.timer2.start(1000)

    def create_map(self, latitude, longitude, heading, dangerous_areas) -> None:
    """
    Create or update a Folium map centered on the given location
    with a heading arrow.
    """
    self.map = folium.Map(
    location=[latitude, longitude],
    zoom_start=14,
    tiles="OpenStreetMap",
    attribution_control=False,
    zoom_control=True
    )
    folium.TileLayer("OpenSeaMap", name="OpenSeaMap").add_to(self.map)
    # Custom CSS for circular map and repositioned controls
    circular_css = """
    <style>
    .leaflet-top.leaflet-left {
    left: 24%; /* Adjust horizontal positioning of controls */
    top: 90%; /* Adjust vertical positioning of controls */
    transform: translate(-24%, -90%);
    position: absolute;
    z-index: 1000;/*Ensure controls are visible above the map*/
    }
    </style>
    """
    # Inject the custom CSS into the map HTML
    self.map.get_root().header.add_child(folium.Element(circular_css))
    # Add a ship marker with an arrow to indicate heading
    size = 22
    arrow_icon = folium.DivIcon(html=f"""
    <div style="transform: rotate({heading}deg);\
    font-size: {size}px; color: red;">
    &#10148;
    </div>
    """)
    marker = folium.Marker(
    location=[latitude, longitude],
    icon=arrow_icon,
    tooltip=f"{latitude}, {longitude}, {heading}°"
    )
    marker.add_to(self.map)

    # Add dangerous areas
    for lat, lon, depth in dangerous_areas:
    circle = folium.Circle(
    location=[lat, lon],
    radius=100,
    color="red",
    fill=True,
    fill_color="red",
    fill_opacity=0.5,
    tooltip=f"Depth: {depth}m"
    )
    circle.add_to(self.map)
    self.render_map()

    def render_map(self) -> None:
    # Save map to memory
    data = io.BytesIO()
    self.map.save(data, close_file=False)
    # Display the map in the WebEngineView
    self.map_view.setHtml(data.getvalue().decode())
    data.close()

    def update_location(self) -> None:
    """Update the map's location and heading based on user input."""
    try:
    latitude = float(self.lat_input.text())
    longitude = float(self.lon_input.text())
    heading = float(self.heading_input.text())
    self.create_map(latitude, longitude, heading, [])
    except ValueError:
    QtWidgets.QMessageBox.critical(self,
    "Error", "Invalid latitude, longitude, or heading")

    def update_frame_style(self) -> None:
    """Update the frame style based on the `triggered` variable."""
    red = "border: 5px solid red; border-radius: 155px;"
    black = "border: 5px solid black; border-radius: 155px;"
    if self.triggered:
    # create a loop to blink the frame in parallel
    self.timer = QTimer()
    self.timer.timeout.connect(self.update_frame_style)
    self.timer.start(1000)
    if self.frame.styleSheet() == red:
    self.frame.setStyleSheet(black)
    else:
    self.frame.setStyleSheet(red)
    else:
    # reset the frame to black
    self.frame.setStyleSheet(black)

    def trigger_frame(self) -> None:
    """Toggle the frame color based on an external trigger."""
    self.triggered = not self.triggered
    self.update_frame_style()


    class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
    super().__init__()
    self.setWindowTitle("Dynamic Location Update with Heading")
    self.resize(600, 600)
    self.setCentralWidget(Map())


    if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main_window = Map()
    main_window.show()
    sys.exit(app.exec_())

    Continue reading...

Compartilhe esta Página