基于Servlet的Google Earth之旅

google|servlet

  从这一点来说Google Earth客户端是我们时代的技术标志。Google Earth并非是第一个地球浏览客户端,而且与它的先驱、不为人知的Keyhole非常相似。但是凭着Google的大名以及基础版对最终用户免费,它完成了市场渗透并得到公认――这是另一个值得大书特书的有趣话题。

  本文只有一个基本使命:即向你展示在servlet和Google Earth客户端之间发送和接收信息是多么的容易。有了这种程度的交互,你就能用基本的Java编程技能创建设想的服务。

使用许可及竞争者

  截至本文发稿时Google Earth还处于beta阶段(版本号3.0.0616),许可证是商业的(见客户端的帮助部分)。如果你想寻求等价的开源范例,我建议你去关注优秀的Nasa World Wind(Nasa世界风)项目

基础知识

  Google Earth客户端以第二版的锁位标记语言(KML)解析XML数据,它有一个专用的命名空间。庞大的KML配置信息可能会影响到GUI显示,开发这种需要平衡利弊的应用的难点在于需要了解更多的KML细节而不是编程技巧。KML实体的简要列表包括:
*Placements(位置),标明在地球上的坐标
*Folders(夹子),帮助组织其它的特征信息
*Documents(文档),存放可能包含风格元素的folder的容器
*Image overlays(图片叠加),用来添加图片
*Network links(网络链接),描述在何处以及如何与服务器或者servlet(本文采用的方式)连接

  本文为了简化的目的,主要探讨了folder、placement和network-link元素的使用;此外还用folder定义了一段旅程(tour),它里面包含了一系列的placement。

  在Windows上安装了Google Earth后,文件扩展名KML和MIME(Multipurpose Internet Mail Extensions,多用途网络邮件扩展)类型“application/keyhole”即被注册。这意味着只要点击KML文件或通过TCP/IP接收“application/keyhole”MIME类型的文件就会激活Google Earth客户端。

  如果返回的KML文本为:
<Folder><name>Hello World [127.0.0.1] </name></Folder>

  则程序将显示如下内容:


图1 Hello World folder的GUI显示

  要想激活Earth客户端,只需浏览适当的URL地址--就好比从资源地址(http://localhost:8080/Tour/hello)下载HelloServlet源程序。这样就能激活doGet()方法,然后重定向到doPost()方法,在所有的Web浏览器里都会看到以下结果:
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);
}
  不要小看这段简单的代码,里面的方法暗藏着玄机。服务器可以作为各种数据类型和Google Earth之间的中介。不妨设想像这样一个场景:在旅程数据中包含有不同的XML方言,在返回响应前由服务器完成扩展风格语言(Extensible Stylesheet Language)的转换。再进一步,服务器可以选择返回哪一种响应,以允许个性化处理。KML文档实体允许风格定义,可根据IP地址范围改变风格,使得不同的用户看到的风格可能会不一样。

  作为实践,我们将从使用Google Earth和输出KML文件开始。在Google Earth的顶部是Add菜单,可以在这里添加placement、folder和image overlay,然后用File菜单保存生成的KML文件。我强烈推荐编辑导出的XML文件以了解改动对Google Earth的影响。好了,让我们开始与这位世界之王共舞!

了解城市定位

  本节给出一个面向教学的应用:一个用来教授学生城市名称与地理位置间关系的程序。我们将创建一个以类似于抽签的方式将城市位置随机发送给客户端的servlet。城市的位置(placement)用KML表示。Placement实体里封装了HTML链接,将用户引导到相关的有趣站点。这样我们就可以使用户在Web浏览器和Google Earth间进行交互。

  学生可以通过在鼠标置于链接之上时出现的菜单中选择Refresh来选择下一个placement,如图2所示。


图2 刷新网络链接生成一个新位置(在这里是伦敦)时的GUI显示

  我们这个应用的后台处理用到了network-link(网络链接)实体,network-link从http://location加载数据文件。将此文件存于桌面并双击,Google Earth开始运行,并从服务器端加载下面的KML代码段。
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>

该配置中的实体含义为:
*visibility(可见性),定义了网络链接是否可见
*open(展开),说明是否展开标签
*refreshVisibility(刷新可见性),定义是否取代用户对刷新位置可见性的设定
*flyToView(巡视),如果设为1,用户可以在View窗口“飞越”位置上空

  许多实体通常都可以跨根元素使用(如description)。注意标签名是大小写敏感的,所以编码时要小心以免出现难以排查的错误。在我看来,各标签值与它们对GUI的交互作用关系并不总是符合逻辑的,因此你可能对任何新的KML代码段的运用都需要花些时间。

注意
  在默认情况下,Firefox、Opera和IE浏览器对于从Web上接收的扩展名为kml的文件反应是不同的。激活网络链接文件最通用的方法是避免服务器将KML文件初始化,并允许用户将文件下载到桌面,这样就能通过双击来启动它们。另一种更好的方法是将KML文件嵌入到JSP(JavaServer Pages)页面里并允许JSP页面返回“application/keyhole”MIME类型的KML代码段。假使对内容类型做修改并去掉XML模式,city.jsp就成了city.kml文件。
该代码的开头为:
<%response.setContentType("application/keyhole");%>
    <NetworkLink>  回到前面的代码,servlet返回了一个在description元素中带有HTML代码的placement。为遵守XML规范,我们将HTML代码段放入<!CDATA[]]>分割标签中,以避免使XML解析器混淆:

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

  在placement里出现了三个新实体:
*address(地址),包含地址的逻辑标签
*styleUrl,定义在此处要显示的图片
*Point/coordinates(点/坐标),位置的柱面坐标

  Servlet通过以下代码生成一个随机的placement响应:
manager.KMLRenderOfRandomPlacement();
  我们的整个应用都是最基础的,servlet没有保持跟踪状态。Management类根据数据的组织重画各个窗口。Manager.java的init方法将数据加载到property bean数组中。显然,真实的应用需要与数据库通信,象iBATIS或Hibernate这样的持久层管理框架将会很有用。placement bean用来为返回的placement准备数据,该bean有一个代表其自身的属性点。当开发者对KML编程的细节以及如何到达Google Earth GUI中的某个点有了更多的了解之后,就可以对此模型进行扩充。

  下面的QuizServlet是对Manager.java的轻量封装,该servlet对每个post或get请求都返回一个有效的KML响应。
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;
   }
}

  为了直接将远程服务器上的图片加到placement上,styleUrl标签需要一个指向Web的链接(如http:/imageServer/image.gif),这就使代码能在View窗口的placement处填充一个图片(在本应用中是一个国旗)。
  对此方法做进一步研究,就可以设计出一个场景:用户在与Google Earth客户端交互的同时还能填写Web表单。图3给出了这一基本构思的示意图。

做进一步研究,就可以设计出一个场景:用户在与Google Earth客户端交互的同时还能填写Web表单。图3给出了这一基本构思的示意图。


图3 基于表单的旅行服务的潜在基本构思

  在两个servlet服务器的前端是Apache Web服务器。第一个是表单服务器,根据发送的参数返回Web表单;第二个是旅程服务器,生成placement列表封装在folder中成为一个旅程。旅程服务器处理图片的URL,图片本身以静态方式存储于文件系统中以改善性能。

  互动流程如下:
1.        用户登录到表单服务器。
2.        服务器通过目录服务(可以是轻量目录访问服务)验证用户身份,并将用户的IP地址存入一个会话表中。
3.        表单服务器重定向到旅程服务器。
4.        旅程服务器检查正在会话中的已注册用户的IP地址。
5.        根据存储在数据库中的用户历史信息返回一个旅程。
6.        Google Earth聚焦到一个位置(placement)并请求一张图片。
7.        用户点击placement中的一个链接,触发表单服务器生成并返回一个表单。
8.        学生填写表单,然后继续旅行。
9.        如此几番后,学生退出会话,引发应用向相关教师发送一个将学生的回答转化为专用格式报告的email,至此服务器完成了作业的交付。

  由此可见,基于上述构想创建一个具备功能性和教育性的应用是可能的。然而,我们还不能以定期的方式直接从客户端向servlet反馈信息,除非学生对位置进行刷新。在下一部分我们将深入探讨这一问题。

双向交流

  在上面的代码示例中,网络链接需要等待我们的刷新操作。幸运的是,我们可以让Google Earth以get方法定期地发送View窗口中用户的位置,如下所示:
<?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>
  实际的动作由Url实体完成。viewRefreshTime标签定义了经过多少秒服务器接收下一套Earth坐标,viewRefreshMode标签设置为onStop就意味着当停止在View窗口里移动时更新Earth坐标。图4是上述配置最终效果的一个截图。


图4 网络链接和关联HTML的GUI显示

  好了,我们可以把那些讨厌的坐标发给服务器了。我们可以用它来做什么呢?让我们从创建一个消息服务开始。图5给出了两个流程。


图5 Google Earth、servlet和浏览器之间的信息流

  首先,通过浏览器发送消息并接收坐标:
1.        浏览器以post方法发送参数名和消息
2.        serlet以类似于以下形式的文本消息返回从Google Earth客户端收到的最后坐标:
Location: -0.134539,51.497,-0.117855,51.5034
IP address: 127.0.0.1
Updated: Fri Oct 21 11:42:45 CEST 2005
  其次,在servlet与Google Earth客户端之间传递坐标并接收位置(placement):
1.        每经过ΔT时间,Google Earth通过get方法发送用户在View窗口中的坐标。
2.        servlet把消息放在placement中返回,placement通过坐标来粗略计算在何处放置返回的消息。请注意我已从KML教程中将对中算法拷贝过来。

  返回生成的位置(placement)类似于下面的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>

 以下就是构成这一协奏乐章的servlet代码:

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);
   }
}  
  来自浏览器的消息保存在静态成员LastMessageBean中,坐标保存在LastLocationBean中,且每个bean都只有一个实例。此外,在执行getting或setting操作时对所有的静态bean都进行同步。我们用单个实例来达到简化的目的,有助于限制要编写的代码数量。然而,更有实用价值的示例应具备跟踪IP地址的会话管理功能并生成相应的处理结果。

  有一个不起眼的错误,在placement实体的名字标签里使用HTML标签会导致显示问题:整个标签在Google Earth客户端的“places”菜单区按HTML显示,但在View窗口里却按文本显示。我认为这种不一致是个bug。

  在本示例中Google Earth客户端推送坐标,servlet返回KML代码段。既然知道能用坐标推送上下文关联信息,我们可以强制通过段中的链接来进行交互,必要的话还可以让浏览器成为宿主。本文展示了如何控制Google Earth客户端,至此你已拥有了一个创建自己互动旅程的概念性工具箱。

时间: 2024-08-03 09:46:14

基于Servlet的Google Earth之旅的相关文章

基于google map api开发web和google earth的KML地标插件

KML文件很好做,在你的google earth(以下简称GE)中选择你自己的或是别人的marker或layer,就可以保存成静态的地标或图层信息(kmz或kml格式),发送给你的朋友,用他的GE打开你的文件就可以看到你所标注的marker信息. 这些相关的文章让我迷失了好一段时间,不过,我想要的是动态的地标.类似于EEMAP那样的GE插件.下面我将讲述它的工作原理和制做方法. 先说几个概念,google map api是基于javascript+xml等技术实现的,另一款产品google ea

Google Earth 5.0新版带你自由畅游 支持简体中文!

&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp;   Google地球(Google Earth)是一款Google公司开发的虚拟地球仪软件, 它把卫星照片.航空照相和GIS布置在一个地球的三维模型上. Google Earth整合Google的本地搜索以及驾车指南两项服务,能够鸟瞰世界,将取代目前的桌面搜索软件.他可以在虚拟世界中如同一只雄鹰在大峡谷中自由飞翔,登陆峡谷顶峰,潜入峡谷深渊. Google今天放出了

EEmap:用Google Earth周游世界必备的中文导航

国内著名互动http://www.aliyun.com/zixun/aggregation/30472.html">电子地图服务商 EEmap近日开放了一个Google earth 中文导航插件:EEmap.kml. Google Earth目前没有 中文版本,也没有开放中文导航数据.EEmap.kml插件提供翔实的中国区域的地理数据(已经到镇乡级,部分到村级)和国际大城市的中文数据,极大的方便了中文用户. EEmap.kml是一个基于eemap.org中文数据的动态地标文件,简单实现了W

Google Earth重现古罗马历史建筑

Google地球(Google Earth)是一款Google公司开发的虚拟地球仪软件, 它把卫星照片.航空照相和GIS布置在一个地球的三维模型上. Google Earth于2005年向全球推出,被"PC 世界杂志"评为2005年全球100种最佳新产品之一.用户们可以通过一个下载到自己电脑上的http://www.aliyun.com/zixun/aggregation/5218.html">客户端软件,免费浏览全球各地的高清晰度卫星图片. Google Earth使

基于PHP的Google Voice 短信API

Google Voice并没有提供官方API接口,但是实际上还是可以通过HTTP和XML请求的手段来实现.目前网上能够找到的API最终大都是溯源到Chad Smith的这篇主题帖. 要通过Google Voice实现发送短信,首先要登录到Google Voice账号,然后取出页面中的"_rnr_se"参数,最后把这个参数和对方手机号.短信内容POST到Google Voice SMS的地址即可.Tyler Hall曾经写过一个Google Voice的php类,但是我试用时并不成功,总

C#调用Google Earth Com API开发(四)

本篇继续介绍Google Earth COM API开发的基础知识,相对第三篇的改进如下: 1)增加鼠标滚轮支持,可以实现放大.缩小.此功能利用上一篇提供的HookAPI.dll实现 2)读取PlaceMarks(Google Earth界面中的位置)并显示.隐藏 3)读取所有图层,显示并隐藏 下面,继续放代码: 1.鼠标滚轮事件,实现放大.缩小 1: ... 2: // 放大 3: private const long ZoomIn = 0x00780000; 4: // 缩小 5: priv

C#调用Google Earth Com API开发(三)

好久没有更新<C#调用Google Earth Com API开发>系列文章了,今天带给大家的是第三篇,本篇相 对于第二篇主要改进了三个方面. 1) 实现GoogleEarth显示画面随窗口大小改变而改变 2) 截获GoogleEarth鼠标消息,实现单击.双击功能:鼠标滚轮缩放现在只能放大!O(∩_∩)O~ 3) 实现GoogleEarth彩色截图(测试环境:Windows 2003 Server ,Vista与Win7中不可用,XP未测) 下面还是继续看代码: 1.GoogleEarth动

C#调用Google Earth Com API开发(一)

一.准备 Google Earth提供了个人免费版.Plus版.Pro版,个人开发只安装个人免费版就可以了,如果需要更 多的功能,那么只有每年上交$400购买专业版了 到目前为止,GoogleEarth的二次开发接口还比较少,功能太弱,仅仅提供了1.0的类库. GoogleEarth COM API参考文档可以在这里找到:http://earth.google.com/comapi/index.html C#调用COM的参考资料多如牛毛,大家可以到网上搜一下 二.例子 这里提供一个利用VS200

Google Earth 中如何对我的照片进行位置标记

Picasa 现在可让您使用 Google Earth 将照片指定到特定的位置."位置标记"功能允许您在照片文件内部嵌入位置信息,并且在 Google Earth 提供的卫星地图上显示照片.要对照片进行位置标记,请执行以下步骤: 1. 在 Picasa 中选择照片 2. 从"工具">"位置标记"菜单中选择"使用 Google Earth 进行位置标记"来启动 Google Earth.在 Earth 的右下角会显示一个小