2

EDIT: I have implemented my possible solution 1, it works however as mentioned it is not the best solution so I would still be interested in other suggestions.

I have a delphi VCL application that loads a mapbox map into a Twebbrowser component. I am using the map as input to get GPS coords and a street address.
The javascript enables you to click on the map to place a marker, you can also drag it around etc. I have a javascript function that performs the reverse geocode on the markers position and saves the address to a variable.

        function getAddress(err, data){
            var center = data.features[0].center;
            streetAddress = data.features[0].place_name;
            myMarker.setPopupContent("Position: <br /> lat = " + center[0] + "<br /> long = " + center[1] + "<br /> Address: " + streetAddress);
        }

        function delphiLocate(){
            geocoder.reverseQuery(myMarker.getLatLng(), getAddress);
            gpsCoords = myMarker.getLatLng().toString();
        }

This function can be called by clicking on the marker or through my Delphi program.
My delphi code can also read the address variable from the javascript.

procedure TfrmCreateSpot.getSpotPos;
var
  doc: OleVariant;
begin
  doc := WebBrowser1.document;

  doc.parentwindow.execScript('delphiLocate()', 'JavaScript');
  sGpsCoords := doc.parentwindow.gpsCoords;
  sGpsCoords := copy(sGpsCoords, Pos('(', sGpsCoords) + 1);
  delete(sGpsCoords, Pos(')', sGpsCoords), 1);

  sStreetAddress := doc.parentwindow.streetAddress;

end;

This getSpotPos procedure is called on a button click to confirm the location.
The problem is the geolocate takes a while to return a result and by the time it returns my program has already recorded the information from the last marker position.

How can I ensure that I get the address from the current marker position?

Possible solutions:

1) If I call the locate function every time the marker is moved then the geolocate should have finished before the user clicks the confirm button. However, this seems like a rather hacky possibly unreliable solution plus it could use a lot more geolocate requests than necessary(This isn't a problem for me as its only a school project and won't be going over my 100 000 limit, but id like to implement the best solution.

2) Create a flag in the javaScript that is made true when the results are returned, in my Delphi code I could use a timer to check if this flag is true and then request the results.

Here is the complete Html script

<title>Map</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.mapbox.com/mapbox.js/v3.3.0/mapbox.js'></script>
<link href='https://api.mapbox.com/mapbox.js/v3.3.0/mapbox.css' rel='stylesheet' />
<style>
    body { margin:0; padding:0; width : 100%; }
    #map {position : absolute; top:0; bottom:0; width: 100%; }
</style>
</head>
<body>
    <div id='map'></div>
    <script>

        function mapClick(e){
            if (!locked){
            myMarker.setLatLng(e.latlng);
        }
        }

        function locate(e) {
            geocoder.reverseQuery(e.latlng, getAddress);
            gpsCoords = e.latlng.toString();
        }

        function getAddress(err, data){
            var center = data.features[0].center;
            streetAddress = data.features[0].place_name;
            myMarker.setPopupContent("Position: <br /> lat = " + center[0] + "<br /> long = " + center[1] + "<br /> Address: " + streetAddress);
        }

        function centerMarker(e){
            if (!locked){
            var coords = e.feature.geometry.coordinates;
            myMarker.setLatLng([coords[1],coords[0]]);// lat lng are swapped 
        }
        }

        function delphiLocate(){
            geocoder.reverseQuery(myMarker.getLatLng(), getAddress);
            gpsCoords = myMarker.getLatLng().toString();
        }

        var locked = false;

        function toggleLock(){
            if (!locked) {
                myMarker.setIcon(redIcon);
                myMarker.dragging.disable();
                locked = true;
            } else {
                myMarker.setIcon(blueIcon);
                myMarker.dragging.enable();
                locked = false;
            }
        }


        L.mapbox.accessToken = 'pk.eyJ1IjoiZ3JlZW5vbGl2ZSIsImEiOiJjazk4N3hpcngwMDU0M2VvMGhydGw2Z2YxIn0.wvwQzgwsaujmkNOX7Bs47A';

        var map = L.mapbox.map('map')
        .setView([-30.487,23.181], 6)
        .addLayer(L.mapbox.styleLayer('mapbox://styles/greenolive/ck9fi6dbk3hag1itg5aem2ypi'));

        var geocoderControl = L.mapbox.geocoderControl('mapbox.places',{autocomplete : true}).addTo(map);


        var geocoder = L.mapbox.geocoder('mapbox.places');

        var blueIcon = L.icon({
            iconUrl: 'blueMarker.png',
            iconSize:     [40, 40], // size of the icon
            iconAnchor:   [20, 40], // point of the icon which will correspond to marker's location
            popupAnchor:  [0, -40] // point from which the popup should open relative to the iconAnchor
        });

        var redIcon = L.icon({
            iconUrl: 'redMarker.png',
            iconSize:     [40, 40], // size of the icon
            iconAnchor:   [20, 40], // point of the icon which will correspond to marker's location
            popupAnchor:  [0, -40] // point from which the popup should open relative to the iconAnchor
        });

        var myMarker = L.marker(map.getCenter(), { draggable : true, icon : blueIcon}).addTo(map).bindPopup("Position: <br /> lat = " + map.getCenter().lat + "<br /> long = " +  map.getCenter().lng+ "<br /> Address: ");

        var gpsCoords;
        var streetAddress;


        map.on('click',mapClick);
        myMarker.on('click', locate);
        geocoderControl.on('select', centerMarker);


    </script>
</body>
</html>
  • Nice work. But like your suggestion, I think you could make the streetAddress empty, when the marker is moved, so it wont show an address until the geolocate function has returned. – R. Hoek Apr 26 '20 at 20:58
  • @R.Hoek I'm not sure how that would help me, I need my Delphi program to know when the function has returned – Oliver Hope Apr 26 '20 at 21:07
  • Hmmm, best to edit your question, because now the question is ‘know if the function has returned’ in which case my idea (or your flag idea) would suffice. But what want is ‘be notified in delphi when the function returns’ (like an event) -right? – R. Hoek Apr 26 '20 at 21:17
  • @OliverHope if you want something like events maybe this thread can help https://javascript.developreference.com/article/20717415/Callback+Delphi+function+from+TWebBrowser+by+javascript+on+Delphi+XE6+for+all+platforms+(including+iOS%2C+ANDROID)%3F – R. Hoek Apr 26 '20 at 21:25
  • And this http://delphidabbler.com/articles?article=22 – R. Hoek Apr 26 '20 at 21:29
  • @R.Hoek yeah I guess an event is ideal, I thought my question was open enough for that. That first link seems to mainly be for firemonekey, ill check out the second link more in-depth but it does seem possible a bit of my depth. thank you – Oliver Hope Apr 27 '20 at 07:20
  • Hmmm, regarding the 1st link: yes it's Firemonkey - when using the VCL TWebBrowser, take a look at the 'OnBeforeNavigate2' event (this is somewhat the equivalent of the FMX 'OnShouldStartLoadWithRequest' event) – R. Hoek Apr 27 '20 at 07:39
  • A timer is an acceptable solution. In theory you could use `AttachEventHandler()` on some HTML element, but I don't know if that technique is usable in Delphi (see a C# example [here](https://stackoverflow.com/a/9185347/12763954)). – Olivier Apr 27 '20 at 11:37
  • @R.Hoek I have been trying to follow along with the first link( to do it 'properly'), when i tried to create the type library it didn't create a .tlb file like the article said it should, do you possible know why this is? – Oliver Hope Apr 27 '20 at 16:28
  • Thank you all for the suggestions and help, I think I will be going with my first solution as hacky as it is. With the first link, I don't know how I can change the URL because the page is locally hosted. I tried the delphidabbler article as well but it was too past my knowledge level so I couldn't solve errors I was getting. @Olivier I tried unsuccessfully, to find some information on attaching an event handler, I may try use a timer at a later stage. – Oliver Hope Apr 28 '20 at 08:45
  • @OliverHope I haven’t used typelib for al long time (and only created a few). But when I create a new typelib using Delphi I do get the tlb file.... – R. Hoek Apr 30 '20 at 06:22

1 Answers1

1

It appears that you are setting the variable gpsCoords here:

        function delphiLocate(){
        geocoder.reverseQuery(myMarker.getLatLng(), getAddress);
        gpsCoords = myMarker.getLatLng().toString();
    }

Which you access in your Delphi script after calling the delphiLocate function. Therefore the gpsCoords variable in JS should be updated. However in your Delphi script you still are accessing the doc object that you have loaded before calling delphiLocate. This object should still have the old gpsCoords.

Can you try to re-load the doc object after calling delphiLocate?

  doc := WebBrowser1.document;

  doc.parentwindow.execScript('delphiLocate()', 'JavaScript');
doc := WebBrowser1.document;
  sGpsCoords := doc.parentwindow.gpsCoords;
Moritz
  • 1,710
  • 1
  • 8
  • 13
  • This sounds like its right, and maybe it's a safer way to do it, but it doesn't seem to cause problems when I don't reload it. My problem was with the street address, not the GPS coords because the javascript has to contact map box serves and get a result, which takes time. – Oliver Hope Apr 29 '20 at 09:17