The Windows 7 Location API

This article was originally included in the MapPoint Programming section of the Mapping-Tools.com HowTo pages. The MapPoint programming pages are being removed, but this page does not directly refer to MapPoint and remains relevant. The Location & Sensors API remains available, but Windows 10 also introduced a new Windows.Devices.GeoLocation API.

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 – i.e. 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 ancient serial (RS-232) connections.

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 a forum like Laptop GPS World 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 GPS devices, U-Blox, already supply a Windows 7 driver that supports the new API. For this page, a BU-353 device was used. No vendor driver was available, but the generic GPSDirect Virtual Sensor Driver was used instead. 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. This free driver 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.

Using the Location API

Okay, so let’s look at the actual Location API. The API is implemented using COM. A .NET COM Interop wrapper is available, but for this sample we shall use MFC. You will also need Microsoft Visual Studio 2008 and the Windows 7 SDK. The Windows 7 SDK is a free download.

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 is 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. A GUI interface (as used here) would require thread locking mechanisms to work properly.

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 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 (i.e. 0.5 seconds ago), and a cumulative distance calculated from 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 spLocationReport;
            CComPtr 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 spLocationReport;
      CComPtr 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.

// Calculate distance between prev & this locations
// cf. Aviation Formulary at http://williams.best.vwh.net/avform.htm
#define DEG2RAD (3.14159265 / 180.0)
#define RAD2DEG (180.0 / 3.14159265)
#define RAD2NM  ( RAD2DEG * 60.0 )
#define RAD2KM  ( RAD2NM * 1.852 )

double CLocationDemoDlg::CalcDistance()
{
	double lf = sin(lfPrevLat*DEG2RAD)*sin(lfThisLat*DEG2RAD);
	lf += cos(lfPrevLat*DEG2RAD)*cos(lfThisLat*DEG2RAD) * cos( (lfPrevLng-lfThisLng)*DEG2RAD );
	double d = acos(lf) * RAD2KM;	// distance in kilometers
	return (d>0.001) ? d : 0.0;  // only return distance if >1m
}

And that is it! I have left out a lot of MFC boilerplate, but the functioning location-specific content is fully covered above. The full VS2008 project including all source code can be downloaded here. This is what the functioning program looks like:

The working demo dialog box showing the current location

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.

Further Information

MSDN has extensive information including a full API reference and samples for both the Location API and the Sensor API. The Windows 7 SDK also includes samples for both synchronous and asynchronous location calls.

Leave a Reply