Exercise 1: Obtaining Location Reports

Exercise 1: Obtaining Location Reports

Sensor & Location Platform - Native

Hands-On Lab

Sensor & Location Platform - Native

Lab version:1.0.0

Last updated:12/8/2018

Contents

Overview

Exercise 1: Obtaining Location Reports

Task 1 - Reading Location Synchronously

Task 2 - Reading Location Asynchronously

Summary

Overview

The Windows7 Sensor & Location Platform enables your applications to adapt to the current environment and change the way they look, feel or behave. Here are few examples:

  • When using a mobile PC (for example, a laptop or tablet) outdoors in a sunny day, an application might increase brightness, contrast and de-saturate colors to increase screen readability
  • An application might provide location-specific information, such as nearby restaurants
  • An application might use 3D accelerometer and buttons as a game controller
  • An application might use a human presence sensor to change the state of the Messenger status

Sensors MSDN Reader png

Figure 1

Modified MSDN Reader thatuses an ambient light sensor to change contrast, size and color saturation

The Sensor & Location Platform has many advantages compared to proprietary solutions:

  • Hardware-independence: No need to learn and invest in a particular vendor's API; all sensors types are handled very similarly
  • Privacy: Because Microsoft recognizes that sensor and location data are private, personally identifying information, all sensors are disabled by default. You can enable/disable sensors at any time via the Control Panel. Applications might prompt you to enable specific sensors via a secure consent UI.
  • Application sharing: Multiple applications can consume data from the same sensor simultaneously
  • Location simplicity: The Location API lets you obtain the location without caring about the particular mechanism used to obtain the information, for example, GPS, cell-tower or Wi-Fi hotspot triangulation. The Location API automatically chooses the most accurate sensor data available. In addition, you don't need to implement GPS protocols such as NMEA.

Objectives

In this Hands-On Lab, you will learn how to work with the Windows7 Location API in your application, including:

  • Synchronously getting civic address location report
  • Asynchronously getting civic address location report
  • Requesting user consent if access to a location information is not enabled

System Requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1
  • Windows7 RC or later
  • Windows7 RC SDK or later
  • Civic address location provider (eDLP) – original source for to download:

Exercise 1: Obtaining Location Reports

In this exercise, you will create a Win32 application thatreads and prints the latitude/longitude location and civic address location synchronously, and then subscribes to location update reports. You will use eDLP to modify your default location (both latitude/longitude and civic), causing the application'slocation update event handlers to execute and print the updated location.

Before beginning this exercise, make sure you don't have any location sensors attached, physical or virtual. They will override the Default Location and will cause incorrect results. To do so:

  1. Open “Location and Other Sensors” windows:
  2. Click the Windows Start button
  3. Type sensor in the search box:

SensorAndLocationControlPannel PNG

To begin this exercise, open Visual Studio. Create a new Win32 Console Application project and call it LocationHOL:File --> New --> Project... --> Visual C++ (may be under Other Languages) --> Win32 --> Win32 Console Application.

  • Create the project in a writable directory (for example, C:\Temp).
  • Name the projectLocationHOL.

Help

In the interests of brevity and simplicity, the demo application does not demonstrate best practices of Win32 development, nor does it exhibit the best design guidelines for object-oriented development and COM development.

Task 1 - Reading Location Synchronously

In this task, you will read and display latitude/longitude location synchronously. You will query the status of the report type as well as request user consent to use the information.

  1. Right-click the LocationHOL project.
  2. Click Properties.
  3. Go to Linker --> Input --> Additional dependencies.
  4. Add locationapi.lib to the list.
  5. Click OK to close the dialog.
  6. Navigate to StdAfx.h.
  7. Add the following #include:

C++

#include<windows.h>

#include<atlbase.h>

#include<atlcom.h>

#include<LocationAPI.h>

  1. Navigate to LocationHOL.cpp.
  2. Add the following lines before _tmainbut after the #includes (this code will initialize ATL/COM):

C++

// Array of report types of interest.

IID REPORT_TYPES[] = { IID_ILatLongReport, IID_ICivicAddressReport };

class CInitializeATL : public CAtlExeModuleT<CInitializeATL>{};

CInitializeATL g_InitializeATL;

// Initializes ATL for thisapplication. This also does CoInitialize for us

  1. Insert the following code into _tmain, before the return:

C++

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

// Create the Location object

if (FAILED(spLocation.CoCreateInstance(CLSID_Location)))

{

wprintf(L"Failed to create Location COM object.\n");

return 1;

}

// Request permissions for this user account to receive location data // for all thetypes defined in REPORT_TYPES

if (FAILED(spLocation->RequestPermissions(NULL, REPORT_TYPES, ARRAYSIZE(REPORT_TYPES), TRUE))) // TRUE means a synchronous

// request

{

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

}

  1. This code creates the ILocation object, which is the main object of the Location API. The code then requests user consent by displaying a dialog asking for permission to use the location information.
  2. To print a description for each value of the LOCATION_REPORT_STATUS enumeration, add the following function in LocationHOL.cpp(you will need to put a function declaration at the beginning of the file, right after global variables):

C++

void PrintReportStatus(LOCATION_REPORT_STATUS status)

{

switch (status)

{

case REPORT_RUNNING:

wprintf(L"Report ready.\n");

break;

case REPORT_NOT_SUPPORTED:

wprintf(L"No devices detected.\n");

break;

case REPORT_ERROR:

wprintf(L"Report error.\n");

break;

case REPORT_ACCESS_DENIED:

wprintf(L"Access denied to report.\n");

break;

case REPORT_INITIALIZING:

wprintf(L"Report is initializing.\n");

break;

}

}

  1. Add the following lines in _tmain right before the return statement at the end of the function:

C++

LOCATION_REPORT_STATUS status = REPORT_NOT_SUPPORTED;

// Get the status of this report type

if (FAILED(spLocation->GetReportStatus(IID_ILatLongReport, &status)))

{

wprintf(L"Error: Unable to obtain lat/long report status.\n");

return 1;

}

wprintf(L"Lat./long. Report status: ");

PrintReportStatus(status);

GetReportStatus returns the status of each report type. There are two location report types available:

  1. IID_ILatLongReport - Provides a subset or all of the following: latitude, longitude, altitude, error radius and altitude error
  2. IID_ICivicAddressReport - Provides a human-readable address: country/region, state/province, city, street, etc.
  1. Add the following functions in LocationHOL.cpp(you will need to put function declarations at the beginning of the file, right after global variables):

C++

void PrintLatLongReport(ILatLongReport *pLatLongReport)

{

DOUBLE latitude = 0, longitude = 0, altitude = 0;

DOUBLE errorRadius = 0, altitudeError = 0;

// Print the Latitude

if (SUCCEEDED(pLatLongReport->GetLatitude(&latitude)))

{

wprintf(L"Latitude: %f\n", latitude);

}

// Print the Longitude

if (SUCCEEDED(pLatLongReport->GetLongitude(&longitude)))

{

wprintf(L"Longitude: %f\n", longitude);

}

// Print the Altitude

if (SUCCEEDED(pLatLongReport->GetAltitude(&altitude)))

{

wprintf(L"Altitude: %f\n", altitude);

}

else

{

// Altitude is optional and may not be available

wprintf(L"Altitude: Not available.\n");

}

// Print the Error Radius

if (SUCCEEDED(pLatLongReport->GetErrorRadius(&errorRadius)))

{

wprintf(L"Error Radius: %f\n", errorRadius);

}

// Print the Altitude Error

if (SUCCEEDED(pLatLongReport->GetAltitudeError(&altitudeError)))

{

wprintf(L"Altitude Error: %f\n", altitudeError);

}

else

{

// Altitude Error is optional and may not be available

wprintf(L"Altitude Error: Not available.\n");

}

}

// This is a helper function that allows us to print a GUID to the // console in a friendly format

void PrintGUID(const GUID guid)

{

wprintf(L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n",

guid.Data1,

guid.Data2,

guid.Data3,

guid.Data4[0],

guid.Data4[1],

guid.Data4[2],

guid.Data4[3],

guid.Data4[4],

guid.Data4[5],

guid.Data4[6],

guid.Data4[7]);

}

// This is a helper function that allows us to print a SYSTEMTIME to // the console in a friendly format

void PrintTimestamp(const SYSTEMTIME time)

{

wprintf(L"Timestamp: YY:%d, MM:%d, DD:%d, HH:%d, MM:%d, SS:%d, "

"MS:%d\n",

time.wYear,

time.wMonth,

time.wDay,

time.wHour,

time.wMinute,

time.wSecond,

time.wMilliseconds);

}

  1. The location report object contains a set of values that can be queried using GetValue(), which expects a PROPERTYKEY (GUID + int) and returns a PROPVARIANT. In order to make life easier for developers, standard properties can be queried using functions such as GetLatitude(). Note: Not all standard values are support by all location providers.
  2. Add the following code to the end of the _tmainfunction, just prior to the last return statement:

C++

if (status == REPORT_RUNNING)

{

// This is our location report object

CComPtr<ILocationReport> spLocationReport;

// This is our LatLong report object

CComPtr<ILatLongReport> spLatLongReport;

// Get the current location report,

// then get the ILatLongReport interface from ILocationReport,

// then ensure it isn't NULL

if ((SUCCEEDED(spLocation->GetReport(IID_ILatLongReport,

&spLocationReport))) &

(SUCCEEDED(spLocationReport->QueryInterface(

IID_PPV_ARGS(&spLatLongReport)))) &

(NULL != spLatLongReport.p))

{

// Print the Timestamp

SYSTEMTIME systemTime;

if (SUCCEEDED(spLatLongReport->GetTimestamp(&systemTime)))

{

PrintTimestamp(systemTime);

}

// Print the Sensor ID GUID

GUID sensorID = {0};

if (SUCCEEDED(spLatLongReport->GetSensorID(&sensorID)))

{

wprintf(L"SensorID: ");

PrintGUID(sensorID);

}
// Print the report

wprintf(L"\nReport:\n");

PrintLatLongReport(spLatLongReport);

}

}

  1. This code calls GetReport() on ILocation, to request a report object of the desired type (latitude/longitude or civic address). Since GetReport() returns ILocationReport, which is the base interface, you need to call QueryInterface() on it to "cast it" to ILatLongReport. The function then prints the timestamp, sensor instance GUID that has generated this location report, and the location report values.
  2. Run eDLP:
  3. Zoom-in on the location you wish to make your default.
  4. Right-click the spot you wish to be your default latitude/longitude location.
  5. If available, the coordinates will display as a civic address that you could set as your default civic address by clicking the "Save Address" button.

  1. Compile and run the application.

Task 2 - Reading Location Asynchronously

In this task, you will read and display a civic address location asynchronously.

  1. Continue with the project from the previous task.
  2. Right-click the LocationHOL project --> Add --> Class. --> C++ --> C++ Class --> Add.
  3. Name the class CLocationEvents.
    This class implements the ILocationEvents interface that is required in order to receive notifications from the Location API.
  4. Navigate to the LocationEvents.h header file and overwrite its contents with the following code:

C++

#pragma once

#include "StdAfx.h"

class CLocationEvents :

public CComObjectRoot,

public ILocationEvents // We must include this interface so the //Location API knows how to talk to our object

{

public:

CLocationEvents(){}

virtual ~CLocationEvents(){}

DECLARE_NOT_AGGREGATABLE(CLocationEvents)

BEGIN_COM_MAP(CLocationEvents)

COM_INTERFACE_ENTRY(ILocationEvents)

END_COM_MAP()

// ILocationEvents

// This is called when there is a new location report

STDMETHOD(OnLocationChanged)(REFIID reportType,

ILocationReport* pLocationReport);

// This is called when the status of a report type changes.

STDMETHOD(OnStatusChanged)(REFIID reportType,

LOCATION_REPORT_STATUS status);

private:

// This is a private helper function that prints the civic address

// to the console.

// Empty fields are not printed

void PrintCivicAddress(ICivicAddressReport *pReport);

};

The ILocationEvents interface defines two functions:

  • OnLocationChanged: Fires when a location has changed --you need to determine if this is report type is the one you're interested in (that is, latitude/longitude vs. civic address report).
  • OnStatusChanged: Fires when a report's status has changed (for example, user has granted access to a sensor, a sensor was attached, GPS obtained a location, etc.)

Navigate to the LocationEvents.cpp file andreplace the file with the following code:
C++

#include"LocationEvents.h"

// This is called when there is a new location report

STDMETHODIMP CLocationEvents::OnLocationChanged(REFIID reportType,

ILocationReport* pLocationReport)

{

// If the report type is a Civic Address report (as opposed to

// IID_ILatLongReport or another type)

if (IID_ICivicAddressReport == reportType)

{

CComPtr<ICivicAddressReport> spCivicAddressReport;

// Get the ILatLongReport interface from ILocationReport

if ((SUCCEEDED(pLocationReport->QueryInterface(

IID_PPV_ARGS(&spCivicAddressReport)))) & (NULL !=

spCivicAddressReport.p))

{

wprintf(L"Civic address location changed:\n\n");

PrintCivicAddress(spCivicAddressReport);

}

}

return S_OK;

}

// This is called when the status of a report type changes.

STDMETHODIMP CLocationEvents::OnStatusChanged(REFIID reportType,

LOCATION_REPORT_STATUS status)

{

if (IID_ICivicAddressReport == reportType)

{

switch (status)

{

case REPORT_NOT_SUPPORTED:

wprintf(L"\nNo devices detected.\n");

break;

case REPORT_ERROR:

wprintf(L"\nReport error.\n");

break;

case REPORT_ACCESS_DENIED:

wprintf(L"\nAccess denied to reports.\n");

break;

case REPORT_INITIALIZING:

wprintf(L"\nReport is initializing.\n");

break;

case REPORT_RUNNING:

wprintf(L"\nRunning.\n");

break;

}

}

return S_OK;

}

// Prints the fields of a civic address location report.

// Empty fields are not printed.

void CLocationEvents::PrintCivicAddress(ICivicAddressReport

*pCivicAddressReport)

{

HRESULT hr = S_OK;

DWORD dwDetailLevel;

CComBSTR bstrAddress1;

CComBSTR bstrAddress2;

CComBSTR bstrPostalCode;

CComBSTR bstrCity;

CComBSTR bstrStateProvince;

CComBSTR bstrCountryRegion;

hr = pCivicAddressReport->GetAddressLine1(&bstrAddress1);

if ((SUCCEEDED(hr)) & (bstrAddress1.Length() != 0))

{

wprintf(L"\tAddress Line 1:\t%s\n", bstrAddress1);

}

hr = pCivicAddressReport->GetAddressLine2(&bstrAddress2);

if ((SUCCEEDED(hr)) & (bstrAddress2.Length() != 0))

{

wprintf(L"\tAddress Line 2:\t%s\n", bstrAddress2);

}

hr = pCivicAddressReport->GetPostalCode(&bstrPostalCode);

if ((SUCCEEDED(hr)) & (bstrPostalCode.Length() != 0))

{

wprintf(L"\tPostal Code:\t%s\n", bstrPostalCode);

}

hr = pCivicAddressReport->GetCity(&bstrCity);

if ((SUCCEEDED(hr)) & (bstrCity.Length() != 0))

{

wprintf(L"\tCity:\t\t%s\n", bstrCity);

}

hr = pCivicAddressReport->GetStateProvince(&bstrStateProvince);

if ((SUCCEEDED(hr)) & (bstrStateProvince.Length() != 0))

{

wprintf(L"\tState/Province:\t%s\n", bstrStateProvince);

}

hr = pCivicAddressReport->GetCountryRegion(&bstrCountryRegion);

if (SUCCEEDED(hr))

{

// Country/Region is an ISO-3166 two-letter code.

wprintf(L"\tCountry/Region:\t%s\n\n", bstrCountryRegion);

}

}

  1. Navigate to the LocationHOL.cpp file andadd the following #include after StdAfx.h:

C++

#include"LocationEvents.h"

  1. Navigate to the end of the _tmain method and add the following code right before the final return statement:

C++

// This is our callback object for location reports

CComObject<CLocationEvents>* pLocationEvents = NULL;

// Create the callback object

if (FAILED(CComObject<CLocationEvents>::CreateInstance(

&pLocationEvents)) || NULL == pLocationEvents){

wprintf(L"Error creation Location events sink.\n");

return 1;

}

pLocationEvents->AddRef();

// Register for reports for ICivicAddressReport

if (FAILED(spLocation->RegisterForReport(pLocationEvents,

IID_ICivicAddressReport, 0)))

{

pLocationEvents->Release();

wprintf(L"Error registering for report.\n");

return 1;

}

wprintf(L"Press ENTER to exit.\n");

getchar();

if (NULL != pLocationEvents)

{

// Unregister for reports

spLocation->UnregisterForReport(IID_ICivicAddressReport);

pLocationEvents->Release();

}

  1. Compile and run the application.
  2. With the help of eDLP:
  3. Right-click different locations on the map, preferably in populated areas that are translatable to civic addresses. If the location you clicked is translatable into an address, you will see it along with the "Save Address" button.
  4. Click various locations and repeat the process several times to see that the application is notified about updates to the civic address location every time.

Summary

In this lab, you have experimented with the Windows7 Location API by writing a simple application that reads location reports both synchronously and asynchronously. You've read both latitude/longitude reports and civic address reports.

Integrating a production-quality application with Windows7 Location support might require slightly more work than you have done in this lab, but you are now sufficiently prepared to begin this quest on your own.

1