在从 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 setupUI
和self.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>
main.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 Tab Widget:如何在鼠标单击时获取活动选项卡窗口的索引?