简体   繁体   English

从 PyQt5 小部件中的 Folium 地图中选择点

[英]Select points from Folium map in a PyQt5 widget

I am fairly new to pyqt5 but I am able to display a Folium map using the PyQt5 QWebEngineView widget, and I can place a button on top of the map.我对 pyqt5 相当陌生,但我能够使用 PyQt5 QWebEngineView小部件显示Folium 地图,并且我可以在地图顶部放置一个按钮。

I want to be able to click the button and that the next click on the map saves the place where I clicked, ie the lat,lon coordinates.我希望能够点击按钮,并在地图上的下一个点击保存在那里我点击的地方,即纬度,经度坐标。 But I do not know how to interact with the map in that manner.但我不知道如何以这种方式与地图交互。

I tried obtaining the position of the click in terms of pixels, but I can not translate it to lat,lon because I can not obtain the coordinates of the bounds of the map at the moment I click, note that the map can be moved around with the cursor too.我尝试以像素为单位获取点击的位置,但我无法将其转换为 lat,lon 因为我无法在我点击的那一刻获得地图边界的坐标,请注意地图可以四处移动也用光标。 Thank you for your help谢谢您的帮助

class FoliumDisplay(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Folium map in PyQt')
        self.window_width, self.window_height = 1200, 800
        self.setMinimumSize(self.window_width, self.window_height)
        layout = QVBoxLayout()
        self.setLayout(layout)

        coordinate = (51.301100, 5.272991)
        m = folium.Map(
            tiles='Stamen Terrain',
            zoom_start=4,
            location=coordinate)
        # save map data to data object
        data = io.BytesIO()
        m.save(data, close_file=False)

        webView = QWebEngineView() # start web engine
        webView.setHtml(data.getvalue().decode()) #give html of folium map to webengine
        layout.addWidget(webView)

        #### CEATE SELECT  BUTTON
        self.button_select_point = QPushButton(self)
        font = QtGui.QFont()
        font.setFamily("Bauhaus 93")
        font.setPointSize(10)
        self.button_select_point.setFont(font)
        self.button_select_point.setGeometry(QRect(100,20,200,50))
        self.button_select_point.setText("Select one point")
        self.button_select_point.clicked.connect(self.clicked_button_select_point)

    def clicked_button_select_point(self):
        print("Clicked")

folium is made to produce HTML based on the previous settings but it unnecessarily complicates the communication between the page events and Qt. folium 是根据先前的设置生成 HTML 的,但它不必要地使页面事件和 Qt 之间的通信复杂化。 In these cases it is better to implement the logic using QtWebChannel where a QObject is used to exchange the information.在这些情况下,最好使用 QtWebChannel 实现逻辑,其中 QObject 用于交换信息。

import json
import os
import sys
from pathlib import Path

from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel

CURRENT_DIRECTORY = Path(__file__).resolve().parent


class MapManager(QtCore.QObject):
    clicked = QtCore.pyqtSignal(float, float)

    @QtCore.pyqtSlot(str, str)
    def receive_data(self, message, json_data):
        data = json.loads(json_data)
        if message == "click":
            self.clicked.emit(data["lat"], data["lng"])


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self._was_clicked = False

        self.button = QtWidgets.QPushButton("Press me")
        self.label = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)

        view = QtWebEngineWidgets.QWebEngineView()
        map_manager = MapManager(self)
        channel = QtWebChannel.QWebChannel(view)
        channel.registerObject("map_manager", map_manager)
        view.page().setWebChannel(channel)
        filename = os.fspath(CURRENT_DIRECTORY / "index.html")
        url = QtCore.QUrl.fromLocalFile(filename)
        view.load(url)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.button)
        lay.addWidget(self.label)
        lay.addWidget(view)

        map_manager.clicked.connect(self.handle_map_clicked)
        self.button.clicked.connect(self.handle_button_clicked)

    def handle_map_clicked(self, lat, lng):
        if self._was_clicked:
            self.label.setText(f"latitude: {lat} longitude: {lng}")
        self._was_clicked = False

    def handle_button_clicked(self):
        self._was_clicked = True


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    w = Widget()
    w.show()

    sys.exit(app.exec_())

index.html索引.html

<!DOCTYPE html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  <script
    type="text/javascript"
    src="qrc:///qtwebchannel/qwebchannel.js"
  ></script>
  <link
    rel="stylesheet"
    href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
    integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
    crossorigin=""
  />
  <script
    src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
    integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
    crossorigin=""
  ></script>
  <style>
    #mapid {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
    }
  </style>
</head>
<body>
  <div id="mapid"></div>
</body>
<script>
  window.onload = function () {
    var map_manager = null;
    var map = L.map("mapid").setView([51.3011, 5.272991], 4);
    L.tileLayer(
      "https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg",
      {
        attribution:
          'Map tiles by \u003ca href="http://stamen.com"\u003eStamen Design\u003c/a\u003e, under \u003ca href="http://creativecommons.org/licenses/by/3.0"\u003eCC BY 3.0\u003c/a\u003e. Data by \u0026copy; \u003ca href="http://openstreetmap.org"\u003eOpenStreetMap\u003c/a\u003e, under \u003ca href="http://creativecommons.org/licenses/by-sa/3.0"\u003eCC BY SA\u003c/a\u003e.',
      }
    ).addTo(map);
    map.on("click", function (e) {
      var data = JSON.stringify(e.latlng)
      map_manager.receive_data("click", data);
    });
    new QWebChannel(qt.webChannelTransport, function (channel) {
      map_manager = channel.objects.map_manager;
    });
  };
</script>

If you insist or prefer using folium.如果您坚持或更喜欢使用大叶。 You can add a custom javascript to your folium map and console.log the click event coordinates.您可以将自定义 javascript 添加到您的 folium 地图和 console.log 单击事件坐标。 As per this answer by @eyllanesc you can add javascript console message handler.根据@eyllanesc 的这个答案,您可以添加 javascript 控制台消息处理程序。

Sample Code:示例代码:

import folium
import io
import sys
import json
from branca.element import Element
from PyQt5 import QtCore, QtGui, QtWidgets

from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView

class WebEnginePage(QWebEnginePage):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent            

    def javaScriptConsoleMessage(self, level, msg, line, sourceID):
        print(msg) # Check js errors
        if 'coordinates' in msg:
            self.parent.handleConsoleMessage(msg)  

class FoliumDisplay(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Folium map in PyQt')
        self.window_width, self.window_height = 1200, 800
        self.setMinimumSize(self.window_width, self.window_height)
        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        coordinate = (51.301100, 5.272991)
        m = folium.Map(
        tiles='Stamen Terrain',
        zoom_start=4,
        location=coordinate)

        #Add Custom JS to folium map
        m = self.add_customjs(m)
        # save map data to data object
        data = io.BytesIO()
        m.save(data, close_file=False)

        webView = QWebEngineView() # start web engine
        page = WebEnginePage(self)
        webView.setPage(page)
        webView.setHtml(data.getvalue().decode()) #give html of folium map to webengine
        layout.addWidget(webView)

        #### CEATE SELECT  BUTTON
        self.button_select_point = QtWidgets.QPushButton(self)
        font = QtGui.QFont()
        font.setFamily("Bauhaus 93")
        font.setPointSize(10)
        self.button_select_point.setFont(font)      
        
        self.button_select_point.setGeometry(QtCore.QRect(100,20,200,50))
        self.button_select_point.setText("Select one point")     self.button_select_point.clicked.connect(self.clicked_button_select_point)

        self.label = QtWidgets.QLabel()
        layout.addWidget(self.button_select_point)
        layout.addWidget(self.label)

    def add_customjs(self, map_object):
        my_js = f"""{map_object.get_name()}.on("click",
                 function (e) {{
                    var data = `{{"coordinates": ${{JSON.stringify(e.latlng)}}}}`;
                    console.log(data)}});"""

        e = Element(my_js)
        html = map_object.get_root()
        html.script.get_root().render()
        # Insert new element or custom JS
        html.script._children[e.get_name()] = e

        return map_object

    def clicked_button_select_point(self):
        print("Clicked")

    def handleConsoleMessage(self, msg):
   
        data = json.loads(msg)       
        lat = data['coordinates']['lat']
        lng = data['coordinates']['lng']
        coords =  f"latitude: {lat} longitude: {lng}"
        self.label.setText(coords)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    w = FoliumDisplay()
    w.show()

    sys.exit(app.exec_())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM