Wednesday, September 12, 2012

Working with Bluetooth and GPS: Part 2 - Parsing GPS Data and Rendering a Map

This article is the second of a two-part series on how to use Java ME technology and Bluetooth to access location data from wireless GPS devices.
Contents
 
Parsing the NMEA Sentences
Requesting a Map Image from an External Map Service
Parsing the XML Result from the Map Service
Conclusion
 
As you may recall from Part 1 of this series, it is very easy to access the raw GPS data from a Bluetooth-enabled GPS device. The listing below shows what the serial output from a typical GPS device would look like:
Listing 1. NMEA Formatted GPS Data
$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
 
As you also may recall from Part 1, a GPS device encodes its data according to the NMEA specification. The purpose of this article is to learn how to accomplish the following tasks:
  • Parse the NMEA sentence data from a GPS device to retrieve the latitude and longitude values
  • Request a map image of our current location from a external map service
  • Parse the XML result data from the map service and render the map image on the mobile device
Parsing the NMEA Sentences
The serial data that is produced by GPS devices is formatted according to the NMEA specification, and each line of data is called an NMEA sentence. There are at least 5 NMEA sentences that provide the coordinates of your current position. The good news is that I only need to create a parser for one of them. I'll choose the $GPGGA header for the purposes of this article. If you want to know more about all the various standard and non-standard NMEA sentences, refer to the NMEA FAQ website. Following is an example of what an ordinary $GPGGA sentence would look like:
$GPGGA,123519,4807.038,N,01131.324,E,1,08,0.9,545.4,M,46.9,M, ,;
 
After further inspection, you can now see that the individual parts of an NMEA sentence are separated by commas. The following facts can be obtained from the preceding NMEA sentence:
  • the GPS fix was taken at 12:35:19 UTC time
  • the latitude coordinate is 48 degrees and 07.038 minutes North
  • the longitude coordinate is 11 degrees and 31.234 minutes East
  • the GPS fix quality is 1
  • 8 GPS satellites were being tracked
  • the horizontal dilution of position was 0.9
  • the altitude of the GPS fix was 545.4 meters
  • the height of the geoid was 46.9 meters
Now, you'd think that it would be really easy for Java ME devices to parse the NMEA sentence using the StringTokenizer class, right? Unfortunately, it's not that easy since the StringTokenizer class only exists in Java SE implementations. However, in the example code I've included a simple NMEA parser and String tokenization classes. The following is a code snippet from Parser.java that properly converts coordinate DMS format (degrees, minutes, seconds) to decimal degree values.
Listing 2. Code Snippet from 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;
}
 
Now that we've properly parsed the NMEA sentence, let's explore how to get a map using an external mapping service.
Requesting a Map Image from an External Map Service
In this day and age, you have several options to choose from when you want to make a simple HTTP request to get an image that represents a map of your current location (or any location for that matter). Several companies -- Mapquest, Google, and ERSi -- provide these services, but I decided to use the Yahoo! Maps service for the following reasons:
  1. All the options can be specified in URL parameters in a single HTTP request.
  2. No external libraries are needed to consume the API.
  3. The response comes back as simple XML document that can be easily parsed.
  4. The resulting map image is in PNG format, which all MIDP devices support.
In order to use the Yahoo! Maps API, all you need to do is sign up for a free developer account id key. So, if I wanted a map with the following parameters:
  • latitude: 46.987484
  • longitude: -84.58184
  • image width: 400 pixels
  • image height: 400 pixels
  • zoom level: 7
then the URL in the HTTP request would look like this:
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
 
Pretty simple, huh? The result of this request is not the image itself, but an XML document that has a link to the image. The listing below shows the XML result of my HTTP request for a map image:
Listing 3. XML Result from the Yahoo! Maps Service
<?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>
 
Parsing the XML Result from the Map Service
Ok, we're almost at the finish line. All we need to do now is to parse the result that we got from the map service and extract the URL to the map image. Fortunately, this is also a trivial task thanks to the JSR-172 XML Parsing API.
You should also be glad to know that the JSR-172 API has been out for several years and is available on a wide variety of mobile handsets. Of course, the JSR-172 API is a part of the Java ME MSA standard, so if your handset supports MSA then you're obviously good to go.
In the following listing, you can see that my XML parsing class only needed to extend the DefaultHandler class in the JSR-172 API. Since we're only interested in the contents of a single tag, namely the <Result> tag, then the code necessary to retrieve the URL for the map image is fairly simple.
Listing 4. A Simple XML Parsing Class Using the JSR-172 API
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;
  }        
}
 
Now the code in Listing 4, specifically the getImageURL() method, will return the URL that points to the PNG image of the map of our current location. The only remaining step is to make another HTTP request to retrieve the image and display it on the mobile device. Figure 1 depicts a mobile device showing our current location.

Link : http://dsc.sun.com/mobility/apis/articles/bluetooth_gps/part2/

No comments:

Post a Comment