Exercise 1: Obtaining Location Reports
In this exercise, you will create a Win32 application that reads 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's location 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:
Open “Location and Other Sensors” windows:
- Click the Windows Start button
Type sensor in the search box:
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 project LocationHOL.
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.
- Right-click the LocationHOL project.
- Click Properties.
- Go to Linker --> Input --> Additional dependencies.
- Add locationapi.lib to the list.
- Click OK to close the dialog.
- Navigate to StdAfx.h.
Add the following #include:#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <LocationAPI.h>
- Navigate to LocationHOL.cpp.
Add the following lines before _tmain but after the #includes (this code will initialize ATL/COM):// Array of report types of interest.
IID REPORT_TYPES[] = { IID_ILatLongReport, IID_ICivicAddressReport };
class CInitializeATL : public CAtlExeModuleT<CInitializeATL>{};
CInitializeATL g_InitializeATL;
// Initializes ATL for this application. This also does CoInitialize for us
- Insert the following code into _tmain, before the return: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 the types 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");
}
- 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.
- 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): 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;
}
}
- Add the following lines in _tmain right before the return statement at the end of the function: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:
- IID_ILatLongReport -Provides a subset or all of the following: latitude, longitude, altitude, error radius and altitude error
- IID_ICivicAddressReport -Provides a human-readable address: country/region, state/province, city, street, etc.
- Add the following functions in LocationHOL.cpp (you will need to put function declarations at the beginning of the file, right after global variables):
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 formatvoid 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);
}
- 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.
Add the following code to the end of the _tmain function, just prior to the last return statement: 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);
}
}
- This code calls GetReport()onILocation,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.
Run eDLP:
- Zoom-in on the location you wish to make your default.
- Right-click the spot you wish to be your default latitude/longitude location.
- 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.
Compile and run the application.
Task 2 - Reading Location Asynchronously
In this task, you will read and display a civic address location asynchronously.
- Continue with the project from the previous task.
- Right-click the LocationHOL project --> Add --> Class. --> C++ --> C++ Class --> Add.
- Name the class CLocationEvents. This class implements the ILocationEventsinterface that is required in order to receive notifications from the Location API.
- Navigate to the LocationEvents.h header file and overwrite its contents with the following code:#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.) #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);
}
}
Navigate to the LocationHOL.cpp file and add the following #include after StdAfx.h:#include "LocationEvents.h"
Navigate to the end of the _tmain method and add the following code right before the final return statement:// 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();
}
- Compile and run the application.
With the help of eDLP:
- 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.
- 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.
|
|