Introduction

Due to a mix-up in exchange rates, a friend ended up owning me $100. For some reason I asked him to get me a
Bluetooth GPS receiver. It came with no documentation and no software. (Not even for Windows: what were they thinking?!)

Anyway BlueZ to the rescue:
baroque$ hcitool scan
Scanning ...
       00:0B:24:33:7B:46       TeleTypeBT01
baroque$ sdptool browse 00:0B:24:33:7B:46
Browsing 00:0B:24:33:7B:46 ...
Oops, it's not browseable. Try searching for something common:
baroque$ sdptool search SP
Searching for SP on 00:0B:24:33:7B:46 ...
Service Name: Serial Port
Service RecHandle: 0x10000
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 1
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Connect to this service as follows:
baroque$ rfcomm connect 0 00:0B:24:33:7B:46
Connected /dev/rfcomm0 to 00:0B:24:33:7B:46 on channel 1
Press CTRL-C for hangup

Capturing the output just after it boots, yields:
$========================================================
$                 2004. 10. 07    Bluetooth
$========================================================
$
$S/W Version(by JCOM) : Jcom_Remote Ver1.26 for BT
$NMEA: GPGGA, GPGSV, GPGSA, GPRMC, 9600bps
$STATIC_NAV_ENABLED
$TRACK_SMOOTH_ENABLED
$Waas On
$PSRF150,1,*12
at+btname=TeleTypeBT01
$Version 2.3.2-GSW2-2.05.024-C1Prod1.1
$TOW: 206721
$WK:  1339
$POS: 3794287  -415194  5092911
$CLK: 96708
$CHNL:12
$Baud rate: 9600  System clock: 12.277MHz
$HW Type: S2AM
$Asic Version: 0x23
$Clock Source: GPSCLK
$Internal Beacon: None

Parsing NMEA Sentences

Reading from this device returns one NMEA sentence per second. Here is some typical output:
baroque$ cat /dev/rfcomm0
$GPGSV,3,2,10,08,35,170,00,04,29,186,00,23,15,068,00,16,10,028,*76
$GPGSV,3,3,10,06,09,321,,29,07,248,*75
$GPRMC,100142,V,5320.0138,N,00614.7191,W,0.000000,0,050805,,*3B
Parsing this data is pretty easy (watch out for invalid data though). From the GPGGA and GPRMC sentences, a POJO containing the following fields is easily constructed:

Google Maps

Google Maps has a map server which will return a map for a particular location (x, y) at a particular zoom. Unfortunately it doesn't accept (lat, lon) pairs but luckily there is plenty of published code which can be used as a starting point.

    private static final double OX = -98.35; // degrees west longitude
    private static final double OY = 39.5;   // degrees north latitude
    private static final double PIXEL_RATIO = 0.77162458338772;

    private static double degrees(int zoom)
    {
        return (double)PIXELS / (double)(131072 >> zoom);
    }

    /**
     * Given decimal representations of latitude and longitude,
     * return the magic Google coordinates.
     * @return a Point containing (x, y) for the map request for
     * the given zoom factor.
     */
    public static Point getMapOrdinates(double lat, double lon, int zoom)
    {
        double z = degrees(zoom);
        double x = (lon - OX) * PIXEL_RATIO / z;
        double y = (OY - lat) * z;
        return new Point((int)Math.floor(x), (int)Math.floor(y));
    }

This allows retrieval of the correct map for any given (lat, lon) pair (e.g., http://mt.google.com/mt?x=18176&y=-3535&zoom=2). In order to find the correct point on the map on which to draw the position, it is necessary to find the (latitude, longitude) bounding box of the map. This is achieved by inverting the calculation above:

    /**
     * Given a magic Google ordinate, return the longitude for
     * a given zoom. This allows computation of a bounding box
     * for a particular map, and plotting exact position on it.
     */
    public static double getLongitude(double x, int zoom)
    {
        return x * degrees(zoom) / PIXEL_RATIO + OX;
    }

    /**
     * Given a magic Google co-ordinate, return the latitude for
     * a given zoom.
     */
    public static double getLatitude(double y, int zoom)
    {
        return OY - y * degrees(zoom);
    }

Screenshots

These components may be usefully combined in several different ways. Here are some screenshots of data captured on a PDA (a Sharp Zaurus 5500 with the Jeode PersonalJava VM and Impronto for BlueZ):
In this display the route taken is plotted in red (where the speed was below the average) and black (where it was above). Only maps which were traversed are displayed.
This program shows position as a moving dot on a map at varying degrees of zoom. When the data is acquired from the GPS unit itself, the strengths of the signals on the various channels are also displayed.

The lefthand screenshot shows a MIDlet running in a J2ME emulator called me4se. Ultimately the MIDlet will run on a sufficiently-capable phone, i.e., one with MIDP-2.0 and JSR-82, such as a Nokia 6600 or a Sony-Ericsson K750i.

The middle screenshot shows the PersonalJava program above except with the new improved map-protocol, v2.5. Under this protocol, tiles are bigger (256x256) and contain more information (e.g., one-way streets).

The righthand screenshot shows the new map-protocol running on the cvm (Personal Profile) on a Zaurus. Although this VM is slower than Jeode, it was used because the new map-protocol uses PNG maps --- a format unsupported by Jeode.

Tile naming in Google Maps v2.x

It is quite easy to deduce the naming scheme used for version 2.5 of Google Maps. (In the following coordinates are represented as (x, y, zoom).)

Updates

License

All code on this page is distributed under the terms of the
GNU GPL.
Last Updated: Sun Jul 16 13:05:49 IST 2006