The Google Earth client is a technological icon from this part of our century. Google Earth is not the first Earth client and is nearly the same as its lesser known predecessor, Keyhole. However, with the Google name behind it and the basic version free to the end user, it has market penetration and recognition—an interesting target to write for.
This article has one overriding mission: to show you how truly simple it is to send information to and from a servlet via the Earth client. With this degree of interaction, you can create imaginative services by applying basic Java programming skills.
Licensing and competitionAt the time of this writing, Google Earth is in beta form (version 3.0.0616). The license (found in the client's Help section) is commercial. If you wish for an equivalent open source example, I recommend concentrating your efforts on the excellent Nasa World Wind project. |
The Google Earth client parses XML in the form of Keyhole Markup Language (KML) version 2, a proprietary namespace. Numerous KML configurations are possible to affect a GUI. The difficulty in creating a well-balanced application is associated more with knowing the KML details than the subtleties of coding. A short list of KML entities includes:
For the sake of simplicity, in this article, I focus on the use of the folder, placement, and network-link elements. Further, I define a tour as a folder that contains a series of placements.
On installation of the Google software under Windows, the file extension KML and the MIME (Multipurpose Internet Mail Extensions) type "application/keyhole" are registered. This means that the client is activated by either a click on a KML file or a receipt, through TCP/IP, of a file with the "application/keyhole" MIME type.
If the KML text returned is:
<Folder><name>Hello World [127.0.0.1] </name></Folder>
Then the program will display the following:
Figure 1. GUI representation of the Hello World folder
To activate the Earth client, simply browse to the correct URL—for example, the HelloServlet
whose source can be downloaded from Resources (http://localhost:8080/Tour/hello). Doing so activates the following doGet()
method, which redirects to doPost()
, allowing all Web browsers to view the results:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("application/keyhole");
PrintWriter out = response.getWriter();
String message ="<Folder><name>Hello World ["
+ request.getRemoteAddr()+ "]</name></Folder>";
out.println(message);
}
Do not be distracted by the code's simplicity. The method has powerful implications. A server can act as intermediary between different data types and Google Earth. One can imagine a situation where different XML dialects are involved for tour data, with the server performing Extensible Stylesheet Language transformations before passing on the finished response. Further, the server can choose which response to return, allowing for personalization. The document entity in KML allows for style definitions, and, by changing the style depending on IP address range, one can potentially differentiate between various customers.
To practice, we should begin working with Google Earth and exporting KML files. At the top of Google's program is the Add menu. From here, you may add placements, folders, and image overlays. You may then save the generated KML via the File menu. I strongly recommend editing the exported XML and seeing how your modifications affect Earth. Yes, let's play king of the world!
This section introduces an educationally orientated application: a program that teaches students names of cities and their relationships with a given land. We shall build a servlet that acts as a flashcard by sending a random city location back to the client. A placement expresses the city in KML. This placement has an embedded HTML link that points the user to a relevant and, one hopes, interesting Website. Thus, we now have user interaction involving both a Web browser and Google Earth.
The student can pull in the next placement by selecting Refresh from the menu that appears when the mouse hovers over the network link, as shown in Figure 2.
Figure 2. GUI representation of refreshing a network link to regenerate a placement (in this case, London)
The behind-the-scenes trick to our application is the use of a network-link entity. The network link loads a data file from http://location. Place the file on your desktop and double click it. Google Earth runs, and the KML fragment, shown below, loads from the server.
City.kml
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<NetworkLink>
<description>Refresh me</description>
<name>Random City</name>
<visibility>1</visibility>
<open>1</open>
<refreshVisibility>1</refreshVisibility>
<flyToView>1</flyToView>
<Url>
<href>http://location </href>
</Url>
</NetworkLink>
</kml>
The entity meanings within this configuration are:
visibility
, which defines whether the network link can be seenopen
, which explains whether the tag is to be expandedrefreshVisibility
, which defines whether the refreshed placement's visibility overrides the user's choiceflyToView
, which "flys" the user to the placement in the view window if set to 1
Many of the entities are common across most root elements—description
, for example. Note that the tag names are case sensitive, so be careful coding or you might have difficult-to-debug failures. Personally, I find the relationship between the different tag values and their influence on interactions with the GUI not always logical. Therefore, you may require play-around time in any new use of the KML fragments.
By default, the browsers Firefox, Opera, and Internet Explorer react differently to receiving a .kml extension from the Web. The most consistent method for activating the network-link file is to avoid the server for the initial KML file, and allow users to download the file onto their desktops, where they can launch it themselves by double-clicking on it. Another more adventurous approach is to place the KML file within a JSP (JavaServer Pages) page and allow the JSP page to return the "application/keyhole" MIME type followed by the relevant KML fragment. For example, the city.jsp page is simply the city.kml file, but with the content type modified and the XML Schema removed. The code starts with:
|
Back to the code, the servlet returns a placement with HTML coding in the description. Being a good XML citizen, we have placed the HTML fragment within the <!CDATA[]]>
delimiting tag, thus avoiding confusing the XML parsers:
<Placemark>
<name>London</name>
<description>
<![CDATA[<a href="http://www.visitlondon.com/choose_site/?OriginalURL=/">London</a>]]>
</description>
<address>London, UK</address>
<styleUrl>root://styleMaps#default+nicon=0x304+hicon=0x314</styleUrl>
<Point>
<coordinates>-0.1261969953775406,51.50019836425783,50</coordinates>
</Point>
</Placemark>
The three new entities seen in this placement are:
address
, a logical tag to contain the addressstyleUrl
, which defines the graphic to be displayed in this case at the placeholderPoint
/coordinates
, cylindrical coordinates of the locationThe servlet generates a random placement response via this code fragment:
manager.KMLRenderOfRandomPlacement();
Our whole application is elementary. The servlet does not keep track of state. Management class refactoring separates rendering from data management. The Manager.java
init method loads data into an array of property beans. Obviously, in a real application, where communication with a database is necessary, a persistence management framework, such as iBATIS or Hibernate, would prove useful. The placement beans model the data needed within the returned placement. The bean has the property point, which is a bean in itself. This allows later expansion of the model as the developer learns more details of KML composition and what a point can achieve within the Earth GUI.
The QuizServlet
shown below is a thin wrapper around Manager.java
; for every post or get request, the servlet returns a valid KML response.
QuizServlet.java
package test.google.earth.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.servlet.ServletConfig;
import test.google.earth.manager.Manager;
public class QuizServlet extends HttpServlet
{
private Manager manager;
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.manager= new Manager();
manager.init();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("application/keyhole");
PrintWriter out = response.getWriter();
out.println(manager.KMLRenderOfRandomPlacement());
}
}
Manager.java
package test.google.earth.manager;
import java.util.Random;
import test.google.earth.bean.PlacementBean;
import test.google.earth.bean.PointBean;
public class Manager {
private PlacementBean[] cityArray;
private String styleURL;
private String open;
private Random generator;
private int idx;
public Manager(){}
public void init(){
this.styleURL="root://styleMaps#default+nicon=0x304+hicon=0x314";
this.open="1";
this.generator = new Random();
String[] coords = {"-0.1261969953775406,51.50019836425783,50",
"12.5,41.889999,50","4.889999,52.369998,0"};
String[] name = {"London","Italy","Amsterdam"};
String[] address={"London, UK","Rome, Italy","Amsterdam, Netherlands"};
String[] description={
"<a href=\"http://www.visitlondon.com/choose_site/?OriginalURL=/\">London</a>",
"<a href=\"http://www.roma2000.it/\">Rome</a>",
"<a href=\"http://www.uva.nl/\">University of Amsterdam</a>"};
this.idx=coords.length;
cityArray= new PlacementBean[coords.length];
//Init the array of placements
for (int i =0; i<coords.length;i++){
placementBean placementBean = new PlacementBean();
placementBean.setAddress(address[i]);
placementBean.setDescription(description[i]);
placementBean.setName(name[i]);
placementBean.setOpen(open);
placementBean.setStyleURL(styleURL);
pointBean pointBean = new PointBean();
pointBean.setCoordinate(coords[i]);
placementBean.setCoordinates(pointBean);
this.cityArray[i]=placementBean;
}
}
public synchronized PlacementBean nextRandomPlacement(){
return cityArray[ generator.nextInt( this.idx )];
}
public synchronized String KMLRenderOfRandomPlacement(){
return renderKMLPlacement(nextRandomPlacement());
}
private String renderKMLPlacement(PlacementBean pBean){
String klmString="<Placemark>\n"+
"\t<name>"+pBean.getName()+"</name>\n"+
"\t<description><![CDATA["+pBean.getDescription()+"]]></description>"+
"\t<address>"+pBean.getAddress()+"</address>\n"+
"\t<styleUrl>"+pBean.getStyleURL()+"</styleUrl>\n"+
"\t<Point>\n"+
"\t\t<coordinates>"+pBean.getCoordinates().getCoordinate()+"</coordinates>\n"+
"\t</Point>\n"+
"</Placemark>\n";
return klmString;
}
}
Adding remotely served graphics to the placements is straightforward. The styleUrl
tag needs a link to the Web, e.g., http:/imageServer/image.gif. This allows the code to expand so the window's view renders a placement with an image, such as a nation's flag, in the current application.
Looking further into this methodology, one could imagine a situation where the user has the opportunity to fill in a Web form while interacting with the Google Earth client. See Figure 3 for a viable definition of such an infrastructure.
Figure 3: Potential infrastructure for a form-based tour service
An Apache Web server sits in front of two servlets. The first is the form server that returns Web forms depending on the parameters sent. The second is the tour servlet that generates a list of placements to make up one tour enclosed in a folder. The tour server calculates the image URL, but the image itself is stored statically on the filesystem to improve performance.
The collaborations are as follows:
As you can see, with imagination, creation of functional and educationally rewarding applications is viable. However, we are still missing direct feedback from the client to the servlet on a periodic basis, not just when the student refreshes the placement. The next section drills down into this issue.Bidirectional communication
In the previous code example, the network link required us to wait for a refresh action. Luckily, periodically we can get Google Earth to send via a get method the coordinates of the user position in the view window, as shown next:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Folder>
<description>Examples of bi directional flow of information</description>
<name>Network Links</name>
<visibility>1</visibility>
<open>1</open>
<NetworkLink>
<description>Lets send coordinates once in a while</description>
<name>Message Pushing</name>
<visibility>1</visibility>
<open>1</open>
<refreshVisibility>1</refreshVisibility>
<flyToView>0</flyToView>
<Url>
<href>http://localhost:8081/Tour/message</href>
<refreshInverval>2</refreshInverval>
<viewRefreshMode>onStop</viewRefreshMode>
<viewRefreshTime>1</viewRefreshTime>
</Url>
</NetworkLink>
</Folder>
</kml>
The real action occurs in the Url
entity. The viewRefreshTime
tag defines how many seconds pass before the server receives the next set of Earth coordinates. The viewRefreshMode
is set toonStop
, which implies an update of Earth coordinates when the motion in the view window stops. Figure 4 is a screen grab displaying the end effect of the above configuration.
Figure 4. GUI rendering of the network link and associated HTML
Okay, so we can get those pesky coordinates to the server. What should we do with them? Well, we can start by creating a messaging service. Figure 5 defines the two collaborations.
Figure 5. The flow of information between Earth, servlet, and the Web browser
The first passes the message and receives the coordinates through the Web browser:
Location: -0.134539,51.497,-0.117855,51.5034
IP address: 127.0.0.1
Updated: Fri Oct 21 11:42:45 CEST 2005
The second collaboration passes the coordinates and receives the placement between the servlet and Earth client:
The generated placement returned resembles the following KML:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Placemark>
<name><![CDATA[<font color="red">Alan Berg</font>]]></name> <description><![CDATA[BLAH BLAH <i> Fri Oct 21 11:42:45 CEST 2005</i>]]>
</description>
<Point>
<coordinates>4.889999,52.369998,0</coordinates>
</Point>
</Placemark>
</kml>
Next is the servlet code that orchestrates the communication:
MessageServlet.java
package test.google.earth.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import test.google.earth.bean.LastLocationBean;
import test.google.earth.bean.LastMessageBean;
import java.util.Date;
public class MessageServlet extends HttpServlet
{
private static LastMessageBean lastMessage=new LastMessageBean();
private static LastLocationBean lastLocation= new LastLocationBean();
public void init(ServletConfig config) throws ServletException {
super.init(config);
lastMessage.setMessage("No message Yet");
lastMessage.setName("System");
lastMessage.setUpdated(new Date());
lastLocation.setCoords("No contact with a client yet");
lastLocation.setIpAddress("");
lastLocation.setUpdated(new Date());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String coords = request.getParameter("BBOX");
if (coords==null){
return;
}
String message;
String name;
Date lastDate;
String ipAddress = request.getRemoteAddr();
synchronized(this) {
lastLocation.setCoords(coords);
lastLocation.setIpAddress(ipAddress);
lastLocation.setUpdated(new Date());
message=lastMessage.getMessage();
name=lastMessage.getName();
lastDate=lastMessage.getUpdated();
}
response.setContentType("application/keyhole");
PrintWriter out = response.getWriter();
String[] coParts= coords.split(",");
float userlon;
float userlat;
try{
userlon = ((Float.parseFloat(coParts[2]) - Float.parseFloat(coParts[0]))/2)+
Float.parseFloat(coParts[0]);
userlat = ((Float.parseFloat(coParts[3]) - Float.parseFloat(coParts[1]))/2) +
Float.parseFloat(coParts[1]);
}catch(NumberFormatException e){
return;
}
String klmString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<kml xmlns=\"http://earth.google.com/kml/2.0\">\n"
+ "<Placemark>\n"
+ "<name><![CDATA[<font color=\"red\">"+name+"</font>]]></name>\n"
+"<description><![CDATA["+message+"<br><i>"+lastDate+"</i>]]></description>\n"
+ "<Point>\n"
+ "<coordinates>"+userlon+","+userlat+",0</coordinates>\n"
+ "</Point>\n"
+ "</Placemark>\n"
+ "</kml>\n";
out.println(klmString);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
String name = request.getParameter("name");
if (name==null){
return;
}
String message;
PrintWriter out;
synchronized(this) {
lastMessage.setMessage(request.getParameter("message"));
lastMessage.setName(name);
lastMessage.setUpdated(new Date());
message="<pre>\nLocation: "+lastLocation.getCoords()+
"\nIP address: "+lastLocation.getIpAddress()+
"\nUpdated: "+lastLocation.getUpdated();
}
response.setContentType("text/html");
out = response.getWriter();
out.println(message);
}
}
The message from the Web browser is stored in one static member, the LastMessageBean
, and the coordinates are stored inLastLocationBean
—only one instance of each bean. Further, synchronization occurs for all the static beans while getting or setting. We use single instances for simplification purposes to help limit the amount of code to be written. However, a more practical example would require a session manager that tracks IP addresses and generates results accordingly.
A side issue is that the use of HTML tags inside the placement entity's name tag causes rendering problems. Within the Google client's "places" section, the client renders the HTML, but, in the view window, the full tag displays as text. I consider this inconsistency a bug.
From the current example, the Google client pushes coordinates. The servlet returns KML fragments. Knowing the coordinates allows the pushing of context-sensitive information. We can force interaction via links in the fragments and, if required, make the Web browser the boss. This article has shown you how to take control of the Google Earth client. You now have a conceptual toolset to build your own interactive tours.
In this article, I have only discussed the rudiments of interaction and have only scratched the surface of KML. You can add further functionality with KML by rendering 3D objects via the extrude
tag or adding pertinent information via snippets. You could use overlays to display famous paintings as users follow Napoleon's retreat from Moscow. With a little imagination and solid Java programming skills, you can build interactive Web applications with the Google Earth client. I look forward to seeing your efforts come to fruition.