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) –源下載版本: http://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位置支持集成到產品級質量的應用程序中可能會需要比本實驗中多一些工作,但是現在你已經有足夠多的准備開始你的探索了。