位置 - 本机(动手实验)
Windows 7 传感器和位置平台
Windows 7中的传感器和位置平台使得应用程序可以适应于当前所处环境并且改变它们的外观,感觉,和行为。例如可以做到:
• 在阳光普照的户外使用一台移动PC(如笔记本或平板电脑),应用程序能自己增加亮度和对比度,并降底颜色饱和度以提高可读性。应用程序能够提供与当前位置相关的信息,如附近的餐厅。可以使用3D加速和游戏操控器
• 应用程序可以使用人类行为传感器
图 1
改进后的MSDN阅读器使用环境光传感器改变对比度,尺寸和色彩饱和度。
传感器和位置平台对比专有解决方案有很多优势:
• 硬件独立:无需学习和开发特定的API; 所有类型的传感器都很容易操作。
• 隐私: 由于微软认为传感器信息和位置数据是私人的和个性化认证的信息,所有的传感器默认为不可用状态。任何时候通过控制面板能启用或关闭传感器。应用程序可能会提示一个安全请求界面来启用相应的传感器。
• 应用程序共享:多个应用程序可同时使用同一传感器的数据。
• 寻址简单:Location API使得你可以得到位置而不用关心特定的获取信息的机制。Location API自动选择最精确的可用传感信息。也无需实现GPS如NMEA。
目标
本动手实验中将会学习到如何在应用程序中使用Windows 7Location API,包括:
• 同步获取城市地址位置报表(location report)
• 异步获取城市地址位置报表
• 当所访问的位置信息未被许可时请求用户的授权
系统要求
为了完成本实验须具有以下的软件
• Microsoft Visual Studio 2008 SP1
• Windows 7 RC或更高版本
• Windows 7 RC SDK 或更高版本
• 城市地图提供者 (eDLP) –源下载版本: https://code.msdn.microsoft.com/SensorsAndLocation
练习:获取位置报表在当前练习中,你将创建Win32应用程序同步读取和打印(经纬度)位置和城市位置信息,然后定阅位置更新报表。使用eDLP修改默认位置(包括经纬度和城市名),更新程序的位置信息以触发相应的事件处理程序,打印更新后的位置。
开始本练习之前,先要确保你没有连接任何物理的或虚拟的位置传感器。这将会覆盖默认的位置导致错误的结果。为此需要进行如下操作:
1. 打开“位置与感应器”(Location and Other Sensors )窗口:
a. 点击Windows start按钮
b. 在搜索框输入Sensor:
打开Visual Studio 开始此练习。创建一个新的Win32控制台应用程序项目(Win32 Application)取名为LocationHOL: 执行File --> New --> Project... --> Visual C++(也可能在其它语言类别下)--> Win32 --> Win32 Console Application。
• 将程序保存在一个可读写的目录下如c:\Temp。
• 取名为LocationHOL.
帮助
为了简便起见,本应用程序没有使用Wind32开发的最佳实践,也没有遵循面向对象原则和COM开发原则。
任务 1 –同步读取位置信息
在这个任务中,你将会同步读取并显示经纬度位置信息。查询报表类型状态以及请求用户的许可以使用用户信息。
1. 右击LocationHOL 工程。
2. 点击 Properties。
3. 执行Linker --> Input --> Additional dependencies。
4. 向列表中添加 locationapi.lib 。
5. 点击OK关闭对话框。
6. 打开 StdAfx.h.
7. 添加如下#include语句:
C++
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
#include <LocationAPI.h>
8. 打开 LocationHOL.cpp.
9. 在_tmain 前#include之后添加如下行。(这段代码初始化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 this application. This also does CoInitialize for us
10. 在_tmain方法的返回语句之前插入如下代码:
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 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");
}
11. 这段代码创建Location API的主要对象Ilocation。然后显示一个对话框向用户请求使用此位置信息的许可。
12. 为了打印所有LOCATION_REPORT_STATUS值的描述信息,在LocationHOL.cpp文件添下如下函数。(需要在文件头部的全局变之量后添加函数声明):
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;
}
}
13. 添加如下代码到_tmain函数的末端返回语句的前面:
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 返回所有类型报表的状态。有两种可用的位置报表类型:
a. IID_ILatLongReport - 提供所有如下信息或一个子集:纬度,经度,海拔高度,误差半径和高度误差
b. IID_ICivicAddressReport –提供人类可读的地址:国家/地区,州/省,城市,街道等。
14. 向LocationHOL.cpp文件添加如下函数(需要在文件头部全局变量之后声明此函数)
15.
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);
}
16. 位置报表对象包含一组可以用GetValue()获取到的值,此方法需要PROPERTYKEY(GUID+int)参数。为了使开发人员使用更简便,可以使用 GetLatitude()方法查询到标准属性。注意:位置信息提供者并不支持所有标准值。
17. 添加如下代码到_tmain函数的末端返回语句的前面:
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);
}
}
18. 这段代码调用Ilocation接口的GetReport()方法以请求一个你所需类型的报表对象(经纬度或城市地址)。当GetReport() 方法返回IlocationReport接口,需要为其调用QueryInterface()以转换成IlatLongReport。此方法然后打印时间戳,获取本报表的传感器实例的GUID和位置报表的值。
19. 运行 eDLP:
a. 放大你想要设为默认值的位置。
b. 右击你要设为默认(经纬度)位置的点。
c. 如果可以,坐标将会显示为城市地址,可点击“Save Address”按钮将其设置为你的默认城市地址。
20. 编译并运行应用程序。
任务2-- 异步读取位置信息
在本任务中将会异步地读取城市地址的位置信息。
1. 继续上一个任务中的工程。
2. 右击工程LocationHOL 执行--> Add --> Class. --> C++ --> C++ Class --> Add。
3. 将类取名为CLocationEvents 。
这个类实现了ILocationEvents接口以接收来自Location API的通知。
4. 打开 LocationEvents.h头文件,用如下代码替换掉现有内容:
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);
};
ILocationEvents 接口定义如下功能:
• OnLocationChanged: 当位置变化时被触发—你需要决定报表的类型是否是你所需要的(经纬度或城市地址报表)
• OnStatusChanged: 当报表状态更新时被触发(例如,用户授予传感器访问权限,传感器已连接,GPS获取到一个位置等等。)
打开 LocationEvents.cpp文件,将文件内容用如下代码替换:
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);
}
}
5. 在LocationHOL.cpp 文件中StdAfx.hp之后添加如下#include语句:
C++
#include "LocationEvents.h"
6. 在_tmain方法末端返回语句前添加如下代码:
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();
}
7. 编译并运行应用程序。
8. 使用eDLP的帮助:
a. 在地图中不同位置右击,最好是人口稠密的能被翻译的城市地区。如果你点击的位置是可转换的,则可以看到一个“Save Address”按钮。
b. 多次点击不同的地方重复这些操作以检查应用程序在每次点击都能得到更新了的关于信息。
总结
本实验中通过一个简单的应用程序体验了使用Windows 7的位置API(Location API)同步及异步地获取位置信息。读取经纬度报表和城市地址报表。
将Windows 7位置支持集成到产品级质量的应用程序中可能会需要比本实验中多一些工作,但是现在你已经有足够多的准备开始你的探索了。