Ramblings

Delphi Programming Observations

Sunday, January 11, 2009

Google Maps in a TWebBrowser from Delphi: Directions

Screenshot

The GoogleMaps API has a GDirections object which can be used to retrieve directions between points.  Full JavaScript applications are possible, but interaction with Delphi allows further possibilities.  Previous demo programs showed how to call JavaScript from Delphi, this demo also allows the JavaScript to call Delphi functions (using JavaScript’s external object, explained later).

The demo program allows you to enter two or three addresses, and will retrieve the directions with the GoogleMaps API.  The directions will be sent to Delphi from JavaScript, and they will be displayed in a TListBox.  Clicking on an item in the TListBox will show the map “blowup” (a zoomed in box for a step, as seen in the screenshot above).

I borrowed mainly from 3 places for this demo.  Google Maps API Tutorial for some JavaScript and using GDirections, How to customise the TWebBrowser user interface and Calling Windows Client code from Javascript hosted inside the WebBrowser control which is implemented in his demo program googlemap.zip. The source code files which I did not write are included in my demo zip file in a subdirectory “Others”.

I think the JavaScript code using GDirections is pretty self explanatory. When you click the button, a callback is assigned for the “load” event, which runs through the returned Waypoints and the steps associated to the waypoints and calls the Delphi functions with external.

The TWebBrowser is customized so that the greyed-out scrollbar is not shown, since we are resizing the map to always fill the full size of the TWebBrowser, and I prefer the look without the 3D border. These are made available from the TWBContainer class in OthersUContainer.pas.

I created a class TWBGoogleMaps in uWBGoogleMaps.pas which is mostly just to keep the code for working with the TWebBrowser and GoogleMaps separate from the main program code. It sets the defaults for the scrollbar and 3D border, as well as re-implements GetExternal of the IDocHostUIHandler (which gives the TWebBrowser an IDispatch interface for use with external).

The way I understand it, when the TWebBrowser executes external in JavaScript, it asks the Host for an IDispatch with IDocHostUIHandler.GetExternal. So if your exe exposes a COM interface, then it allows the JavaScript to call Delphi functions. I prefer not to have to define an actual COM object and create a TLB, and everything that entails, so I used the Automation.pas from Steve Trefethen’s VCLMap demo (linked above as googlemap.zip). If you are using Delphi 7 or below, I believe you have to go the COM interface and TLB route as I remember reading somewhere that the TObjectDispatch doesn’t work or doesn’t exist for older versions. You can get more information for implementing the external object using a COM inferface here. My demo code works for me with Delphi 2006, Delphi 2007 and Delphi 2009.

Using Steve Trefethen’s Automation.pas simplifies things a lot. First you need to define the interface, as in what external in JavaScript is allowed to call. I created a class which defines procedures NewDirections which will clear any existing directions, AddWaypoint which sets up a new Waypoint, AddStep which adds a step to the last Waypoint added, and CopyRight which adds the directions’ CopyRight information and signals that all Waypoints and Steps have been added.

TExtJavaScript = class(TObjectWrapper)
published
   procedure NewDirections;
   procedure AddWaypoint(const aLocation, aAddress,
      aRouteDistanceHTML, aRouteDurationHTML: String);
   procedure AddStep(const aLocation, aStepDescriptionHTML,
      aStepDistanceHTML: String);

   procedure CopyRight(const aValue: String);
end;

Second, you have to return the TAutoObjectDispatch object when the TWebBrowser asks for the External IDispatch. I implemented the GetExternal in the TWBGoogleMaps class to call the OnGetExternal event, so we set that, and in the event create and return the TObjectWrapper class and the TAutoObjectDispatch:

procedure TfrmDirections.FormCreate(Sender: TObject);
begin
   fWBGoogleMaps := TWBGoogleMaps.Create(WebBrowser1);
   fWBGoogleMaps.OnGetExternal := WBOnGetExternal;
   ...
end;

// and then
function TfrmDirections.WBOnGetExternal(
   out ppDispatch: IDispatch): HResult;
var
   W: TExtJavaScript;
begin
   W := TExtJavaScript.Connect(Forms.Application);
   ppDispatch :=
      TAutoObjectDispatch.Create(W) as IDispatch;
   Result := S_OK;
end;

The rest of the code is just for demonstration purposes.

Let me know what you think.

posted by Jason at 12:04 am  

15 Responses to “Google Maps in a TWebBrowser from Delphi: Directions”

  1. wmc says:

    Hi Sir,

    Any guidnance on how to make the demos work in delphi7?

    Regards,
    Wmc

    • Jason says:

      I have never used Delphi 7, it may be time to upgrade, it’s 7 years old. I definitely missed quite a few features of the later versions (2006+) testing this.

      Anyway, for the “Directions” demo, I was able to get it working:

      1. Remove Automation.pas from the DPR USES,

      2. replace Automation in fDirections.pas USES with ObjComAuto

      3. Change TExtJavaScript to
      {$METHODINFO ON}
      TExtJavaScript = class(TObject)
      ...
      end;
      {$METHODINFO OFF}

      4. Change WBOnGetExternal to
      ppDispatch := TObjectDispatch.Create(TExtJavaScript.Create);
      Result := S_OK;

  2. Stefan says:

    Do you have any Idea how to “overlay” the Google-Map with an own Marker Set which can be selected ?
    My Idea is to Program a “Layer” Window which stays on top of the WebBrowser and redirects Key/Mouse Commands to the underlaying WebBrowser.
    Or is there a better alternative ?

    br
    Stefan

  3. luca says:

    Hi…
    Can I use the code for my own commercial projects?
    Thanks, bye luca
    PS: Automation.pas ecc…

    • Jason says:

      The files in the \Others\ directory are subject to any license and copyright their author’s have released them under. You’ll find the URL where I found the original files in a comment at the top of each file.

      For automation.pas, if you follow the links, it originally came from a blog post by Allen Bauer, and it’s on CodeCentral, so I assume you’re allowed to use it for commercial use.

      http://cc.embarcadero.com/item/24664
      “Copyright: All rights reserved
      Terms of use: Embarcadero use at your own risk disclaimer”

  4. george says:

    Hi Jason,
    I applied your concept to an app i’m making but used openstreetmap instead…would you happen to have an idea how to download the tiles directly and display them on a form?
    I idea here is to add the ability to show the map even if the user is offline

    • Jason says:

      no idea

  5. Kamen says:

    Pleace tell me where is subdirectory “Others”
    I try to compile “uGoogleMaps.pas”
    Thanks

  6. Micheal says:

    Let me start be say great example….. I added this to a project I’m currently working, However after adding it to the project the directions are no longer being created in the ListBox the map is being created just not posting the directions to the listbox. Works great by its self , just not working after adding it to the project……….To add I just added all files to my project and included the main form in the uses clause, I’m using the following to call the main form:

    procedure Temerg.GoogleMapsBTNClick(Sender: TObject);
    begin
    with TfrmDirections.Create(Self) do
    try
    ShowModal;
    finally
    Free;
    end;
    end;

    Any idea on whats being overlooked

    Thanks; Micheal

    • Jason says:

      The TExtJavaScript object was using the global form variable for the TfrmDirections, so using an unnamed object in the `with` was never setting the frmDirections var.

      I removed the dependency on the global var, so it should work now; see the updated code on github.

  7. Micheal says:

    Thanks works great……. I have the app up and running, However today i got an email saying that the default page in loading, but when They click the button to create the map, nothing happens I expect it is an issue on there side “Network Setting” do you happen to know what network and Firewall settings to look for and what required for this to work…..Thanks Again

  8. Ed Jordan says:

    Jason,

    This is very cool. Thanks for posting this.

  9. Corne Fourie says:

    First, thanks the code is fantastic, however, when I put in a second set of directions, it still shows the previous set of directions, can you please help me how to clear the previous directions so I can only see the new set of directions highlighted on the map.

    thanks

    • Jason says:

      If you call fWBGoogleMaps.ExecJS('dirn.clear();');, the previous directions will be cleared.