Search code examples
pythonpython-3.xpyqt5qtwebengineqwebengineview

Creating a open street maps view


I am trying to port this code: https://gist.github.com/ataffanel/13aac4b79bc0080b881f to the most recent versions of pyqt5, thus, using qtwebengine. The goal is just to have a map widget in the end.

This is the code I already have, running with random html and javascript it worked, but I cant get a map view how the original code is intended to.

Main.py:


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi()
        self.show()
        self.raise_()

    def setupUi(self):
        #self.setFixedSize(800, 500)        
        vbox = QtWidgets.QVBoxLayout()
        self.setLayout(vbox)

        label = self.label = QtWidgets.QLabel()
        sp = QtWidgets.QSizePolicy()
        sp.setVerticalStretch(0)
        label.setSizePolicy(sp)
        vbox.addWidget(label)
        view = self.view = QtWebEngineWidgets.QWebEngineView()  
        channel = self.channel = QWebChannel()

        channel.registerObject('MainWindow', self)
        view.page().setWebChannel(channel)      
      # view.page().mainFrame().addToJavaScriptWindowObject("MainWindow", self)

      # view.page().setLinkDelegationPolicy(QQWebPage.DelegateAllLinks)
        html = open('src/main/assets/map.html','r').read()
        self.view.setHtml(html)
        view.loadFinished.connect(self.onLoadFinished)
        #view.linkClicked.connect(QtWidgets.QDesktopServices.openUrl)

        vbox.addWidget(view)

        button = QtWidgets.QPushButton('Go to Paris')
        panToParis = functools.partial(self.panMap, 2.3272, 48.8620)
        button.clicked.connect(panToParis)
        vbox.addWidget(button)

    def onLoadFinished(self, ok):            
       if ok:   
            page=self.view.page()
            with open('src/main/assets/map.js', 'r') as f:
                src=f.read()
                page.runJavaScript(src)


    @QtCore.pyqtSlot(float, float)
    def onMapMove(self, lat, lng):
        self.label.setText('Lng: {:.5f}, Lat: {:.5f}'.format(lng, lat))

    def panMap(self, lng, lat):
        page=self.view.page()
        page.runJavaScript('map.panTo(L.latLng({}, {}));'.format(lat, lng))

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    w = MainWindow()

app.exec_()

map.html:

<!DOCTYPE html>
<html>
<head>
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
    <style>
        body { padding: 0; margin: 0; }
        html, body, #map { height: 100%; }
    </style> 
</head>
<body>
    <div id="map"></div>

</body>
</html>

map.js:

var map = L.map('map').setView([55.61121, 12.99351], 16);
L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
    maxZoom: 18,
    attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
        'tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>',
    subdomains: '1234',
}).addTo(map);

var marker = L.marker(map.getCenter()).addTo(map);
marker.bindPopup("Hello World!").openPopup();

new QWebChannel(qt.webChannelTransport, function (channel) {
    window.MainWindow = channel.objects.MainWindow;

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

There's no error message, just a blank screen where it was supposed to be a map.... I don't get why this happens since both the necessary javascript and html render if i change them to something else...

If there's a better way to create this widget would be enough too!

Thanks in advance!


Solution

  • The main problem that your code has is that you are using URL template deprecated and old versions of leaflet.

    On the other hand it is not necessary to load the html and js as text, QtWebEngine can show the file directly.

    Considering the above, the solution is:

    structure:

    ├── main.py
    └── src
        └── main
            └── assets
                ├── map.html
                └── map.js
    

    map.js

    var map;
    
    function initialize(){
        map = L.map('map').setView([55.61121, 12.99351], 16);
    
        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
            maxZoom: 18
        }).addTo(map);
    
        var marker = L.marker(map.getCenter()).addTo(map);
        marker.bindPopup("Hello World!").openPopup();
        new QWebChannel(qt.webChannelTransport, function (channel) {
            window.MainWindow = channel.objects.MainWindow;
            if(typeof MainWindow != 'undefined') {
                var onMapMove = function() { MainWindow.onMapMove(map.getCenter().lat, map.getCenter().lng) };
                map.on('move', onMapMove);
                onMapMove();
            }
        });
    }
    

    map.html

    <!DOCTYPE html>
    <html>
    <head>
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
        <script type="text/javascript" src="map.js"></script>
        <style>
            body { padding: 0; margin: 0; }
            html, body, #map { height: 100%; }
        </style> 
    </head>
    <body onload="initialize()">
        <div id="map"></div>
    </body>
    </html>
    

    main.py

    import os
    import functools
    from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets, QtWebChannel
    
    
    class MainWindow(QtWidgets.QWidget):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.setupUi()
    
        def setupUi(self):
            # self.setFixedSize(800, 500)
            vbox = QtWidgets.QVBoxLayout()
            self.setLayout(vbox)
    
            label = self.label = QtWidgets.QLabel()
            sp = QtWidgets.QSizePolicy()
            sp.setVerticalStretch(0)
            label.setSizePolicy(sp)
            vbox.addWidget(label)
            view = self.view = QtWebEngineWidgets.QWebEngineView()
            channel = self.channel = QtWebChannel.QWebChannel()
    
            channel.registerObject("MainWindow", self)
            view.page().setWebChannel(channel)
    
            file = os.path.join(
                os.path.dirname(os.path.realpath(__file__)),
                "src/main/assets/map.html",
            )
            self.view.setUrl(QtCore.QUrl.fromLocalFile(file))
    
            vbox.addWidget(view)
    
            button = QtWidgets.QPushButton("Go to Paris")
            panToParis = functools.partial(self.panMap, 2.3272, 48.8620)
            button.clicked.connect(panToParis)
            vbox.addWidget(button)
    
        @QtCore.pyqtSlot(float, float)
        def onMapMove(self, lat, lng):
            self.label.setText("Lng: {:.5f}, Lat: {:.5f}".format(lng, lat))
    
        def panMap(self, lng, lat):
            page = self.view.page()
            page.runJavaScript("map.panTo(L.latLng({}, {}));".format(lat, lng))
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())
    

    Output:

    enter image description here