Java Solaris 加入 SDN 参与讨论 我的社区 注册说明
 
 
 
 
 
 
Java API 文档中文版
使用 Bluetooth 和 GPS:第 2 部分:解析 GPS 数据并呈现地图
 
By Bruce Hopkins, 12/11/08  

本文是本系列文章(共两篇)的第二部分,介绍如何使用 Java ME 技术和 Bluetooth 来从无线 GPS 设备访问位置数据。

目录

 

解析 NMEA 语句
从外部地图服务请求地图图像
解析来自地图服务的 XML 结果
结束语

 

回顾本系列文章的第 1 部分,通过启用 Bluetooth 的 GPS 设备访问原始的 GPS 数据是非常容易的。下面的清单显示了传统的 GPS 设备上的串行输出,如下所示:

清单 1. NMEA 格式的 GPS 数据
$GPGSV,3,3,10,31,76,012,31,32,60,307,38,,,,,,,,*72
$GPGSA,A,3,32,31,16,11,23,,,,,,,,4.5,3.1,3.3*34

$GPRMC,122314.000,A,3659.249,N,09434.910,W,0.0,0.0,220908,0.0,E*78
$GPGGA,122314.000,3659.24902,N,09434.91042,W,1,05,3.1,261.51,M,-29.1,M,,*58

$GPGSV,3,1,10,01,62,343,00,11,14,260,34,14,35,079,27,16,29,167,28*73
$GPGSV,3,2,10,20,44,309,00,22,13,145,00,23,08,290,31,30,23,049,33*7C
$GPGSV,3,3,10,31,76,012,31,32,60,307,38,,,,,,,,*72
$PSTMECH,32,7,31,7,00,0,00,0,14,4,30,4,16,7,00,0,11,7,23,7,00,0,00,0*50

$GPRMC,122315.000,A,3659.249,N,09434.910,W,0.0,0.0,220908,0.0,E*79
$GPGGA,122315.000,3659.24902,N,09434.91048,W,1,05,3.1,261.61,M,-29.1,M,,*50

 

本系列文章的第 1 部分已经介绍,GPS 设备根据 NMEA 规范来编码其数据。本文的目的是教您学会如何完成下列任务:

  • 解析来自 GPS 设备的 NMEA 语句数据,以便检索纬度和经度值。
  • 从外部地图服务请求我们当前位置的地图图像。
  • 解析来自地图服务的 XML 结果数据并在移动设备上呈现地图图像。

解析 NMEA 语句

GPS 设备生成的串行数据根据 NMEA 规范进行格式化,且每行数据都被称作一个 NMEA 语句。要提供您当前位置的坐标,至少需要 5 个 NMEA 语句。而我只需要为其中一个语句创建一个解析器。这可是个好消息。根据本文的意图,我选择了 $GPGGA 标题。有关各种标准的和非标准的 NMEA 语句的更多信息,请参阅 NMEA 常见问题解答网站。下面给出了一个普通 $GPGGA 语句的示例:

$GPGGA,123519,4807.038,N,01131.324,E,1,08,0.9,545.4,M,46.9,M, ,;

 

经过深入查看 ,您可以看到 NMEA 语句的各个单独部分都是通过逗号分隔的。根据前面的 NMEA 语句,我们能得到以下信息:

  • GPS fix 数据取自世界标准时间 12:35:19
  • 纬度坐标为北纬 48 度 07.038 分
  • 经度坐标为东经 11 度 31.234 分
  • GPS fix 的质量为 1
  • 8 个 GPS 卫星正在被跟踪
  • 位置的水平精度因子为 0.9
  • GPS fix 的海拔高度为 545.4 米
  • 大地水准面高度为 46.9 米

现在,您可能认为对于 Java ME 设备来说使用 StringTokenizer 类来解析 NMEA 语句会非常简单?遗憾的是,这种做法并不容易,因为 StringTokenizer 类只在 Java SE 实现中存在。不过,在示例代码中,我包括了一个简单的 NMEA 解析器和 String 符号化类。Parser.java 能将坐标 DMS 格式(度、分、秒)恰当地转换为十进制的度数值,下面给出了它的代码片段。

清单 2. 来自 Parser.java 的代码片段
if (token.endsWith("$GPGGA")) {
type = TYPE_GPGGA;

// Time of fix
tokenizer.next();

// Latitude
String raw_lat = tokenizer.next();
String lat_deg = raw_lat.substring(0, 2);
String lat_min1 = raw_lat.substring(2, 4);
String lat_min2 = raw_lat.substring(5);
String lat_min3 = "0." + lat_min1 + lat_min2;
float lat_dec = Float.parseFloat(lat_min3)/.6f;
float lat_val = Float.parseFloat(lat_deg) + lat_dec;

// Latitude direction
String lat_direction = tokenizer.next();
if(lat_direction.equals("N")){
// do nothing
} else {
lat_val = lat_val * -1;
}

record.latitude = lat_val + "";

// Longitude
String raw_lon = tokenizer.next();
String lon_deg = raw_lon.substring(0, 3);
String lon_min1 = raw_lon.substring(3, 5);
String lon_min2 = raw_lon.substring(6);
String lon_min3 = "0." + lon_min1 + lon_min2;
float lon_dec = Float.parseFloat(lon_min3)/.6f;
float lon_val = Float.parseFloat(lon_deg) + lon_dec;


// Longitude direction
String lon_direction = tokenizer.next();
if(lon_direction.equals("E")){
// do nothing
} else {
lon_val = lon_val * -1;
}
record.longitude = lon_val + "";

record.quality = tokenizer.next();

record.satelliteCount = tokenizer.next();
record.dataFound = true;
// Ignore rest
return 200;
}

 

我们已经恰当地解析了 NMEA 语句。接下来,我们将探索如何使用外部地图服务来获取地图。

从外部地图服务请求地图图像

如今,当您希望提交一个简单的 HTTP 请求来获取表示您当前位置(或者针对某种事物所处位置)的地图时,您有多种方式可以选择。Mapquest、Google 和 ERSi 这几家公司都提供了这些服务,但是我决定使用 Yahoo!Maps 服务,原因如下:

  1. 所有选项都可以在单独 HTTP 请求的 URL 参数中指定。
  2. 不需要借助外部库来使用 API。
  3. 响应作为可以被轻松解析的、简单的 XML 文档而返回。
  4. 结果地图图像以所有 MIDP 设备都支持的 PNG 格式表示。

要使用 Yahoo!Maps API,您只需 注册并获取一个自由开发人员帐户 id 密钥。因此,如果我希望获得一个带有下列参数的地图:

  • 纬度:46.987484
  • 经度:-84.58184
  • 图像宽度:400 像素
  • 图像高度:400 像素
  • 缩放级别:7

那么,HTTP 请求中的 URL 就会如下所示:

http://local.yahooapis.com/MapsService/V1/mapImage?appid=
YOUR_YAHOO_ID_KEY&latitude=46.987484&longitude=-
84.58184&image_width=400&image_height=400&zoom=7

 

相当简单,是不是!该项请求的结果并不是图像本身,而是一个连接到该图像的 XML 文档。下面的清单显示了针对一个地图图像的 HTTP 请求的 XML 结果:

清单 3. 来自 Yahoo! 地图服务的 XML 结果
<?xml version="1.0"?>
<Result xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
http://gws.maps.yahoo.com/mapimage?MAPDATA=Voo4MOd6wXXT6pG.WpNC6XPETWAN8WDUsxa
8qRQ2kzC_f8vO7.FvQhW3hSbWbF_jO3H4.J2Gb7Qhc2vqoCTL0DWbaCfT751_Zt9Ysqtg0dKo2mv95
EIc4bbgdYrmebNqFcwfKb8YhOFe38Ia3Q--&mvt=m?cltype=onnetwork&.intl=us
</Result>

 

解析来自地图服务的 XML 结果

好了,我们几乎就要实现既定目标了。我们现在要做的事情就是解析从地图服务所获得的结果并将该 URL 解压到该地图图像中。幸运的是,由于有了 JSR-172 XML 解析 API,这项操作也变成了小事一桩。

您还会高兴地了解到,JSR-172 API 已经推出好几年了,并在可以在许多移动手机上使用。当然,JSR-172 API 是 Java ME MSA 标准 的组成部分。因此,如果您的手机支持 MSA,显然应该使用 MSA。

在以下清单中,您能看到只需要 XML 解析类来扩展 JSR-172 API 中的 DefaultHandler 类。既然我们只对单独标记 <Result> 的内容感兴趣,那么检索用于地图图像的 URL 所需代码就变得相当简单了。

清单 4. 使用 JSR-172 API 的简单 XML 解析类
public class SimpleHandler extends DefaultHandler {

public String image_url = null;

public SimpleHandler() {}

public void startDocument() throws SAXException {}

public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

if(qName.equals("Result")) {
// do nothing
} else {
throw new SAXException("<Result> tag not found");
}
}

public void characters(char[] ch, int start, int length) throws SAXException {
image_url = new String(ch, start, length).trim();
}

public void endElement(String uri, String localName, String qName, Attributes attributes) throws SAXException{}

public void endDocument() throws SAXException{}

public String getImageURL(){
return image_url;
}
}

 

现在,清单 4 中的代码,特别是 getImageURL() 方法将返回指向我们当前位置的地图的 PNG 图像的 URL。最后一步是提出另一个 HTTP 请求以检索图像并将其显示在移动设备上。图 1 描述了显示我们当前位置的移动设备。

 
图 1. 我们当前位置的最终地图图像
 

 

结束语

本文提供的示例代码演示了如何轻松地使用 JSR-82(Bluetooth)API 访问来自支持 Bluetooth 的 GPS 接收器的数据、解析数据流并获得当前位置的坐标。此外,您已经了解了如何通过 HTTP 请求来访问外部地图服务、使用 JSR-172(XML 解析和 Web 服务)API 来解析结果,并提交最终请求来获得地图服务。JSR-82 和 JSR-172 均包括在 Java ME MSA 标准 中。

我希望本文提供的示例代码将指导您创建一些真正令人兴奋的、基于位置的应用程序。