在从 JS 到 PyQt5 的鼠标点击时存储 lat long

Posted

技术标签:

【中文标题】在从 JS 到 PyQt5 的鼠标点击时存储 lat long【英文标题】:storing lat long on mouse click from JS to PyQt5 【发布时间】:2018-06-29 17:38:10 【问题描述】:

我正在尝试将鼠标单击时的 lat/lng 存储到几何点。 所以基本上当用户点击地图时,我需要获取这些坐标(纬度/经度)并将它们存储到变量中,例如 point = Point(lat, lng)所以我可以计算最近的几何图形并从 PostGIS 中提取数据。

我知道我需要建立后端和@pyqtSlot(float,float),但由于我是新手,所以我无法让它工作。我有这个由 QtDesigner 生成的代码,你不必为所有的按钮而烦恼。 这是 html/JS 部分:

maphtml = '''
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta name="robots" content="index, all" />    
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <title>WebGL Earth API - Side-by-side - Basic Leaflet 
    compatibility</title>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet- 
    0.7.2/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
    <script src="http://www.webglearth.com/v2/api.js"></script>
    <script src="scripts/qwebchannel.js"></script>
    <script>
    var backend;
    new QWebChannel(qt.webChannelTransport, function (channel) 
    backend = channel.objects.backend;


    );
    function init() 
    var m = ;

    start_(L, 'L');
    start_(WE, 'WE');

    function start_(API, suffix) 
    var mapDiv = 'map' + suffix;
    var map = API.map(mapDiv, 
    center: [51.505, -0.09],
    zoom: 4,
    dragging: true,
    scrollWheelZoom: true,
    proxyHost: 'http://srtm.webglearth.com/cgi-bin/corsproxy.fcgi?url='
    );
    m[suffix] = map;

    //Add baselayer
    API.tileLayer('http://s.tile.openstreetmap.org/z/x/y.png',
    attribution: '© OpenStreetMap contributors'
    ).addTo(map);

    //Add TileJSON overlay
    var json = "profile": "mercator", "name": "Grand Canyon USGS", "format": 
    "png", "bounds": [15.976953506469728, 45.813157465613884], "minzoom": 10, 
    "version": "1.0.0", "maxzoom": 16, "center": [15.976953506469728, 
    45.813157465613884, 16], "type": "overlay", "description": "", 
    "basename": 
    "grandcanyon", "tilejson": "2.0.0", "sheme": "xyz", "tiles": 
    ["http://tileserver.maptiler.com/grandcanyon/z/x/y.png"];
    if (API.tileLayerJSON) 
    var overlay2 = API.tileLayerJSON(json, map);
     else 
    //If not able to display the overlay, at least move to the same location
    map.setView([json.center[1], json.center[0]], json.center[2]);
    



    //Print coordinates of the mouse
    map.on('click', function(e) 

    document.getElementById('coords').innerHTML = e.latlng.lat + ', ' + 
    e.latlng.lng;
    backend.foo(e.latlng.lat,e.latlng.lng)

    );
    

    //Synchronize view
    m['L'].on('click', function(e) 
    var center = m['L'].getCenter();
    var zoom = m['L'].getZoom();
    m['WE'].setView([center['lat'], center['lng']], zoom);
    );
    
    </script>
    <style>
    html, bodypadding: 0; margin: 0; overflow: hidden;
    #mapL, #mapWE position:absolute !important; top: 0; right: 0; bottom: 0; 
    left: 0;
    background-color: #fff; position: absolute !important;
    #mapL right: 0%;
    #mapWE left: 100%;
    #coords position: absolute; bottom: 0;
    </style>
    </head>
    <body onload="javascript:init()">
    <div id="mapL"></div>
    <div id="mapWE"></div>
    <div id="coords"></div>
    </body>
    </html>
    '''

这里是 PyQt 代码,它们都在同一个 .py 中。一开始我有Backend() 应该做它的工作。你可以跳到def setupUIself.mapa = QtWidgets.QWidget(self.centralwidget) 看看我在哪里打电话给Backend()

class Backend(QObject):
    @pyqtSlot(float,float)
    def foo(self, lat,lng):
        global x,y
        x=lng
        y=lat
        print(lat, lng)

class Ui_MainWindow(object):

    def selected(self, text):
        self.selected_text = text
        return self.selected_text

    def connecti(self):
        try:
            engine = 
     create_engine('postgresql://postgres:hrvatina@localhost:5432/Diplomski')
            self.connection = engine.connect()

            return QMessageBox.information(None, "Uspješna konekcija", 
     "Spojeni ste na bazu")
        except:

            return QMessageBox.critical(None, "Pogreška", "Neuspješno 
     spajanje na bazu")


    def odabir(self):

        try:

            broj = self.link.text()

            self.comboBox.activated[str].connect(self.selected)

            value = str(self.comboBox.currentText())
            if self.pon.isChecked():
                radio = str(self.pon.text())
                print(radio)
            elif self.uto.isChecked():
                radio = str(self.uto.text())
                print(radio)
            elif self.sri.isChecked():
                radio = str(self.sri.text())
                print(radio)
            elif self.cet.isChecked():
                radio = str(self.cet.text())
                print(radio)
            elif self.pet.isChecked():
                radio = str(self.pet.text())
                print(radio)
            elif self.sub.isChecked():
                radio = str(self.sub.text())
                print(radio)
            elif self.ned.isChecked():
                radio = str(self.ned.text())
                print(radio)


            query ='SELECT * FROM ' + radio + '_' + value + ' where "IdLink" 
     = %s' % (broj)

            df = pd.read_sql_query(query, con=self.connection, params= 
     'link': '%' + broj + '%')
            df = df.transpose()

            hf = df.join(df.iloc[:, 0].str.split(';', 3, 
    expand=True).rename(columns=0: 'mean', 1: 'median', 2: 'std'))


            g = hf.drop(hf.columns[[0]], axis=1)

            hm = g.drop(g.index[[0]])

            prva1 = hm.apply(pd.to_numeric, errors='coerce')

            #Izračun srednjih vrijednosti
            mean=prva1['mean'].mean()
            self.mean.setText(str(mean))
            median=prva1['median'].mean()
            self.median.setText(str(median))
            std=prva1['std'].mean()
            self.std.setText(str(std))

            upper_bound = go.Scatter(
                name='Gornja granica',
                x=prva1.index.to_native_types(),
                y=prva1['mean'] + prva1['std'],
                mode='lines',
                marker=dict(color="028F1E"),
                line=dict(width=1, color="028F1E"),
                fillcolor='rgba(68, 68, 68, 0.3)',

                fill='tonexty')

            trace = go.Scatter(
                name='Srednja vrijednost',
                x=prva1.index.to_native_types(),
                y=prva1['mean'],
                mode='lines',
                line=dict(color='rgb(31, 119, 180)'),
                fillcolor='rgba(68, 68, 68, 0.3)',
                fill='tonexty')

            lower_bound = go.Scatter(
                name='Donja granica',
                x=prva1.index.to_native_types(),
                y=prva1['mean'] - prva1['std'],
                marker=dict(color="9B111E"),
                line=dict(width=1, color="9B111E"),
                mode='lines')

            data = [lower_bound, trace, upper_bound]

            layout = go.Layout(
                xaxis=dict(
                    title='AXIS TITLE',
                    titlefont=dict(
                        family='Arial, sans-serif',
                        size=18,
                        color='lightgrey'
                    ),
                    showticklabels=True,
                    tickangle=45,
                    tickfont=dict(
                        family='Old Standard TT, serif',
                        size=14,
                        color='black'
                    ),
                    exponentformat='e',
                    showexponent='All'
                ),
                yaxis=dict(title='Brzina (km/h)'),
                title='Minutni interval za ' + radio + ' sa standardnom 
          devijacijom za ' + broj,
            )

            fig = go.Figure(data=data, layout=layout)
            raw_html = '<html><head><meta charset="utf-8" />'
            raw_html += '<script src="https://cdn.plot.ly/plotly- 
    latest.min.js"></script></head>'
            raw_html += '<body>'
            raw_html += plotly.offline.plot(fig, include_plotlyjs=False, 
    output_type='div')
            raw_html += '</body></html>'

            #PLOTLY
            self.graf.setHtml(raw_html)

        except:

            return QMessageBox.critical(None, "Pogreška", "Podaci za odabrane 
            opcije ne postoje!")
    def setupUi(self, MainWindow):

        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(978, 704)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.formLayout = QtWidgets.QFormLayout(self.centralwidget)
        self.formLayout.setObjectName("formLayout")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.gumb_spoji = QtWidgets.QPushButton(self.centralwidget)
        self.gumb_spoji.setObjectName("gumb_spoji")

        # Spoji se na bazu
        self.gumb_spoji.clicked.connect(self.connecti)
        engine = 
        self.verticalLayout.addWidget(self.gumb_spoji)
        self.link = QtWidgets.QLineEdit(self.centralwidget)
        self.link.setObjectName("link")
        self.verticalLayout.addWidget(self.link)
        self.pon = QtWidgets.QRadioButton(self.centralwidget)
        self.pon.setObjectName("pon")
        self.verticalLayout.addWidget(self.pon)
        self.uto = QtWidgets.QRadioButton(self.centralwidget)
        self.uto.setObjectName("uto")
        self.verticalLayout.addWidget(self.uto)
        self.sri = QtWidgets.QRadioButton(self.centralwidget)
        self.sri.setObjectName("sri")
        self.verticalLayout.addWidget(self.sri)
        self.cet = QtWidgets.QRadioButton(self.centralwidget)
        self.cet.setObjectName("cet")
        self.verticalLayout.addWidget(self.cet)
        self.pet = QtWidgets.QRadioButton(self.centralwidget)
        self.pet.setObjectName("pet")
        self.verticalLayout.addWidget(self.pet)
        self.sub = QtWidgets.QRadioButton(self.centralwidget)
        self.sub.setObjectName("sub")
        self.verticalLayout.addWidget(self.sub)
        self.ned = QtWidgets.QRadioButton(self.centralwidget)
        self.ned.setObjectName("ned")
        self.verticalLayout.addWidget(self.ned)
        self.comboBox = QtWidgets.QComboBox(self.centralwidget)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.verticalLayout.addWidget(self.comboBox)
        self.gumb_dohvati = QtWidgets.QPushButton(self.centralwidget)
        self.gumb_dohvati.setObjectName("gumb_dohvati")

        # Dohvati podatke
        self.gumb_dohvati.clicked.connect(self.odabir)

        self.verticalLayout.addWidget(self.gumb_dohvati)
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.mean = QtWidgets.QLineEdit(self.centralwidget)
        self.mean.setObjectName("mean")
        self.verticalLayout.addWidget(self.mean)
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.verticalLayout.addWidget(self.label_2)
        self.median = QtWidgets.QLineEdit(self.centralwidget)
        self.median.setObjectName("median")
        self.verticalLayout.addWidget(self.median)
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setObjectName("label_3")
        self.verticalLayout.addWidget(self.label_3)
        self.std = QtWidgets.QLineEdit(self.centralwidget)
        self.std.setObjectName("std")
        self.verticalLayout.addWidget(self.std)
        self.formLayout.setLayout(0, QtWidgets.QFormLayout.LabelRole, 
        self.verticalLayout)

        # I want to pass lat/lng here and after user click show data in 
        Qwidget below, but i will do that on my own i just need those lat/lng 
        after every click
        self.mapa = QtWidgets.QWidget(self.centralwidget)
        self.mapa.setObjectName("mapa")
        backend = Backend()
        channel = QWebChannel()
        channel.registerObject('backend', backend)

        self.mapa = QWebEngineView()
        self.mapa.page().setWebChannel(channel)

        self.mapa.setHtml(maphtml)

        point=Point(y,x)





        #sql = 'select * from geo'
        #df = gpd.GeoDataFrame.from_postgis(sql, self.connection, 
 geom_col='geometry' )
        #def min_dist(point, gpd2):
            #df['Dist'] = df.apply(lambda row:  
 point.distance(row.geometry),axis=1)
            #geoseries = df.iloc[gpd2['Dist'].idxmin()]
            #return geoseries['IdLink']
        #point=Point(self.Backend.lat(),self.Backend.lng())
        #self.brojka=min_dist(point,df)

        self.formLayout_3 = QtWidgets.QFormLayout(self.mapa)
        self.formLayout_3.setObjectName("formLayout_3")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, 
        self.mapa)
        self.graf = QtWidgets.QWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, 
        QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(255)

   sizePolicy.setHeightForWidth(self.graf.sizePolicy().hasHeightForWidth())
        self.graf.setSizePolicy(sizePolicy)
        self.graf.setMouseTracking(True)
        self.graf.setObjectName("graf")
        self.graf = QWebEngineView(self.centralwidget)
        self.formLayout_2 = QtWidgets.QFormLayout(self.graf)
        self.formLayout_2.setObjectName("formLayout_2")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, 
        self.graf)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 978, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.gumb_spoji.setText(_translate("MainWindow", "Spoji se na bazu"))
        self.pon.setText(_translate("MainWindow", "Ponedjeljak"))
        self.uto.setText(_translate("MainWindow", "Utorak"))
        self.sri.setText(_translate("MainWindow", "Srijeda"))
        self.cet.setText(_translate("MainWindow", "Cetvrtak"))
        self.pet.setText(_translate("MainWindow", "Petak"))
        self.sub.setText(_translate("MainWindow", "Subota"))
        self.ned.setText(_translate("MainWindow", "Nedjelja"))
        self.comboBox.setItemText(0, _translate("MainWindow", "5min"))
        self.comboBox.setItemText(1, _translate("MainWindow", "15min"))
        self.gumb_dohvati.setText(_translate("MainWindow", "Dohvati 
        podatke"))
        self.label.setText(_translate("MainWindow", "Srednja vrijednost"))
        self.label_2.setText(_translate("MainWindow", "Median"))
        self.label_3.setText(_translate("MainWindow", "Standardna 
        devijacija"))


if __name__ == "__main__":
    import sys
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()

    sys.exit(app.exec_())

我看到了类似的问题,但到目前为止还无法解决... 在 PyQt4 中我几乎可以正常工作,但由于我不擅长 JS,我不知道如何从这段代码中返回 lat/lng 而不是 getCenter().lat:

if(typeof MainWindow != 'undefined') 
    var onMapMove = function(e)  MainWindow.onMapMove(map.getCenter().lat, 
map.getCenter().lng) ;
    map.on('click', onMapMove);
    onMapMove();

【问题讨论】:

什么是Point Point 是 shapely 的几何对象! 【参考方案1】:

我无法对您的代码进行太多处理,因为它格式错误并且缺少要定义的元素,因此我的回答将基于向您展示示例。

首先它并没有把所有的代码都放在一个文件里,这使得代码维护起来很困难,在这种情况下我分为以下几个文件:

.
├── index.html
├── main.py
├── utils.css
└── utils.js

window.onload = function() 
    new QWebChannel(qt.webChannelTransport, function (channel) 
        window.backend = channel.objects.backend;
    );

    var m = ;

    start_(L, 'L');
    start_(WE, 'WE');

    function start_(API, suffix) 
        var mapDiv = 'map' + suffix;
        var map = API.map(mapDiv, 
            center: [51.505, -0.09],
            zoom: 4,
            dragging: true,
            scrollWheelZoom: true,
            proxyHost: 'http://srtm.webglearth.com/cgi-bin/corsproxy.fcgi?url='
        );
        m[suffix] = map;

        //Add baselayer
        API.tileLayer('http://s.tile.openstreetmap.org/z/x/y.png', 
            attribution: '© OpenStreetMap contributors'
        ).addTo(map);

        //Add TileJSON overlay
        var json = 
            "profile": "mercator",
            "name": "Grand Canyon USGS",
            "format": "png",
            "bounds": [15.976953506469728, 45.813157465613884],
            "minzoom": 10,
            "version": "1.0.0",
            "maxzoom": 16,
            "center": [15.976953506469728,
                45.813157465613884, 16
            ],
            "type": "overlay",
            "description": "",
            "basename": "grandcanyon",
            "tilejson": "2.0.0",
            "sheme": "xyz",
            "tiles": ["http://tileserver.maptiler.com/grandcanyon/z/x/y.png"]
        ;
        if (API.tileLayerJSON) 
            var overlay2 = API.tileLayerJSON(json, map);
         else 
            //If not able to display the overlay, at least move to the same location
            map.setView([json.center[1], json.center[0]], json.center[2]);
        

        //Print coordinates of the mouse
        map.on('click', function(e) 
            document.getElementById('coords').innerHTML = e.latlng.lat + ', ' + e.latlng.lng;
            backend.pointClicked(e.latlng.lat, e.latlng.lng);
        );
    

    //Synchronize view
    m['L'].on('click', function(e) 
        var center = m['L'].getCenter();
        var zoom = m['L'].getZoom();
        m['WE'].setView([center['lat'], center['lng']], zoom);
    );
html, body 
     padding: 0;
     margin: 0;
     overflow: hidden;


#mapL, #mapWE 
     position: absolute !important;
     top: 0;
     right: 0;
     bottom: 0;
     left: 0;
     background-color: #fff;
     position: absolute !important;

 #mapL 
     right: 0%;

 #mapWE 
     left: 100%;

 #coords 
     position: absolute;
     bottom: 0;
<!DOCTYPE HTML>
<html>

<head>
    <meta name="robots" content="index, all"/>
    <title>WebGL Earth API - Side-by-side - Basic Leaflet compatibility
    </title>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css"/>
    <link rel="stylesheet" href="utils.css"/>

    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
    <script type="text/javascript" src="http://www.webglearth.com/v2/api.js"></script>
    <script type="text/javascript" src="utils.js"> </script>
</head>

<body>
    <div id="mapL"></div>
    <div id="mapWE"></div>
    <div id="coords"></div>
</body>

</html>

ma​​in.py

from PyQt5 import QtWebEngineWidgets, QtWidgets, QtCore, QtWebChannel

class Backend(QtCore.QObject):
    pointChanged = QtCore.pyqtSignal(float, float)

    @QtCore.pyqtSlot(float,float)
    def pointClicked(self, x, y):
        self.pointChanged.emit(x, y)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        map_view = QtWebEngineWidgets.QWebEngineView()

        backend = Backend(self)
        backend.pointChanged.connect(self.onPointChanged)
        channel = QtWebChannel.QWebChannel(self)
        channel.registerObject('backend', backend)
        map_view.page().setWebChannel(channel)

        file = QtCore.QDir.current().absoluteFilePath("index.html")
        map_view.load(QtCore.QUrl.fromLocalFile(file))

        self.setCentralWidget(map_view)

    @QtCore.pyqtSlot(float,float)
    def onPointChanged(self, x, y):
        print("new points")
        print(x, y)


if __name__ == '__main__':
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

谢谢你!正在观看您所有类似的答案,您是 PyQt 的摇滚明星!我知道我的代码是一团糟……因为它是由 QtDesigner 生成的,我只是将我在实施到 GUI 之前制作的代码粘贴。再一次感谢你!我将在我的代码中实现这个示例, @ValentinoVizner 根据您提供的信息,我已经合并了我的解决方案,您可以在以下链接中找到它:github.com/eyllanesc/***/tree/master/51106914 。请不要忘记将我的答案标记为正确,因为您指出这是在 SO 中感谢的最佳方式,如果您不知道,请给自己一点时间并查看 tour 非常感谢!你真的很了解 PyQt,希望有一天我能和你一样了解。我标记了它,并将审查游览!

以上是关于在从 JS 到 PyQt5 的鼠标点击时存储 lat long的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5 - 如何在鼠标点击位置上画一个点?

模拟鼠标拖动事件 PyQt5

QFormLayout的PyQt5顶行没有响应鼠标点击

PyQt5 Tab Widget:如何在鼠标单击时获取活动选项卡窗口的索引?

如何在从串口python pyqt接收的textbrowser pyqt5上附加串行数据[重复]

Pyqt5 鼠标事件不适用于我的自定义标签栏