Servlet-based Google Earth tours

引用
http://www.javaworld.com/javaworld/jw-11-2005/jw-1114-google.html

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 competition
At 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 basics

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:

  • Placements, which mark the location on Earth
  • Folders, which help organize the other features
  • Documents, which are containers for folders that may include style elements
  • Image overlays, which are used for adding images
  • Network links, which are a means of describing where and how to communicate with a server or, as in our situation, a servlet

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!

Learning city locations

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 seen
  • open, which explains whether the tag is to be expanded
  • refreshVisibility, which defines whether the refreshed placement's visibility overrides the user's choice
  • flyToView, 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.

Caution
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:
 <%response.setContentType("application/keyhole");%>
    <NetworkLink>


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 address
  • styleUrl, which defines the graphic to be displayed in this case at the placeholder
  • Point/coordinates, cylindrical coordinates of the location

The 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:

  1. User logs on to the form server.
  2. Server verifies user against directory service, possibly lightweight directory access protocol, and places user's IP address in a session table.
  3. Form server sends back redirect to tour server.
  4. Tour server checks that the registered user's IP address is in the session.
  5. A tour is returned based on the user history stored in the database.
  6. Google Earth zooms in on a placement and requests an image.
  7. The user clicks on a link within the placement that then triggers the form server to generate and return a form.
  8. The student fills in the form and continues with the tour.
  9. Sometime later, the student logs out. This forces the application to send an email to the relevant teacher with a professionally formatted report on the user's answers. Thus, the server has delivered the homework.

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:

  1. The Web browser sends via a post method the parameter's name and message
  2. The servlet returns the coordinates last received from the Google client as a text message similar to:

     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:

  1. For every delta T, Google Earth sends via a get method the user's coordinates within the view window.
  2. The servlet returns a message in a placement. The placement uses the coordinates to calculate roughly where to set down the returned message. Please note that I have copied the algorithm for centering from the KML tutorial.

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.

Finally

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.

About the author

Alan Berg, has been a lead developer at Central Computer Services at the University of Amsterdam for the last seven years. In his spare time, he writes computer articles. He has a bachelor's degree, two masters, and a teaching qualification. In previous incarnations, he was a technical writer, an Internet/Linux course writer, and a science teacher. He likes to get his hands dirty by building and gluing together systems. He remains agile by playing computer games with his kids, who (sadly) consistently beat him.

你可能感兴趣的:(bean,xml,Web,servlet,Google)