I need to display a map in my pyqt5 application. After trying many options, I have come to a conclusion that using leaflet js is my best bet. I have created a simple ui in Qt designer and connected the ui in index.py
:
import sys
import os.path
from os import environ
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUiType
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
ui,_=loadUiType('user_interface.ui')
def suppress_qt_warnings():
environ["QT_DEVICE_PIXEL_RATIO"] = "0"
environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
environ["QT_SCREEN_SCALE_FACTORS"] = "1"
environ["QT_SCALE_FACTOR"] = "1"
class MainApp(QMainWindow, ui):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
def main():
suppress_qt_warnings()
app=QApplication(sys.argv)
QApplication.processEvents()
window = MainApp()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
There is a promoted widget called leafwidget in the ui with the following code to display my leaflet map (leafwidget.py):
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebChannel import QWebChannel
class WebEnginePage(QWebEnginePage):
def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
print("javaScriptConsoleMessage: ", level, message, lineNumber, sourceID)
class LeafWidget (QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.browser = QWebEngineView()
self.browser.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
self.browser.setPage(WebEnginePage(self.browser))
self.browser.load(QUrl.fromLocalFile(QDir.current().absoluteFilePath('mymap.html')))
self.browser.loadFinished.connect(self.onLoadFinished)
self.channel = QWebChannel()
self.handler = CallHandler()
self.channel.registerObject('jsHelper', self.handler)
self.browser.page().setWebChannel(self.channel)
lay = QVBoxLayout(self)
lay.addWidget(self.browser)
def onLoadFinished(self, ok):
if ok:
self.browser.page().runJavaScript("")
class CallHandler(QObject):
@pyqtSlot(int)
def logMapId(self, ID1):
print('callback received', ID1)
As you can see in this code, I am actually loading mymap.html
file with a leaflet map and some javascript to style and load a geojson file. Here is my html code:
<!DOCTYPE html>
<head>
<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="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript">
var backend;
new QWebChannel(qt.webChannelTransport, function(channel) {
backend = channel.objects.jsHelper;
})
</script>
<style>
#my-map {
width:1200px;
height:450px;
}
</style>
</head>
<body>
<div id="my-map"></div>
<script>
var osmUrl1 = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png';
var grayscale = L.tileLayer(osmUrl1, {id: 'MapID', attribution:'1'});
var baseMaps = {
"Grayscale": grayscale,
};
var myStyle = {
"color": "blue",
"weight": 4,
};
$.getJSON("roadway.geojson", function(data) {
var geojson = L.geoJson(data, {
style: myStyle,
onEachFeature: function (feature, layer) {
layer.on('click', function (e) {
backend.logMapId(feature.properties.ID1)
});
},
});
var map = L.map('my-map')
.fitBounds(geojson.getBounds());
var overlayMaps = {
"net": geojson
};
grayscale.addTo(map)
geojson.addTo(map);
L.control.layers(baseMaps, overlayMaps).addTo(map);
});
</script>
</body>
</html>
The key point to note here is the click event that returns the clicked line ID of the geiojson layer using a QWebChannel
to leafwidget.py
. Then in leafwidget.py
, I receive the clicked line id using a slot inside CallHandler
class. It all works well and I can see the printed ID values in the console every time I click a line.
Now the last step is to pass every link id to my index.py and then display in a listview
or something. I was thinking of defining a signal inside the CallHandler
class and emit the line id each time a line is clicked in the map and received in CallHandler
. Then receive the emitted value in a slot in my index.py and add to the listview
. I am not sure how to do that though?
That is a trivial task that you have answered yourself: Use signals as they allow you to exchange information between objects asynchronously. On the other hand, the concept of module or .py file only makes sense in the organization of classes, functions, etc. but it does not intervene in the operation itself, since this occurs between the interaction of objects.
class CallHandler(QObject):
geojson_clicked = pyqtSignal(int)
@pyqtSlot(int)
def logMapId(self, ID1):
self.geojson_clicked.emit(ID1)
class MainApp(QMainWindow, ui):
def __init__(self):
QMainWindow.__init__(self)
self.setupUi(self)
self.LeafWidget.handler.geojson_clicked.connect(self.handle_geojson_clicked)
def handle_geojson_clicked(self, id_):
self.statusBar().showMessage("{} clicked".format(id_))