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.
June 25, 2011 at 8:53 am
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
June 25, 2011 at 2:53 pm
Thomas you are right, the call to the function PutMarker inside of the GotoLatLng must be removed.
August 26, 2011 at 4:17 pm
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.
August 29, 2011 at 7:12 am
Hi Bruce,
if you have problems with a download, you can send me a email to tngab(at)t-online(dot)de.
Many greatings
Thomas Nitzschke
June 30, 2011 at 9:44 am
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
July 21, 2011 at 2:29 pm
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’) ;
July 8, 2011 at 5:19 am
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.
July 16, 2011 at 8:22 am
Hi Bruce,
see my example (http://www.delphipraxis.net/1108245-post90.html).
July 21, 2011 at 9:43 am
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
July 21, 2011 at 2:30 pm
Gwenael, add more details to your question.
August 3, 2011 at 4:37 pm
Thomas,
in your example, is missing the following files
{$INCLUDE gmConfig.inc}
uses
BrowserTools, HTMLObjects, gmAPI, gmBase, gmMap, gmMarker, gmEvents;
could you post please
August 3, 2011 at 5:08 pm
Hi camilo823,
thank you for your interest!
The framework is in the first post (http://www.delphipraxis.net/157004-google-maps-ueber-com-component-object-model.html): GoogleMaps_1.1_Source&Demos.zip. There are all necessary files include (BrowserTools, HTMLObjects, gmXXX… and gmConfig.inc).
Many greetings
Thomas Nitzschke
August 9, 2011 at 2:07 pm
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
August 29, 2011 at 7:01 am
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
September 21, 2011 at 5:03 am
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!
September 21, 2011 at 1:15 pm
The IPersistStreamInit interface is declarated in the ActiveX unit in delphi, but it seems which not is included in Lazarus, so you must declare yourself using the MSDN documentation.
September 22, 2011 at 2:41 am
Thanks for the quick reply, I’ll try to look it up!
January 8, 2012 at 3:36 am
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?
February 8, 2012 at 4:44 pm
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.
February 8, 2012 at 5:08 pm
Warren, here http://neftali.clubdelphi.com/?p=1361 you can find a delphi application with source code included which load KML files in a google map, also you can check the torry page of the same author http://www.torry.net/authorsmore.php?id=6160
February 8, 2012 at 5:54 pm
Thank you!
I downloaded the project/app and tried to a) run application/exe (it would not go into full execution) and b) tried to compile using Delphi XE2. It got an error in compile; I contacted the developer. I will share good results that I might get with this exchange. In the meantime, perhaps there is another known demo app showing how to view KML files with Google Maps?
Thanks again.
Pingback: Important Note about using the Google Maps API from Desktop Apps | The Road to Delphi