1

I have building a GUI in Python through PyQt5. I am showing a web browser with a google maps page. The user is supposed to move the marker and my program should process the coordinates of the marker. Therefore I have to pass the coordinates from JS to Python, but I can't manage to make it work.

Here is the HTML file:

<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">

 function geocodePosition(pos) {
  geocoder.geocode({
    latLng: pos
  }, function(responses) {
    if (responses && responses.length > 0) {
      updateMarkerAddress(responses[0].formatted_address);
    } else {
      updateMarkerAddress('Cannot determine address at this location.');
    }
  });
}

function updateMarkerStatus(str) {
  document.getElementById('markerStatus').innerHTML = str;
}

function updateMarkerPosition(latLng) {
  document.getElementById('info').innerHTML = [
    latLng.lat(),
    latLng.lng()
  ].join(', ');
}

function updateMarkerAddress(str) {
  document.getElementById('address').innerHTML = str;
}


var geocoder = new google.maps.Geocoder();
var map;


var goldStar = {
    path: 'M 125,5 155,90 245,90 175,145 200,230 125,180 50,230 75,145 5,90 95,90 z',
    fillColor: 'yellow',
    fillOpacity: 0.8,
    scale: 0.1,
    strokeColor: 'gold',
    strokeWeight: 1
};



function addMarker(lat, lon, city, url) {
    var newmarker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lon),
        icon: goldStar,
        map: map,
        title: city
    });
    newmarker['infowindow'] = new google.maps.InfoWindow({
            content: url
        });
    google.maps.event.addListener(newmarker, 'click', function() {
        this['infowindow'].open(map, this);
    });
}


function initialize() {
  var latLng = new google.maps.LatLng(40.767367, -111.848007);
  // create as a global variable
  map = new google.maps.Map(document.getElementById('mapCanvas'), {
    zoom: 11,
    center: latLng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  });
    var marker = new google.maps.Marker({
    position: latLng,
    title: 'Point A',
    map: map,
    draggable: true
  });

  // Update current position info.
  updateMarkerPosition(latLng);
  geocodePosition(latLng);

  // Add dragging event listeners.
  google.maps.event.addListener(marker, 'dragstart', function() {
    updateMarkerAddress('Dragging...');
  });

  google.maps.event.addListener(marker, 'drag', function() {
    updateMarkerStatus('Dragging...');
    updateMarkerPosition(marker.getPosition());
  });

  google.maps.event.addListener(marker, 'dragend', function() {
    updateMarkerStatus('Drag ended');
    geocodePosition(marker.getPosition());
  });

//  return latLng
}


// Onload handler to fire off the app.
google.maps.event.addDomListener(window, 'load', initialize);

</script>
</head>
<body>
  <style>
  #mapCanvas {

    # width: 1000px;
    width: 102%;
    height: 500px;
    float: left;
    margin-left: -7px;
    margin-right: -10px;
    margin-top: -7px;
    margin-bottom: 10px;
  }
  #infoPanel {
    float: center;
    margin-left: 20px;
  }
  #infoPanel div {
    margin-bottom: 10px;
  }
  </style>

      <font size="3" color="black" face="verdana">
  <div id="mapCanvas"></div>
  <div id="infoPanel">
    <font size="3" color="black" face="verdana">
    <!-- <b>Marker status:</b> -->
    <div id="markerStatus"><i>Click and drag the marker.</i></div>
    <font size="3" color="black" face="verdana">
    <b>Current position:</b>
    <div id="info"></div>
    <!--<b>Closest matching address:</b>-->
    <!--<div id="address"></div>-->
  </div>
</body>
</html>

And here is the Python code:

import sys
from PyQt5.QtWidgets import *
from GUI_tmy3 import *

class ShowMap_fun(QMainWindow):
    def __init__(self):
        super().__init__()
        self.map_ui = Ui_tmy3page()  # The name of my top level object is MainWindow
        self.map_ui.setupUi(self)
        self.map_ui.html_code.load(QtCore.QUrl.fromLocalFile('/Users/carlo/Dropbox/modules_NEW/useless.html'))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ShowMap_fun()
    ex.show()
    sys.exit(app.exec_())

With the GUI code:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_tmy3page(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(900, 620)
        MainWindow.setMinimumSize(QtCore.QSize(900, 620))
        MainWindow.setMaximumSize(QtCore.QSize(900, 620))
        MainWindow.setWindowTitle("")
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.html_code = QtWebEngineWidgets.QWebEngineView(self.centralwidget)
        self.html_code.setGeometry(QtCore.QRect(0, 0, 901, 621))
        self.html_code.setUrl(QtCore.QUrl("about:blank"))
        self.html_code.setObjectName("html_code")
        MainWindow.setCentralWidget(self.centralwidget)

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

    def retranslateUi(self, MainWindow):
        pass

from PyQt5 import QtWebEngineWidgets

I understand the easiest method would be to use QWebChannel. I found an example here but I can't adapt it to my case.

Any suggestion?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Carlo Bianchi
  • 115
  • 4
  • 15

1 Answers1

1

To make the work more orderly I have separated the javascript code to a new file called useless.js.

What you should do is create a QWebChannel object, set it on the page and register the object, also you must create a slot that receives the information:

class ShowMap_fun(QMainWindow):
    def __init__(self):
        super().__init__()
        self.map_ui = Ui_tmy3page()  # The name of my top level object is MainWindow
        self.map_ui.setupUi(self)

        channel = QtWebChannel.QWebChannel(self.map_ui.html_code.page())
        self.map_ui.html_code.page().setWebChannel(channel)
        channel.registerObject("jshelper", self)

        self.map_ui.html_code.load(QtCore.QUrl.fromLocalFile(QtCore.QDir.current().filePath("useless.html")))

    @QtCore.pyqtSlot(float, float)
    def markerMoved(self, lat, lng):
        print(lat, lng)

Then you must add the qwebchannel.js file to the .html

useless.html

<html>
<head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=yes"/>
    <script type="text/javascript" src="./qwebchannel.js"></script>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="useless.js"></script>
</head>
[...]

In the code js the object must be obtained:

useless.js

var jshelper;

new QWebChannel(qt.webChannelTransport, function (channel) {
    jshelper = channel.objects.jshelper;
});

[...]

google.maps.event.addListener(marker, 'drag', function () {
    updateMarkerStatus('Dragging...');
    updateMarkerPosition(marker.getPosition());
    jshelper.markerMoved(marker.position.lat(), marker.position.lng());
});

The complete example can be found at the following link

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thanks for the example @eyllanesc! In a similar way, I've been trying to make a `PyQt5.QtCore.pyqtProperty` from Python accessible to JS, but it keeps prompting that the property "has no notify signal and is not constant, value updates in HTML will be broken!". Do you have any hint for me on how to properly implement such a property that can be written and read from both Python and JS? – Jeronimo Jan 23 '18 at 14:28
  • A complete example link is broken. Can you please update it? – shaik moeed Jan 20 '20 at 07:16
  • Can we use two `new QQWebChannel(qt.webChannelTransport, ...` in same html? – Python coder Feb 04 '20 at 16:24
  • @eyllanesc Cuz, I wan't to run one Object in `setInterval` and one Object `.onload()`. And both use different methods of python. – Python coder Feb 04 '20 at 16:29
  • @Pythoncoder I don't understand you, that callback is only to get the QObject, I think you're confused – eyllanesc Feb 04 '20 at 16:30
  • Yeah, May be. I will try. Actually, I was struck in middle of development. – Python coder Feb 04 '20 at 16:41