基于 SWT 实现桌面版 Google Map
2010-08-03 00:00:00 来源:WEB开发网引言
不知道什么时候开始,地图应用已经如此的普及,基于地图的 WEB 应用丰富多彩。比较著名的有 Google Map、Yahoo Map、Bing Map 等等,其中使用率最高的要属 Google Map,它简单易用,包含丰富的内容,并且具有良好的用户体验。与此同时,地图应用也普及到桌面应用上,人们试图基于桌面地图开发新应用,用的比较普遍的是 GIS(Geographic Information System),然而 GIS 对比 Google Map 的简单易用、信息丰富,还是有一定的差距。因此,很多人希望将 Google Map 集成到桌面应用当中,可以让桌面控件与 Google Map 进行数据交互。本文将基于 SWT(Standard Widget Toolkit)桌面技术,集成 Google Map,制作桌面版 Google Map 的 SWT 控件。
文章首先展示使用开发好的 Google Map 控件的 使用实例,让读者对目标程序有个大致的印象,接下来简介了重要的 Browser 控件,以及基于 Browser 控件实现 Google Map 控件的 实现原理,包括需要解决的问题,给出 实现控件的步骤,最后做简单的总结,告诉读者如此设计控件的优点所在。
实例展示
为了让读者更好的了解文章的目的,首先展示开发好的 Google Map 控件的使用实例,如图 1。注意,操作 Google Map 控件的都是 Java 代码,刚打开时地图是最原始的 Google 地图。
图 1. 效果展示
查看原图(大图)
图 1 中,左半部分为 Google Map 控件部份,任意点击地图,可以在点击处添加标记;右半部分为功能按钮,提供了设置中心点、添加标记、打开提示窗口、绘画直线这些功能。
例子中数据形式是以 ,(逗号)分隔,各个按钮的功能和数据要求如下表。
表 1. 例子使用
功能 | 数据要求 |
设置中心点 | 经度 , 纬度 |
设置标记 | 经度 , 纬度 |
弹出信息 | 经度 , 纬度 , 弹出信息 |
绘画直线 | 起始点经度 , 起始点纬度 , 终点经度 , 终点纬度 , 颜色 , 粗细 |
使用该 Google Map 桌面控件的 Java 代码风格与直接在 HTML 文件中使用 Google Map API 编程风格非常相似,目的是让编写过 Google Map Web 应用的读者可以快速上手使用该桌面控件,而不会有陌生感。 清单 1 展示了该控件各接口的使用例子,查看代码注释可以了解各个接口的含义。
清单 1. 使用控件
public void createPartControl(Composite parent) {
// 新建 Google Map,与 SWT 控件使用方法相同
final GMap2 map2 = new GMap2(parent, null);
// 添加鼠标点击事件监听
map2.addListener(GMap2.CLICK, new IListener() {
public Object function(Object[] arguments) {
// 获得点击的经度
double lat = ((Double) arguments[0]).doubleValue();
// 获得点击的纬度
double lng = ((Double) arguments[1]).doubleValue();
GLatLng center = new GLatLng(lat, lng);
// 移动适配地图的中心点
map2.fromLatLngToDivPixel(center);
// 在中心添加标记
map2.addOverlay(new GMarker(center));
return null;
}
});
// 初始化操作需要放到 IInvoke 接口的 invoke 方法中
map2.invoke(new IInvoke() {
public void invoke() {
// 设置 Map 的中心点
map2.setCenter(new GLatLng(39.917, 116.397), 14);
// 在中心打开消息框,显示 hello world 消息
map2.openInfoWindow(map2.getCenter(), "hello world");
// 在中心添加标记
map2.addOverlay(new GMarker(map2.getCenter()));
// 在中心点和(44,120)点之间绘画直线
map2.addOverlay(new GPolyline(map2.getCenter(),
new GLatLng(44, 120), "#ff0000", 10));
}
});
}
Browser 控件简介
在继续了解本文介绍的 Google Map 控件之前,需要了解 org.eclipse.swt.browser.Browser 控件,org.eclipse.swt.browser.Browser 叫做浏览器控件,它使用系统默认浏览器(Windows 中的 IE、Linux 中的 FireFox)作为控件内核,开发者使用 Browser 控件,就可以让程序带有浏览器的功能。Browser 类主要提供了如表 2 的常用接口,可以看到 Browser 提供了浏览器一大部分基本功能。
表 2. Browser 常用接口
方法名 | 功能 |
back() | 浏览器后退 |
forward() | 浏览器前进 |
refresh() | 浏览器刷新 |
stop() | 停止加载页面 |
setText() | 设置显示内容 |
execute(script: String); | 执行脚本(该功能在后面被大量使用) |
setUrl(url: String) | 设置网页地址 |
控件基本原理
实际上该 Google Map 控件就是一个 Browser 控件打开 Google Map 网页的结果,这个网页中 Google Map 占用了整个页面,Google Map 控件提供的各个 Java 接口,实际上是 Java 触发 JavaScript 调用 Google Map API 的结果。按照 Google Map 控件的需求,需要解决如下表的几个问题。
Browser 控件执行JavaScript
屏蔽默认的浏览器弹出菜单
传递 SWT 数据到Google Map
传递Google Map 数据到 SWT
屏蔽 F5 刷新
Google Map控件与 Google Map 同步缩放
开发环境配置
开发 Google Map 控件使用到 Browser 控件的新特性,这需要新版本的 SWT 支持。这里建议直接使用最新版的 RCP 开发 Eclipse:Eclipse for RCP/Plug-in Developer,使用该版本 Eclipse,可以减少很多不必要的配置工作。
另外,请确认使用普通浏览器可以正常的访问 Google Map,这样可以确保开发的 Google Map 控件的可用性。
Browser 控件执行 JavaScript
Browser 控件执行 JavaScript 是指在 Browser 打开的网页内执行某段 JS 的功能,是由 Browser 触发执行 JS 脚本。这个过程类似于利用 IE 打开某个网页,并且在 IE 的地址栏中输入 JS 内容,如 图 2。按回车,就会在当前网页中弹出 hello world 对话框,如图 3。
图 2. 地址栏输入 JS 脚本
图 3. 执行脚本
Browser 执行 JS 的原理也是如此,通过调用 Brower 的 execute 方法,可以执行某段 JS 脚本 , 目标是 Browser 的当前网页。例如可以这样调用 execute 方法:execute(“alert( ‘ hello world ’ )”), 以此实现上述的弹出 hello world 对话框的功能。事实上执行的脚本可以更加复杂,它可以操作当前网页内部的 DOM 结构,修改当前网页的内容等等。
屏蔽默认的浏览器弹出菜单'
在使用 Browser 控件的时候,右击 Browser 的显示区域,会弹出默认浏览器的下拉菜单,如 图 4。
图 4. 默认弹出菜单
桌面控件的下拉菜单应该是可以自定义的,为了让 Google Map 控件更加贴近 SWT 桌面控件,并且防止用户点击下拉菜单的选项而引起误操作,需要屏蔽默认的 Browser 右键弹出菜单。
清单 2. 屏蔽右键菜单
final Browser browser = … ;
browser.addMouseListener(new MouseListener() {
public void mouseDoubleClick(MouseEvent arg0) {
}
public void mouseDown(MouseEvent event) {
if (event.button == 3)
browser.execute("document.oncontextmenu = function() {return false;}");
}
public void mouseUp(MouseEvent arg0) {
}
});
为 Browser 控件添加鼠标事件监听,在 mouseDown 中处理鼠标单击事件,event.button == 3 代表右击操作。如果是鼠标右击事件,browser 执行脚本:document.oncontextmenu = function() {return false;},这段 JS 脚本可以阻止浏览器的当前网页弹出右键菜单。
传递 SWT 数据到 Google Map
Browser 执行 JavaScript 脚本,是 SWT 向 JavaScript 传递数据的一种途径,因为 Browser 所执行的 JavaScript 脚本可以由 Java 字符串组合而成,传递的数据就包含在字符串当中。清单 3 是让 Browser 弹出对话框,显示的内容是 Java 端传递过去的。
清单 3. Browser 执行 JS 的 alert 操作
String content = “hello world”;
Browser browser = … ;
browser.execute(“alert( ‘”+content+“’ )”);
清单 3 中可以看出,弹出对话框显示的内容,是由 content 字符串决定。Java 端修改该 content 字符串,也就修改了浏览器对话框显示的内容,这也就是 Java 向 JS 传递数据的方法。
传递 Google Map 数据给 SWT
最新版本的 SWT,支持 JavaScript 调用 Java 方法,利用这个特性可以实现 Google Map 传递数据给 Java。要实现传递这个功能,需要按三步走,第一步,Java 端需要实现类继承于 org.eclipse.swt.browser.BrowserFunction 处理 JavaScript 的调用请求,该类只在新版本的 SWT 中存在,如 清单 4。
清单 4. 处理 JavaScript 调用请求
public class InitFunction extends BrowserFunction {
public InitFunction(Browser browser, String name) {
//name 为该函数的名字,JavaScript 根据这个名字调用该函数
super(browser, name);
}
public Object function(Object[] arguments) {
//arguments 为 JavaScript 传递来的参数,包含 Java 端需要的数据
String content = ((String)arguments[0]);
System.out.println(content);
// 返回数据给 JavaScript 端
return content;
}
}
第二步,绑定 BrowserFunction 到 Browser,使得所有 Browser 打开的网页内,都允许 JavaScript 调用该 BrowserFunction。
清单 5. 绑定 BrowserFunction 到 Browser
Browser browser = … ;
new InitFunction(browser, "InitComplete");
第三步,在 Browser 打开的网页内调用绑定好的名为 InitComplete 的函数。
清单 6. JavaScript 端调用 BrowserFunction
<script type="text/Javascript">
var result = InitComplete(“helloworld”);
//result 为 Java 端返回的数据
alert(result);
</script>
上面三个步骤的例子的数据流如 图 5。
图 5. JavaScript 与 Java 的数据流
主要过程是 JavaScript 调用绑定的 BrowserFunction,传递数据给 Java 端,Java 端得到数据并且处理,返回结果,JavaScript 获得结果并处理。
实现步骤
了解前面几个部分的技术基础后,就可以逐步设计实现 Google Map 的控件了。下个部分将讲解 实例展示部分的各个功能实现。
建立模板 HTML
该 Google Map 控件是基于 Browser 的,控件中实际显示的 Google Map 也是一个网页,只是这个网页中,Google Map 占用了整个页面。因此,需要设计一个模板 HTML,该模板网页中使用一个 DIV 占据整个页面,并且利用 Google Map API 在此 DIV 上建立地图。Google Map 控件的初始化过程,实际上就是 Browser 打开模板 HTML 文件的过程。清单 7 是模板 HTML 文件主要代码。
清单 7. 模板 HTML 文件
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<script src="http://ditu.google.cn/maps?file=api&v=2&
key=ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrv
YughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA&hl=zh-CN"
type="text/Javascript"></script>
<script type="text/Javascript">
var map;
function initialize()
{
if (GBrowserIsCompatible())
{
map = new GMap2(document.getElementById("map_canvas"));
。。。。。。
}
}
。。。。。。
</script>
</head>
<body onunload="GUnload()"
style="padding: 0 0 0 0;margin: 0 0 0 0;">
<div id="map_canvas" style="width: 100%; height: 600px"></div>
</body>
</html>
屏蔽 F5 刷新
使用 Browser 还有一个问题,当用户按 F5 时,Browser 打开的网页会执行刷新操作,网页又会重新加载。为了防止用户误操作,解决方法是需要修改 Browser 显示的网页内容,重写 document.onkeydown 方法,如 清单 8。也就是说,在 Google Map 控件的开发中,需要修改该控件的模板 HTML 文件,在模板文件中添加 清单 8 的方法,那么用户按 F5 就不执行刷新。
清单 8. 屏蔽 F5 刷新
function document.onkeydown()
{
if ( event.keyCode==116)
{
event.keyCode = 0;
event.cancelBubble = true;
return false;
}
}
Google Map 自动缩放
虽然使用模板 HTML 文件解决了 Google Map 的显示问题,但是使用 Browser 的时候,Browser 的父容器被调整高度时,Browser 有可能会出现滚动条。这在大部分情况下是用户不希望看到的,从用户的角度看,控件被调整大小,地图也应该自动调整大小。清单 9 为地图自动缩放的代码。
清单 9. 屏蔽 F5 刷新
browser.addControlListener(new ControlListener() {
public void controlMoved(ControlEvent arg0) {
}
public void controlResized(ControlEvent e) {
browser
.execute("document.getElementById('map_canvas').style.height="
+ browser.getClientArea().height);
}
});
browser.addProgressListener(new ProgressListener() {
public void changed(ProgressEvent arg0) {
}
public void completed(ProgressEvent arg0) {
browser
.execute("document.getElementById('map_canvas').style.height="
+ browser.getClientArea().height);
}
});
为 Browser 添加事件监听,一个是 ProgressListener,监听 Browser 加载 HTML 的进度,在页面加载结束时,会触发 completed 方法,另外一个是 ControlListener,在用户设置控件大小时会执行 controlResized 方法,在上述两个方法中调用 Browser 的 execute 方法,执行如下脚本:"document.getElementById('map_canvas').style.height="+browser.getClientArea().height。目的是修改模板 HTML 文件中放置 Google Map 的 DIV 的高度,使得 DIV 的高度和 Browser 的 ClientArea 区域(Browser 实际显示的区域)的高度相同,这样滚动条就不会出现。至于宽度为什么不设置,是因为模板 HTML 中已经设置了 DIV 的宽度为 100%,所以宽度已经会自动缩放,而高度是不能按百分比设置的,需要进行如上处理。
新建 Google Map,设置中心点
定义 GMap2、GLatLng 类 ,GMap2 代表整个地图控件,GLatLng 存放经纬度信息,在 GMap2 类中声明 setCenter(GLatLng latlng, int scale) 方法 , 第一个参数是中心点的经纬度,第二个参数是地图的缩放级别。
清单 10. 新建地图,设置中心点
final GMap2 map2 = new GMap2(parent, null);
map2.invoke(new IInvoke() {
public void invoke() {
map2.setCenter(new GLatLng(39.917, 116.397), 14);
}
});
清单 10 中 parent 是 SWT 中 Browser 所在的父容器,属于 SWT 对象,IInvoke 参数对象的 invoke 方法是一个回调方法,当地图加载完毕后会调用该 invoke 方法。回调的原理是定义 InitFunction 继承于 BrowserFunction,命名为 Init,这些在 GMap2 的构造函数中实现,在模板 HTML 中在地图加载后调用该 Init 方法,通知 Java 端地图已经加载完毕,InitFunction 接收通知就调用 IInvoke 参数对象的 invoke 方法。实际执行 setCenter 操作的原理是调用 Browser 的 execute 方法执行 js 脚本,调用 Google Map API,如 清单 11,也正是因为调用 Google Map API 对地图进行操作,才需要等待地图被加载完毕。
清单 11. setCenter 实现
public void setCenter(GLatLng latlng, int scale) {
browser.execute("map.setCenter(new GLatLng(" + latlng.getLat() + ", "
+ latlng.getLng() + "), " + scale + ");");
}
添加标记
定义 GMarker 类,它代表地图的标记,它包含一个 GLatLng 对象属性,代表此标记所在的经纬度。清单 12 使用 GMarker 给地图中心添加标记。清单 13 是 addOverlay 的实现代码。
清单 12. 为地图添加标记
map2.addOverlay(new GMarker(map2.getCenter()));
清单 13. addOverlay 实现(GMarker)
public void addOverlay(GMarker gMarker) {
browser.execute("map.addOverlay(new GMarker(new GLatLng("
+ gMarker.getCenter().getLat() + ","
+ gMarker.getCenter().getLng() + ")));");
}
弹出提示框
GMap2 有一个方法名为 openInfoWindow,用于在某个经纬度上弹出信息框。实现如清单 14
清单 14. openInfoWindow 实现
public void openInfoWindow(GLatLng latlng, String text) {
browser.execute("map.openInfoWindow(new GLatLng(" + latlng.getLat()
+ ", " + latlng.getLng() + "),document.createTextNode('" + text
+ "'));");
}
绘画直线
定义 GPolyline 类,它代表地图上的某个直线,它包含名为 from 和 to 的两个 GLatLng 对象属性,代表直线的起始经纬度和终点经纬度,color 属性指定连线的颜色,scale 指定了连线的大小。清单 15 表明了添加连线的实现原理。
清单 15. addOverlay 实现(GPolyline)
public void addOverlay(GPolyline polyline) {
browser.execute("map.addOverlay(new GPolyline([new GLatLng("
+ polyline.getFrom().getLat() + ","
+ polyline.getFrom().getLng() + "),new GLatLng("
+ polyline.getTo().getLat() + "," + polyline.getTo().getLng()
+ ")], '" + polyline.getColor() + "', " + polyline.getScale()
+ "));");
}
添加事件监听
事件监听的原理是在 Java 端定义事件处理的 BrowserFunction;在模板 HTML 中为地图添加相应事件监听,在事件处理中调用定义的 BrowserFunction,传递数据给 Java 端;BrowserFunction 接收数据进行处理,比如操作数据库。清单 16 是定义的 SingleClickFunction,处理鼠标单击事件,清单 17 绑定 SingleClickFunction 到 Browser 上, 清单 18 是模板 HTML 中定义事件监听, 清单 19 是 Java 端为地图添加事件监听。
清单 16. SingleClickFunction 的实现
public class SingleClickFunction extends BrowserFunction {
private GMap2 map;
public SingleClickFunction (GMap2 map, String name) {
super(map.getBrowser(), name);
this.map = map;
}
public Object function(Object[] arguments) {
IListener listener = map.getListener(GMap2.CLICK);
if (listener != null) {
return listener.function(arguments);
}
return null;
}
public GMap2 getMap() {
return map;
}
}
清单 17. 绑定 SingleClickFuction
GMap2 map = … ;
new SingleClickFunction(map, “SingleClick”);
清单 18. 模板 HTML 中添加监听
GEvent.addListener(map,"click", function(overlay,latlng)
{
// 调用 BrowserFunction 传递数据给 Java 端
SingleClick (latlng.lat(), latlng.lng());
});
清单 19. Java 端为地图添加单击监听
map2.addListener(GMap2.CLICK, new IListener() {
public Object function(Object[] arguments) {
// 接收传递的数据并进行处理
double lat = ((Double) arguments[0]).doubleValue();
double lng = ((Double) arguments[1]).doubleValue();
System.out.println(“lat is ” + lat + “ lng is ” + lng);
return null;
}
});
为什么要这么做
之所以文章介绍如何实现一个这样的 Google Map 控件,目的是为了可以将 Google Map 集成到 SWT 桌面应用当中,使得编写过 Google Map Web 应用的人可以很轻松的上手使用该控件。有些读者会有这样的疑问,为什么不直接编写模板 HTML 页面,让模板 HTML 页面丰富多彩,而不需要设计很多类。笔者认为,编写模板 HTML 的做法缺少灵活性,模板 HTML 页面与 Java 交互变得非常死板。而设计详细的 Java 类,使得与 JavaScript 的交互变得非常简单、粒度小,Java 代码和 JavaScript 调用可以互相穿插调用,业务逻辑可以让 Java 程序控制,让 JavaScript 的调用真正的可以面向对象,更容易控制和修改,同时也使得单纯熟悉 SWT 桌面编程的人也可以使用 Google Map。另外,使用这样的设计,地图信息可以保存到数据库中,也就是在地图的操作过程中,可以进行数据库的交互作业,那么这样就和桌面程序无缝结合了。
模板 HTML 应该用于修改地图的显示样式,比如标记的图片、连线的样式等等可以在模板 HTML 里设置,至于业务逻辑应该放置到 Java 端。
笔者的目的也在于让读者了解 SWT Browser 的新特性,介绍 Java 与 JavaScript 交互的各种方式方法以及技巧。
总结
本文的目的主要在于让读者了解 SWT Browser 的特性以及 Java 与 JavaScript 交互的技巧,如果读者有兴趣的话,可以继续扩展该 Google Map SWT 控件,使得它变得更加 OO(Object Oriented),更加的易用。由于笔者知识水平有限,如果有错误的地方,欢迎联系我指正。
更多精彩
赞助商链接