Saturday, December 6, 2008

Embedding Google Earth in a C# Application

Note: I have received some complains about the formatting, so you can see an alternative post here at codeproject.

If you're interested in utilizing the Google Earth viewer as a control in a custom application, then continue reading. There are quite a few links around the internets that provide information as to how to add references to your project, create a ApplicationGE and get controlling the Google Earth application. However, most of these tutorials do not focus on the details of embedding Google Earth in an application. That's what I will focus on here. From now on GE means Google Earth.

You'll probably start off on your embeddi
ng adventure much like I did, with the good-ole:
EARTHLib.ApplicationGE ge = new ApplicationGEClass();
If the GE process already existed, we will obtain a reference to it, if not a new process will be created and you would see the GE logo flash on the screen while loading. You will then see the main Google Earth screen with the embedded viewer. Since we are interested in embedding GE, we are not interested in having the main screen around, so we hide it!

ShowWindowAsync(ge.GetMainHwnd(), 0);
For all intents and purposes, we now have an empty screen. What we'd like to do next is embed the scene that GE renders into our application, we accomplish this by setting the render hwnds parent to that of the window we would like to render to:
SetParent(ge.GetRenderHwnd(), this.Handle.ToInt32());
In the example above, 'this' is a Windows Form. The end result if you perform these same steps should look similar to the image below, although I do warn that results may vary :-)


You will notice that resizing the window has no effect on the scene. If you plan on embedding GE in an application for any useful purposes, you'll most likely need it to respond to resizing. I spent a bit of time sniffing into the events that were passed to the scene when I performed re-size of the main GE application window. This led me to one special event WM_QT_PAINT in addition to a sequence of others. It took a bit of tinkering to get it all right but this appeared to work for me.
SendMessage(ge.GetMainHwnd(), WM_COMMAND, WM_PAINT, 0);
PostMessage(ge.GetMainHwnd(), WM_QT_PAINT, 0, 0);
SetWindowPos( ge.GetMainHwnd(), HWND_TOP, 0, 0,(int)this.Width, (int)this.Height, SWP_FRAMECHANGED);
SendMessage(ge.GetRenderHwnd(),
WM_COMMAND, WM_SIZE, 0);
This blip should allow the scene to adjust according to the parent forms size. I bundled this up into a method named "ResizeGoogleControl" and called it after SetParent and within my Form_Resize event. The results are illustrated in this image:

I slapped together a test app that you are welcome to use for reference. If you have any comments or suggestions, be sure to send me an email or add a comment. Create a new C# project in Visual Studio and replace the Form1.cs code with this:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;


using EARTHLib;


namespace resize_google_earth
{
public partial class Form1 : Form
{
[DllImport("user32.dll")]
private static extern int SetParent(
int hWndChild,
int hWndParent);


[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(
int hWnd,
int nCmdShow);


[DllImport("user32.dll", SetLastError = true)]
private static extern bool PostMessage(
int hWnd,
uint Msg,
int wParam,
int lParam);


[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
private static extern bool SetWindowPos(
int hWnd,
int hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags);


[DllImport("user32.dll")]
private static extern int SendMessage(
int hWnd,
uint Msg,
int wParam,
int lParam);


private const int HWND_TOP = 0x0;
private const int WM_COMMAND = 0x0112;
private const int WM_QT_PAINT = 0xC2DC;
private const int WM_PAINT = 0x000F;
private const int WM_SIZE = 0x0005;
private const int SWP_FRAMECHANGED = 0x0020;


public Form1()
{
InitializeComponent();


ge = new ApplicationGEClass();


ShowWindowAsync(ge.GetMainHwnd(), 0);
SetParent(ge.GetRenderHwnd(), this.Handle.ToInt32());
ResizeGoogleControl();
}


private void Form1_Resize(object sender, EventArgs e)
{
ResizeGoogleControl();
}


private void ResizeGoogleControl()
{
SendMessage(ge.GetMainHwnd(), WM_COMMAND, WM_PAINT, 0);
PostMessage(ge.GetMainHwnd(), WM_QT_PAINT, 0, 0);


SetWindowPos(
ge.GetMainHwnd(),
HWND_TOP,
0,
0,
(int)this.Width,
(int)this.Height,
SWP_FRAMECHANGED);


SendMessage(ge.GetRenderHwnd(), WM_COMMAND, WM_SIZE, 0);
}


private EARTHLib.ApplicationGE ge = null;
}
}

11 comments:

nvalwaikar said...

hi,
first of all very much thanks for the idea of embedding GE into an windows application.
Using your C# code i was able to do the same thing on VB.Net.
But now i need to do some more with it like Add placeMark wherever the user clicks on the renderer screen. Also i would like to know whether i would be able to save these placemarks to a KML file.
Thanks in Advance.

Cristian said...

Hola como estas mi nombre es Cristian, soy de Argentina. Me gustarĂ­a saber como puedo yo asignarle la latitud y longitud donde quiero que se sitĂșe el mapa y como agregar una imagen sobre ese mapa, como una especie de auto o algo.
Muchas Gracias.
duenderata@gmail.com

Adriano said...

Hi there,
by shure I missed the first master step.
How can I issue the
"ge = new ApplicationGEClass()"
sentence?
I think I need to link (and download) some class library.
Is it?
Thanks,
adriano

Lucia said...

Hi Joseph, I'm a student and I'm writing my thesis.

As I need to use GE with c#, I'm trying your example, but I cannot visualize the controls on a right side.
Why??

Another thing if u can help me: in my application I need to draw a rectangle and get the coordinates, or at least clicking in a point and get the coordinates.
Can you help me in write this part of code??

I read the GE aPI examples here: http://earth-api-samples.googlecode.com/svn/trunk/demos/interactive/index.html but how can I to "translate" the examples in my c# application???

Many thanks in advance,

Anto

Fraser said...

Nice blog and very cool post. I too have been playing with this theme.

http://fraserchapman.blogspot.com/2008/08/google-earth-plug-in-and-c.html

and

http://code.google.com/p/winforms-geplugin-control-library/

Phuong said...

Dear Joseph,

Just one question! I have integrated success GE Render Window to my Sharpmap Application. But the GE Window is always on top, so I can not see the vector layers. How can I send GE window back, and send vector layer front?

Thanks !

Phuong said...

Dear Joseph,

Just one question! I have integrated success GE Render Window to my Sharpmap Application. But the GE Window is always on top, so I can not see the vector layers. How can I send GE window back, and send vector layer front?

Thanks !

Jeff said...

Joe, nice work, esp with finding the mystery QT_PAINT event

For those who stumble here that use python, I am making notes about using Joe's code with wxPython, see http://naicharred.blogspot.com/

-jeff

Dustin said...

Great job on getting a working control. I am having one issue though: the Google Earth Application View does not take up the entire form and leaves a gap underneath and to the right of the viewer. Do you know of a workaround for something like this? Thanks

vfdvgf said...

if wow gold and maple story mesos wow gold

Ivan said...

Hi, I think you have done a great job doing this code, however i would like to know if is there any way to change this code to be compatible with Visual Basic 6.0? Hope you can help me. My best wishes
Ivan