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;
}
}

Wednesday, December 3, 2008

Map Control First Look

Last night, I got to wondering how difficult it would be to slap together a map control for my phone. The goal? To host up some NLT Landsat 7 tiles from my PC and be able to browse them seamlessly from my phone, using the stylus. The Platform is my TMobile Wing, with Windows Mobile 6 Pro running CE OS 5.2. My language of choice is C# and using the Windows Mobile 6 SDK within Visual Studio 2005.

My first goal was to load up a reasonably high-res blue marble image (1024x512) and hack out some code to handle the mouse events. I soon after started tinkering a bit with the panning of the image to see what type of behavior I would receive from using managed code on the platform. Along the way, I started to get some ideas for how I could have the UI behave in the longer term. I started asking myself some reasonably simple questions, such as:

.. should the app run full screen or not?
.... are the standard menu buttons at the bottom of the screen necessary?
...... what should my mouse clicks Do?
........ with respects to mouse clicks, what is the nicest way to zoom?

I was able to work out a small demo, nothing spectacular but i'll probably spend some time building off of it this week. I will probably spend a bit of time tomorrow night working on the network code, request buffering, etc... For now, enjoy this small demo of how it currently acts.



Some things to take note of:

- The behavior when sporadically panning the image is pretty good
- You can see some artifact along the outskirts of the image, although you'd never see this once the outskirt data is being buffered in
- There's an annoying "jump" glitch, which I will clean up tomorrow :-)

Friday, November 21, 2008

Lassen DR+GPS J-Track Ramblings

My Lassen DR+GPS unit is currently sitting ever-so-gently on my desk. I have the antenna propped between my blinds and the antenna is pointed out the window facing towards the north eastern skies. The software comes with a sky plotter (see the snapshot below). Tonight, I became interested in seeing how well aligned the output would be with that of JTrack, so I spent a bit of time eyeballing the SV plots. This was out of curiosity more than anything. Of course one curiosity leads to another and curiosity got the best of me this evening. I started to wonder about where JTrack 3D gets its data. Unfortunately, it's not open source so there is no digging into the code for me tonight. Fortunately, there is Wireshark. A preliminary sniff while running JTrack3d resulted in a couple of http gets for a jar, some dll and a couple of text files; jtrack3dvectors.txt and codes.txt. I got to thinking it would be reasonably interesting to plot these in Google Earth. Perhaps I will hack at that a bit this weekend.