The Road to Delphi

Delphi – Free Pascal – Oxygene

Using the Google Maps API V3 from Delphi – Part III Getting the latitude and longitude of a mouse click

22 Comments

In this post I will show, how you can interact with a google map embedded in a TWebbrowser component in order to get the location ( latitude and longitude) of  a point when you click in the map.

JavaScript

To get the location of the mouse when you make a click in the map you must add a  Google maps Event Listener, passing a function to process the event, the values of the current location are retrieved in the event.latLng variable , the next step is store the values returned in a hidden field element to after get these values from Delphi.

Check this sample JavaScript snippet which create an event listener and store the values in the LatValue and LngValue fields.

    google.maps.event.addListener(map, "click",
         function(event)
           {
            document.getElementById("LatValue").value = event.latLng.lat();
            document.getElementById("LngValue").value = event.latLng.lng();
            PutMarker(document.getElementById("LatValue").value, document.getElementById("LngValue").value,"")
           }
   );

This is the PutMarker function which creates a marker in the current location

function PutMarker(Lat, Lang, Msg)
{
 var latlng = new google.maps.LatLng(Lat,Lang);
 var marker = new google.maps.Marker({
     position: latlng,
     map: map,
     title: Msg+" ("+Lat+","+Lang+")"
  });

   //put the created marker in an array
   markersArray.push(marker);

   //compute the index to associate an image to the marker
   index= (markersArray.length % 10);
   if (index==0) { index=10 }
   icon = "http://www.google.com/mapfiles/kml/paddle/"+index+"-lv.png";
   marker.setIcon(icon);
 }

And this is the code to create the 2 input hidden fields in the html page to store the values returned by the Event listener

<body onload="initialize()">
  <div id="map_canvas" style="width:100%; height:100%"></div>
  <div id="latlong">
  <input id="<span class=" type="hidden" />LatValue" >
  <input id="<span class=" type="hidden" />LngValue" >
  </div>
</body>

Delphi

Now from the Delphi side, you must detect the click event in the TWebBrowser component and then read the values stored in the hidden fields. Exists several ways to detect the click in the TWebBrowser, in this case I will use the OnCommandStateChange event.

Check this code which detect the click event and then read the values stored in the hidden fields.

procedure TForm1.WebBrowser1CommandStateChange(ASender: TObject;  Command: Integer; Enable: WordBool);
var
  ADocument : IHTMLDocument2;
  ABody     : IHTMLElement2;
  Lat : string;
  Lng : string;

      //get the value from a field
      function GetIdValue(const Id : string):string;
      var
        Tag      : IHTMLElement;
        TagsList : IHTMLElementCollection;
        Index    : Integer;
      begin
        Result:='';
        TagsList := ABody.getElementsByTagName('input');
        for Index := 0 to TagsList.length-1 do
        begin
          Tag:=TagsList.item(Index, EmptyParam) As IHTMLElement;
          if CompareText(Tag.id,Id)=0 then
            Result := Tag.getAttribute('value', 0);
        end;
      end;

begin
  //is a valid command?
  if TOleEnum(Command) <> CSC_UPDATECOMMANDS then //-1
    Exit;

  //The page is loaded?
  ADocument := WebBrowser1.Document as IHTMLDocument2;
  if not Assigned(ADocument) then
    Exit;

  //the page has body?
  if not Supports(ADocument.body, IHTMLElement2, ABody) then
    exit;

  // get the values of the Latitude and Longitude
  Lat :=GetIdValue('LatValue');
  Lng :=GetIdValue('LngValue');

  //Now process the data
  if  (Lat<>'') and (Lng<>'') and ((Lat<>Latitude.Text) or (Lng<>Longitude.Text)) then
  begin
    Latitude.Text :=Lat;
    Longitude.Text:=Lng;

  end;
end;

Finally this is the full source code for the demo application

unit uMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, StdCtrls, ExtCtrls, XPMan, ComCtrls,MSHTML;

type
  TFrmMain = class(TForm)
    WebBrowser1: TWebBrowser;
    PanelHeader: TPanel;
    ButtonGotoLocation: TButton;
    XPManifest1: TXPManifest;
    LabelLatitude: TLabel;
    LabelLongitude: TLabel;
    Longitude: TEdit;
    Latitude: TEdit;
    ButtonClearMarkers: TButton;
    ListView1: TListView;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure ButtonClearMarkersClick(Sender: TObject);
    procedure WebBrowser1CommandStateChange(ASender: TObject; Command: Integer;  Enable: WordBool);
    procedure ButtonGotoLocationClick(Sender: TObject);
  private
    HTMLWindow2: IHTMLWindow2;
    procedure AddLatLngToList(const Lat,Lng:string);
  public
  end;

var
  FrmMain: TFrmMain;

implementation

{$R *.dfm}

uses
   ActiveX;

const
HTMLStr: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script type="text/javascript">// <![CDATA[
src</span>="http://maps.google.com/maps/api/js?sensor=false&language=en">
// ]]></script> '+
//'<script type="text/javascript">// <![CDATA[
src</span>="http://maps.google.com/maps/api/js?sensor=false">
// ]]></script> '+
'<script type="text/javascript"> '+
''+
''+
'  var geocoder; '+
'  var map;  '+
'  var markersArray = [];'+
''+
''+
'  function initialize() { '+
'    geocoder = new google.maps.Geocoder();'+
'    var latlng = new google.maps.LatLng(40.714776,-74.019213); '+
'    var myOptions = { '+
'      zoom: 13, '+
'      center: latlng, '+
'      mapTypeId: google.maps.MapTypeId.ROADMAP '+
'    }; '+
'    map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); '+
'    map.set("streetViewControl", false);'+
'    google.maps.event.addListener(map, "click", '+
'         function(event) '+
'                        {'+
'                         document.getElementById("LatValue").value = event.latLng.lat(); '+
'                         document.getElementById("LngValue").value = event.latLng.lng(); '+
'                         PutMarker(document.getElementById("LatValue").value, document.getElementById("LngValue").value,"") '+
'                        } '+
'   ); '+
''+
'  } '+
''+
''+
'  function GotoLatLng(Lat, Lang) { '+
'   var latlng = new google.maps.LatLng(Lat,Lang);'+
'   map.setCenter(latlng);'+
'  }'+
''+
''+
'function ClearMarkers() {  '+
'  if (markersArray) {        '+
'    for (i in markersArray) {  '+
'      markersArray[i].setMap(null); '+
'    } '+
'  } '+
'}  '+
''+
'  function PutMarker(Lat, Lang, Msg) { '+
'   var latlng = new google.maps.LatLng(Lat,Lang);'+
'   var marker = new google.maps.Marker({'+
'      position: latlng, '+
'      map: map,'+
'      title: Msg+" ("+Lat+","+Lang+")"'+
'  });'+
'  markersArray.push(marker); '+
'  index= (markersArray.length % 10);'+
'  if (index==0) { index=10 } '+
'  icon = "http://www.google.com/mapfiles/kml/paddle/"+index+"-lv.png"; '+
'  marker.setIcon(icon); '+
'  }'+
''+
''+
''+'</script> '+
'</head> '+
''+
'<body onload="initialize()"> '+
'  <div id="map_canvas" style="width:100%; height:100%"></div> '+
'  <div id="latlong"> '+
'  <input type="hidden" id="LatValue" >'+
'  <input type="hidden" id="LngValue" >'+
'  </div>  '+
''+
'</body> '+
'</html> ';

procedure TFrmMain.FormCreate(Sender: TObject);
var
  aStream     : TMemoryStream;
begin
   WebBrowser1.Navigate('about:blank');
    if Assigned(WebBrowser1.Document) then
    begin
      aStream := TMemoryStream.Create;
      try
         aStream.WriteBuffer(Pointer(HTMLStr)^, Length(HTMLStr));
         //aStream.Write(HTMLStr[1], Length(HTMLStr));
         aStream.Seek(0, soFromBeginning);
         (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(aStream));
      finally
         aStream.Free;
      end;
      HTMLWindow2 := (WebBrowser1.Document as IHTMLDocument2).parentWindow;
    end;
end;

procedure TFrmMain.WebBrowser1CommandStateChange(ASender: TObject;  Command: Integer; Enable: WordBool);
var
  ADocument : IHTMLDocument2;
  ABody     : IHTMLElement2;
  Lat : string;
  Lng : string;

      function GetIdValue(const Id : string):string;
      var
        Tag      : IHTMLElement;
        TagsList : IHTMLElementCollection;
        Index    : Integer;
      begin
        Result:='';
        TagsList := ABody.getElementsByTagName('input');
        for Index := 0 to TagsList.length-1 do
        begin
          Tag:=TagsList.item(Index, EmptyParam) As IHTMLElement;
          if CompareText(Tag.id,Id)=0 then
            Result := Tag.getAttribute('value', 0);
        end;
      end;

begin
  if TOleEnum(Command) <> CSC_UPDATECOMMANDS then
    Exit;

  ADocument := WebBrowser1.Document as IHTMLDocument2;
  if not Assigned(ADocument) then
    Exit;

  if not Supports(ADocument.body, IHTMLElement2, ABody) then
    exit;

  Lat :=GetIdValue('LatValue');
  Lng :=GetIdValue('LngValue');
  if  (Lat<>'') and (Lng<>'') and ((Lat<>Latitude.Text) or (Lng<>Longitude.Text)) then
  begin
    Latitude.Text :=Lat;
    Longitude.Text:=Lng;
    AddLatLngToList(Lat, Lng);
  end;
end;

procedure TFrmMain.AddLatLngToList(const Lat, Lng: string);
var
  Item  : TListItem;
begin
   if (Lat<>'') and (Lng<>'') then
   begin
     Item:=ListView1.Items.Add;
     Item.Caption:=Lng;
     Item.SubItems.Add(Lat);
     Item.MakeVisible(False);
   end;
end;

procedure TFrmMain.ButtonClearMarkersClick(Sender: TObject);
begin
  HTMLWindow2.execScript('ClearMarkers()', 'JavaScript');
  ListView1.Items.Clear;
end;

procedure TFrmMain.ButtonGotoLocationClick(Sender: TObject);
begin
  if Assigned(ListView1.Selected) then
    HTMLWindow2.execScript(Format('GotoLatLng(%s,%s)',[ListView1.Selected.SubItems[0],ListView1.Selected.Caption]), 'JavaScript');
end;

end.

Check the source code on Github.

Author: Rodrigo

Just another Delphi guy.

22 thoughts on “Using the Google Maps API V3 from Delphi – Part III Getting the latitude and longitude of a mouse click

  1. Thank you for this tutorial!

    But I have a question:
    079 ‘ function GotoLatLng(Lat, Lang) { ‘+
    080 ‘ var latlng = new google.maps.LatLng(Lat,Lang);’+
    081 ‘ map.setCenter(latlng);’+
    082 ‘ PutMarker(Lat, Lang, Lat+”,”+Lang);’+ //<-???
    083 ' }'
    Is this a mistake? This code creates a new marker on the old one when the button "Go to Location" is pressed. Or is it about you wanted?

    I've translated your demo with my framework (http://www.delphipraxis.net/1108245-post90.html). Additionally show the TEdit components "Latitude" and "Longitude" the current cursor position.

    Many greetings
    Thomas Nitzschke

    • Thomas you are right, the call to the function PutMarker inside of the GotoLatLng must be removed.

    • Hi Thomas

      Sorry for the strange posting but I can’t for some reason download any of your Google Map examples on the Delphi Praxis site even after creating an account. Every time I click one of the demo zip files the site refuses to let me download.

      I can’t seem to find your contact email so I decided to see if this will work.

      Bruce.

  2. Nice tutorial Rodrigo.
    Got a question on the direction function from another script u do.

    I got this in my direction.html :

    function calcRoute(adresseDep, adresseArr) {
    var start = adresseDep ;
    var end = adresseArr ;
    var request = {
    origin:start,
    destination:end,
    travelMode: google.maps.DirectionsTravelMode.DRIVING
    };
    directionsService.route(request, function(response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
    directionsDisplay.setDirections(response);
    distance = “The distance between the two points on the chosen route is: “+response.routes[0].legs[0].distance.text;
    distance += “The aproximative driving time is: “+response.routes[0].legs[0].duration.text;
    alert(distance) ;
    }
    });
    }

    and i want to launch it from Delphi with this :
    ExecScript(Format(‘calcRoute(%s,%s)’, [‘rue de freville, paris’, ‘rue de rennes, nantes’]), ‘Javascript’) ;

    But that don’t work … any solutions ???

    I also tried to get some values from the javacsript, but i don’t know how to get them in a delphi variable.

    Gwenael

    • I don’t know what you mean with not work , I need more details, Anyway try change this line
      ExecScript(Format(‘calcRoute(%s,%s)’, [‘rue de freville, paris’, ‘rue de rennes, nantes’]), ‘Javascript’) ;

      to

      ExecScript(Format(‘calcRoute(%s,%s)’, [QuotedStr(‘rue de freville, paris’),QuotedStr( ‘rue de rennes, nantes’)]), ‘Javascript’) ;

  3. Rodrigo

    Is there anyway of putting a listener on the maps ‘mousemove’ event and then picking up the values of the lat/long and displaying them in an edit box so as the user moves the pointer the values are continually updated?

    In a similar way could you detect a click on a marker in the map by adding a listener?

    Bruce.

  4. Hi All,
    Firstly thanks to all of u for ur help, i managed to do all i wanted under GoogleMap.
    All, not really, just need a last help from u : i need to make a Delphi TEdit text that would show the autocomplete options from Google Maps.

    Means when i’ll get values in my TEdit, i’ll have an auto-completion from Google Maps which will give me all near’name values. Hope u’ll understand my waitings.

    Any help will be welcome. Gwenael

  5. Thomas,

    in your example, is missing the following files

    {$INCLUDE gmConfig.inc}

    uses
    BrowserTools, HTMLObjects, gmAPI, gmBase, gmMap, gmMarker, gmEvents;

    could you post please

  6. Thank you for the framework, very good job.
    There is a way to clean all the markers (custom marker demo) without refresh all page? (on a timer event for example…)
    I intend to use this framework to animate markers (bus on routes with a gps tracker)
    Ideas are welcome…

    Sandro Adad
    Radsystem Des. Sistemas – Brazil

    • Hi Sandro,

      mean you Rodrigo or me?
      If you mean my framework: Thank you and sorry for the late reply! But here is Rodrigo’s blog and I don’t read it every day. If there are questions about the framework, it is better to put them in the forum Delphi-Praxis (http://www.delphipraxis.net). This forum is in German, but you can also write in English.

      At the moment I have unfortunately no time for a own homepage and therefore I host the project in this forum.

      To your question: Marker.Map:=nil remove a marker from map.
      All markers can be deleted, for example, in this way:
      with Script do
      begin
      while Markers.Count>0 do
      begin
      Markers[0].Map:=nil; //remove marker from map
      Markers.Delete(0); //delete Delphi wrapper object
      end;
      end;

      The weather-demo (http://www.delphipraxis.net/1119316-post108.html) demonstrated the deletion of markers (Button Clear).

      But you can also change the position of a marker:
      Marker.Position:=Script.Google.Maps.LatLng(NewLat,NewLng);

      Many greetings
      Thomas Nitzschke

  7. Thank you for the very informative code!
    I’m trying to port this Lazarus/FreePascal and it seems that I’ve hit a roadblock: I can’t find a way to load the stream to the browser. Assuming a win32 only environment, one can call easily IE in Lazarus like this:

    procedure TForm1.FormShow(Sender: TObject);
    var
    Browser: OleVariant;
    begin
    Browser := CreateOleObject('InternetExplorer.Application');
    Windows.SetParent(Browser.hwnd, Form1.Handle);
    Browser.Toolbar := False;
    Browser.Fullscreen := True;
    Browser.Resizable := False;
    Browser.Visible := True;
    Browser.Navigate('http://www.google.com');
    end;

    Now I’m trying to add your code:

    procedure TForm1.FormShow(Sender: TObject);
    var
    Browser: OleVariant;
    aStream: TMemoryStream;
    begin
    aStream := TMemoryStream.Create;
    try
    aStream.WriteBuffer(Pointer(HTMLStr)^, Length(HTMLStr));
    aStream.Seek(0, soFromBeginning);
    aStream.SaveToFile(tmpFile);
    (Browser.Document as IPersistStreamInit).Load(TStreamAdapter.Create(aStream));
    finally
    aStream.Free;
    end;

    Browser := CreateOleObject('InternetExplorer.Application');
    Windows.SetParent(Browser.hwnd, Form1.Handle);
    Browser.Toolbar := False;
    Browser.Fullscreen := True;
    Browser.Resizable := False;
    Browser.Visible := True;
    Browser.Navigate('http://www.google.com');
    end;

    But the problem with the above is that there is no IPersistStreamInit interface in Lazarus.
    Does anyone know a workaround to this?
    Any help would be very appreciated!

  8. I have tried to combine the two examples (I and III) in this excellent contribution of code.
    My aim is to
    1) put in an address (like in example I)
    2) get the lat and lng values from the marker
    3) write these lat and lng values in a Delphi program’s memo together with the address

    How do I get the coordinates (lat, lng) of the marker and put it into my Delphiprogram?

  9. How to add a KML file to the Google Maps API V3 from Delphi project?

    I want to view an existing multi-polygon KML file as an overlay on the Google maps (in a desktop Delphi app).

    Thank you.

  10. Pingback: Important Note about using the Google Maps API from Desktop Apps | The Road to Delphi

Leave a comment