Using the New Windows 7 Location API

Richard Marsden discusses the new “Location and Sensors” API and how to get started with development including a complete working example with source code available to download.

Probably one of the most
interesting additions in Windows 7 for application developers is the addition
of a “Location and Sensors” API. This provides a common interface for a wide
range of sensor devices – ie. similar to a device driver for a printer. This
will be very useful where such peripherals are often shackled to vendor-only
applications and/or serial (RS-232) connections. Today, the only place where I
actively use an RS-232 connection is in the daisy chain of cables and adapters
that we have to take with us on the EcoMap Costa Rica Project (URL: http://www.ecomapcostarica.com ) in
order to read data from our HOBO water temperature recorders! Closer to home,
the geospatial world is still encumbered by the NMEA-0183 standard which is
based on a serial (“COM port”) connection although it is usually implemented
virtually. You only need to search the forums here to see the problems caused
by this virtual COM port arrangement: users have to install a USB driver and
configure “non-existent” settings like COM port and baud rate. They rightly
expect to be able to plug their GPS device in and it “just works” – like a
printer or any other USB device. These problems should go away as more people
adopt Windows 7 and vendors roll out Location API drivers for their devices.
This article shows you how to use the new Location API in your own application.

The suppliers of the MapPoint and
Streets & Trips devices, U-Blox, already supply a Windows 7 driver that
supports the new API. For this article I used a BU-353 device. No vendor
driver is available for this yet, but I was able to use the generic GPSDirect
Virtual Sensor Driver (URL: http://www.turboirc.com/gps7/
). This maps any generic NMEA-0183 device to the new Location API. You still
have to configure the COM settings and I found it could do with some polish,
but it is free. It should be good enough for development, but I would hesitate
to use it in a production environment.

Although most readers will be
interested in using the Location API with GPS devices, the API can also support
other location services such as WiFi base station setting, and cell-phone
triangulation. It can also provide location reports as street addresses if the
device (real or virtual) supports this information.

After installing your Location
API driver and the Windows 7 SDK, you can configure Visual Studio. The SDK
comes with a configuration tool that should be able to do this for you. If not,
you will have to manually add the SDK to the standard paths for include files
and library (lib) files. In a standard install, they will be something like
this:

C:\Program files\Microsoft SDKs\Windows\v7.0\Include

C:\Program files\Microsoft
SDKs\Windows\v7.0\Lib

I found I had to put these paths
at the top of path listings for includes and libs to avoid an incompatible mix
of different files from different versions.

Next, create a new project using
the multi-threading options. For this sample I have chosen an MFC dialog box
application. Add “LocationAPI.lib” to the Additional Dependencies setting in
the project property pages (Linker->Input). This library provides the new
API. You will also need to include LocationAPI.h which provides the interface
prototypes.

The API can work in two modes:
Synchronous and Asynchronous. I found the synchronous API to be quick enough
for my needs. The asynchronous method is multi-threaded, and the call-back
function will be called on a different thread. This will be preferable for a
large application, but it adds complications which are beyond the scope of this
article. An asynchronous example that uses the console for output can be found
on the Microsoft website here ( URL: http://msdn.microsoft.com/en-us/library/dd317649%28VS.85%29.aspx
). A GUI interface (as used here) would require thread locking mechanisms.

So let’s start coding! Our
program will request location updates every half a second, and measure the
distance travelled between each update.

Our dialog box class’s include
file (LocationDemoDlg.h) consists of mainly MFC boilerplate, but we do have
some implementation specific definitions:

private:
// our location specific private data members

LOCATION_REPORT_STATUS status;

CComPtr<ILocation> spLoc; // This is the main
Location interface

double lfPrevLat, lfPrevLng;

double lfThisLat, lfThisLng;

double lfDistance;

LOCATION_REPORT_STATUS is an enum
that stores the status of the latest location report request. We simply use a
class-level variable as a part of the text mapping. This could be made more
efficient and stored as a location variable that is mapped as a function parameter.
spLoc is the pointer to our main Location interface. The remaining variables
simply keep track of the current location, previous location (ie. 0.5 seconds
ago), and a cumulative distance calculated between these differences.

Next we move to the implementation
of CLocationDemoDlg. Again, I shall skip the bulk of the MFC boilerplate, but
the following snippet should help to explain how the dialog box GUI is hooked
up. The dialog box (CLocationDemoDlg) has four text labels which are used to
display information at 0.5 second intervals. These four labels are hooked up to
four CString definitions:

void
CLocationDemoDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

DDX_Text(pDX,
IDC_STATUS, sStatus);

DDX_Text(pDX,
IDC_PREVPOS, sPrevPos);

DDX_Text(pDX,
IDC_THISPOS, sThisPos);

DDX_Text(pDX,
IDC_DISTANCE, sDistance);

}

Beyond the automatically
generated call backs produced by MFC, we need to add two event handlers. ON_WM_TIMER
handles the timer events, and a second handler handles the OK button press (to
shut things down).

Next we move to our
OnInitDialog() definition: This starts the Location API, gets a status, and an
initial location:

BOOL
CLocationDemoDlg::OnInitDialog()

{

// (more MFC boiler plate)

CDialog::OnInitDialog();

SetIcon(m_hIcon,
TRUE);

SetIcon(m_hIcon,
FALSE);

// Our code starts here

lfDistance
= 0.0;

// Create the Location object

status = REPORT_NOT_SUPPORTED;

if
(SUCCEEDED(spLoc.CoCreateInstance(CLSID_Location)))

{

// Array of report types of interest.

// Civic addresses/etc also supported

IID
REPORT_TYPES[] = { IID_ILatLongReport };

// Request permissions for this user account to receive
location

// data for all the types defined in
REPORT_TYPES

// The final parameter (TRUE) indicates
a synchronous request

// use FALSE to request asynchronous
calls

if (FAILED(spLoc->RequestPermissions(NULL,
REPORT_TYPES,

ARRAYSIZE(REPORT_TYPES),
TRUE)))

{

AfxMessageBox("Warning: Unable to request permissions.\n");

}

// Get the report status

if
(SUCCEEDED(spLoc->GetReportStatus(IID_ILatLongReport, &status)))

{

// Map status enum to a text label for display

SetStatusString();

// Next define our report objects

// These store the reports we request from spLoc

CComPtr<ILocationReport> spLocationReport;

CComPtr<ILatLongReport> spLatLongReport;

// Get the current location report (ILocationReport)
and

// then get a ILatLongReport from it

// Check they are are reported okay and not null

if ( (SUCCEEDED(

spLoc->GetReport(IID_ILatLongReport, &spLocationReport))

) &&

(SUCCEEDED(

spLocationReport->QueryInterface(&spLatLongReport))

))

{

lfThisLat = 0;

lfThisLng = 0;

// Fetch the latitude & longitude

spLatLongReport->GetLatitude(&lfThisLat);

spLatLongReport->GetLongitude(&lfThisLng);

// Format them into a label for display

sPrevPos.Format("Lat: %.6f, Lng:%.6f",
lfThisLat, lfThisLng);

// set some sensible initial values

lfPrevLat = lfThisLat;

lfPrevLng = lfThisLng;

sDistance = "0.0";

}

}

// We have completed our call

// but keep the spLoc pointer for
future use

// Start the timer (Timer #1) with a
500ms (0.5s) interval

SetTimer(1,500, 0);

}

// Update labels with their new information

UpdateData(FALSE);

return TRUE;

}

The above is a simple example
of using the location API in a synchronous manner. We only fetch the location’s
longitude and latitude, but other parameters such as estimated error and
altitude are available.

We fetched the status but left
it to the function SetStatusString() to process it. This simply maps the status
enum into a human-readable label. Here is the definition:

// Set the status label string according to the status enum

// Updatedata is not called (typically this would be called after
this routine anyway)

void
CLocationDemoDlg::SetStatusString()

{

switch (status) // If
there is an error, print the error

{

case REPORT_RUNNING:

sStatus = "Report received okay";

break;

case REPORT_NOT_SUPPORTED:

sStatus = "No devices detected.";

break;

case REPORT_ERROR:

sStatus = "Report error.";

break;

case REPORT_ACCESS_DENIED:

sStatus = "Access denied to reports.";

break;

case REPORT_INITIALIZING:

sStatus = "Report is initializing.";

break;

}

}

The call back for the OK button
stops the timer and closes the dialog box:

void
CLocationDemoDlg::OnBnClickedOk()

{

// Stop the timer

KillTimer(1);

// Close dialog box

OnOK();

}

Next we define the actual timer
call back. This is almost a complete copy of the above synchronous location
code. We fetch a new report, the report’s status, and longitude,latitude
coordinate. The distance between the previous coordinate and the new coordinate
is calculated and added to lfDistance – a cumulative distance.

void
CLocationDemoDlg::OnTimer(UINT nIDEvent)

{

// Get the report status

if
(SUCCEEDED(spLoc->GetReportStatus(IID_ILatLongReport, &status)))

{

SetStatusString();

CComPtr<ILocationReport> spLocationReport;

CComPtr<ILatLongReport> spLatLongReport;

if ((SUCCEEDED(

spLoc->GetReport(IID_ILatLongReport, &spLocationReport))

) &&

(SUCCEEDED(

spLocationReport->QueryInterface(&spLatLongReport))

))

{

lfPrevLat = lfThisLat;

lfPrevLng = lfThisLng;

// Fetch the new latitude & longitude

spLatLongReport->GetLatitude(&lfThisLat);

spLatLongReport->GetLongitude(&lfThisLng);

// Format coords into a label for display

sThisPos.Format("Lat: %.6f, Lng:%.6f",
lfThisLat, lfThisLng);

sPrevPos.Format("Lat: %.6f, Lng:%.6f",
lfPrevLat, lfPrevLng);

// Calculate the new cumulative distance, and update
label

lfDistance += CalcDistance();

sDistance.Format("%.3f km",
lfDistance);

// update the display

UpdateData(FALSE);

}

}

CDialog::OnTimer(nIDEvent);

}

Finally we need to implement
CalcDistance(). This calculates the straight line (great circle) distance
between the latest coordinate and the previous coordinate. Distances less than
1 metre are returned as 0 metres.

Note that a GPS device has errors
and the precise coordinate will typically “wander” by a few metres back and
forth. This program picks up these “wanderings” if they are larger than 1 metre
per 0.5 second interval. These can quickly accumulate if reception is
particularly poor.

Prior to Winwaed, Richard worked as a software developer working on
seismic processing algorithms for the oil exploration industry. He holds
geology and geophysics degrees from the University of Cambridge
(Churchill College), and the University of Durham; and an
interdisciplinary MBA from the University of Dallas.