1
votes

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>
1
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
@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
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
@OliverHope if you want something like events maybe this thread can help javascript.developreference.com/article/20717415/… - R. Hoek

1 Answers

0
votes

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;