双向交流
在上面的代码示例中,网络链接需要等待我们的刷新操作。幸运的是,我们可以让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客户端,至此你已拥有了一个创建自己互动旅程的概念性工具箱。
