在托管代码中使用Skyhook Wireless XPS定位服务(译文)
By S.F.
本文链接 https://www.kyfws.com/news/using-the-skyhook-wireless-xps-positioning-service/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 23 分钟阅读 - 11333 个词 阅读量 0在托管代码中使用Skyhook Wireless XPS定位服务(译文)
原文地址:https://www.codeproject.com/Articles/32301/Using-the-Skyhook-Wireless-XPS-Positioning-Service
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
包装程序和示例程序演示了Skyhook Wireless XPS SDK(使用GPS,WiFi定位和Celltower定位的混合定位系统)的使用
介绍
Skyhook无线以提供基于WiFi的定位而闻名.该服务在iPhone,用于相机的Eye-Fi地理标签存储卡上使用,并且可用于Windows桌面操作系统,OS X,Linux,Symbian和Windows Mobile.最近,Skyhook Wireless更新了SDK,以利用蜂窝塔和GPS定位结合了这三种定位技术的信息,为应用程序提供了难以置信的大覆盖范围.对于Windows Mobile,可通过非托管库访问该服务.通过阅读Skyhook Wireless开发人员论坛,我发现托管开发人员无法访问100%的功能.我决定自己创建一个包装器.. 我第一次创建包装器的尝试失败了,我很快发现了为什么其他开发者没有成功的原因. Compact Framework中可用的互操作功能不是使用常规技术制作包装程序所需要的.在年底休假后的一段时间里,我设法创建了一个包装程序,以使该服务的功能100%可用.在本文中,我将重新讨论我去年发布的程序,该程序会自动响应手机所在位置的SMS请求,并在其中添加基于XPS的功能使用Microsoft的Virtual Earth网络服务进行定位和地图绘制. 在我最初发表本文时,Skyhook Wireless SDK仅执行基于WiFi的定位.他们现在已经更新了客户端,以利用GPS和Cell Tower的位置,从而使开发人员可以通过单个函数调用从最精确的源中获取位置数据.
技术与要求
此处提供的示例程序需要运行Windows Mobile 5或Windows Mobile 6的Windows Mobile Professional设备.设备上必须存在.NET Framework版本3.5,并且最后一个示例程序还使用SQL Server CE版本3.5.此外,您将需要一部具有SMS计划和无限数据计划的手机.示例程序将通过您的数据连接下载地图图像,并且图像可以快速消耗数据.这里介绍的程序旨在放大到800x800屏幕.
条款
在本文中,我将多次使用一些术语.
- WPS
- WPSAPI
- WpsProxy
- XPS
设置您的开发者帐户
我在示例代码中调用的某些Web服务需要使用帐户.在所有情况下,开发人员帐户都是免费的.开发人员帐户可以拨打的电话数量受到限制.尽管允许的呼叫数量远远超过一个人的数量,但如果我在分发此代码时嵌入了我的帐户信息,我相信可以很快达到允许的最大通话量(而且我敢肯定您将理解为什么我不希望共享我的帐户信息). 此处的示例程序将在一组注册表项中查找帐户信息.将帐户信息存储在注册表中允许一次指定该信息,然后将其提供给所有需要该信息的程序.如果您尝试在未先设置自己的开发人员帐户的情况下在此处运行程序,则它们将无法工作.要将帐户信息保存到设备,请在名为" SetMyKeys"的项目中运行代码. 您可以通过访问以下每个URI来设置开发者帐户:
- 天钩无线
- 虚拟地球/MapPoint
为什么要使用XPS定位
GPS设备可以提供令人难以置信的精确位置信息.尽管具有分辨率,但我认为GPS设备有两个主要缺点.他们可能需要一些时间来获取定位,并且在许多情况和环境下,GPS设备根本无法获取定位(在"城市峡谷"中或在建筑物内部时).即使有清晰的GPS信号,也不是所有Windows Mobile设备都具有GPS硬件.通过使用其他定位技术,我们既可以扩大潜在客户群,又可以提高计划快速获取位置信息的能力.
XPS定位如何工作?
Skyhook Wireless的XPS使用三种定位技术. WiFi,手机信号塔和GPS. WiFi路由器不断发送其ID号(MAC地址). Skyhook Wireless已积累了一个庞大的数据库,其中包含这些路由器ID以及可以接收其信号的位置.他们聘请了500多名全职驾驶员来带走WiFi信号,以将其与GPS位置相关联.此外,Skyhook Wireless还具有一个蜂窝塔ID及其位置的数据库.当无法接收到GPS信号时,XPS将提交附近路由器和基站的ID,并返回您的位置. Skyhook Wireless在本页上解释了该技术. WPSAPI库还利用了智能缓存.开发人员可以指定缓存大小,WPSAPI会将数据库的子集下载到本地设备的存储中.下载之后,在用户移到缓存信息覆盖的区域之外之前,WPSAPI无需进行其他往返Skyhook Wireless服务器的往返操作即可获取位置信息.除了获取位置之外,该服务还可以对位置进行地理编码,从而提供用户所在的州,城市甚至街道.
我所在区域的覆盖范围如何?
找到XPS客户端无法找到位置的位置非常困难.其通过GPS,手机信号塔和WiFi定位的覆盖范围非常出色.如果您想查看Skyhook Wireless在您所在地区拥有的WiFi覆盖类型,则可以查看覆盖图.乍一看,美国的地图看起来有点稀疏.然后,我意识到很多人口位于都会区或其附近,然后将覆盖图与人口密度图进行了比较.以这种方式看待事物,我发现覆盖范围非常好.我住在美国乔治亚州亚特兰大市郊的郊区.我沿着从房子到餐厅的7英里长的车道行驶,手机通过WPS轮询我的位置,而只遇到了一段路,手机无法看到WiFi信号来推断我的位置.即使在WiFi覆盖范围之外的区域,也请记住XPS客户端还将使用手机信号塔和GPS位置.
获取Skyhook无线SDK
Skyhook Wireless SDK是免费下载的,只需要您注册开发者帐户.当您打开帐户时,您可以使用用户ID和领域来指定您的身份.没有密码.领域只是您决定用来标识公司或集团的名称.它可以是实体名称,域名或您选择的多种名称中的任何一种.注册并登录后,您将看到可以下载的许多不同的SDK和文件.您将要为本文下载两个文件.一种是用于手机的Windows Mobile 5和6 PocketPC(臂)和用于[Windows Mobile PocketPC的虚拟GPS] (用于手机的(手臂).)(http://www.skyhookwireless.com/developers/downloads/nmea-2.7.0_05-ppc.cab)它可用于许多其他平台,但出于本文的考虑,我只会考虑Windows Mobile下载.
无论您是否使用本文所附的项目,如果使用的是Skyhook Wireless WPSAPI SDK,您都需要更新WpsProxy
类的属性,以便它知道头文件和库的位置文件用于SDK.要在Visual Studio中更改这些设置,请右键单击使用WPSAPI库的C/C ++项目,然后选择"属性".其他包含目录的设置在"配置属性"->" C/C ++"->“常规"下.可以在"配置属性”->“链接器”->“其他库目录"下找到"其他库"的设置.
立即使用WPS,而无需更改基于GPS的程序
如果您已经有使用GPS的应用程序,则可以通过虚拟GPS应用程序立即使用Skyhook Wireless WPS服务.这对于没有GPS硬件的设备特别有用.虚拟GPS应用程序模拟GPS设备.通过COM端口或GPS中间驱动程序与GPS设备通信的软件可以使用虚拟GPS.如果您的设备具有GPS硬件,则可以将虚拟GPS配置为同时使用GPS硬件和WPS来获取您的位置.您的软件将在可用时接收GPS信息,而在GPS信息不可用时将回退到WPS信息.由于Skyhook Wireless已经提供了详细介绍如何设置虚拟GPS的文档,因此在此我不会详细介绍虚拟GPS的设置.
P/调用WPS代码的挑战
当我第一次尝试为WPSAPI创建包装器时,采用的路径与我调用的其他本机代码采用的路径相同.使用WPSAPI,我发现这不合适. WPSAPI使用ANSI字符串(单字节字符串),而Compact Framework使用Unicode字符串(2字节).在这种情况下,当在桌面框架上编排字符串时,我们可以通过将带有UnmanagedType.LPStr的MarshalAs属性添加到有问题的字符串字段中,来自动从一种字符串类型转换为另一种类型.但是,Compact Framework不支持该值. 进行包装时遇到的另一个复杂问题是,正在从WPS API传递复杂的结构.一个结构可以包含一个指向另一个结构的指针,该指针可以包含一个指向另一个实体的结构.当试图传递这种性质的结构时,CLR通常会返回人们熟悉但又不具描述性的” NotSupportedException".这两个问题共同导致托管开发人员难以访问WSP API.
重新思考WPSAPI库的接口
在遇到字符串转换和复杂结构传递的障碍之后,我想:“如果WPSAPI使用Unicode字符串并传递平面结构,那会很好吗?“由于我没有WPSAPI库的代码,因此无法更改该库的字符串实现.因此,我制作了自己的本机DLL来调用WPSAPI功能.此DLL在项目WpsProxy中.该项目用C编写,主要作为翻译层存在,位于托管代码和本机API之间.此层处理Unicode和ANSI字符串的转换,并将复杂的WPS结构转换为平面结构,可以从.NET语言中更轻松地对其进行操作.
WpsProxy的托管包装器在项目J2i.Net.WiFiPositioning中的类名为WPS. WPS中包含的几乎所有功能都通过包装程序公开.唯一的例外是存在两个用于释放由WPS调用分配的资源的函数.我没有将非托管资源传递给托管代码并使开发人员负责取消分配资源,而是从托管代码内部分配了必要的内存,然后将其传递给代理以进行填充.填充信息后,WpsProxy
代码将取消分配由WPS
API调用分配的内存.
以下是通过代理传递的最简单调用之一的示例:
extern "C" WPSPROXY_API void SetServerUrl(const LPWSTR url)
{
//Convert unicode string to ANSI string.
//this call allocates memory
LPSTR szUrl = wcstombs(url);
//call WPSAPI function with ANSI string
WPS_set_server_url(szUrl);
//Free the ANSI string
delete szUrl;
}
包装最困难的功能是涉及回调的功能.通常,可以通过将托管委托传递给本机函数并允许运行时处理两个代码实体之间的编组,来进行本机代码的回调.但是,如前所述,紧凑框架无法处理字符串的自动转换和需要传递的结构的转换.要变通解决此问题,调用需要回调的WPSAPI函数时,它将在我的WpsProxy项目中收到指向该函数的指针.然后,该函数将回调从托管代码收到的委托.当托管代码返回值时,WpsProxy组件将获取该返回值,并将其传递回WPSAPI.在WPSAPI文档中将回调函数作为可选列出的情况下,它仍将从WpsProxy接收回调函数.如果WpsProxy没有要调用的托管委托,它只是将控制权返回给WPSAPI. 以下是托管代码在WpsProxy中调用的功能之一.来自托管代码的委托在参数回调中传递.函数主体执行从两字节字符到单字节字符的转换,然后调用WPSAPI函数.
extern "C" WPSPROXY_API WPS_ReturnCode PeriodicLocation(
LPWSTR userName,
LPWSTR realm,
WPS_StreetAddressLookup lookupType,
unsigned long period,
unsigned long iterations,
WiFiLocationDelegate* callback
//This is the delegate passed in from managed code
)
{
//perform conversion from double byte strings to single byte strings
WPS_SimpleAuthentication simpleAuthentication;
simpleAuthentication.realm = wcstombs(realm);
simpleAuthentication.username = wcstombs(userName);
WPS_ReturnCode result;
result = WPS_periodic_location(
&simpleAuthentication,
lookupType,period,
iterations,
WiFiLocationCallback, //Function predefined within WPSAPI
callback //managed delegate. Passed as data parameter
);
//delete userName;
//delete realm;
return result;
}
使回调工作所需的WpsProxy代码如下所示.我在这里删除了很多代码,因为此代码完成的其他工作并不像了解如何实现回调机制那样重要. WPSAPI执行此函数的回调,然后此函数将回调到托管代码(如果托管代码已提供委托),或者如果未传递任何委托,则不执行回调.
WPS_Continuation WiFiLocationCallback(void* arg, WPS_ReturnCode result,
const WPS_Location* location)
{
WPS_Continuation retCode;
retCode = WPS_STOP;
if(arg!=NULL) // if a callback function has been passed
{
WiFiLocationDelegate delegateMethod = (WiFiLocationDelegate)arg;
//Code deleted from here for clarity. At the end of the work that is
//performed by the code that I've deleted here the callback function
//is called.
retCode = delegateMethod(result,location->latitude, location->longitude,
location->hpe, location->nap, location->speed,
location->bearing, streetNumber, addressLine,
city, stateName, stateCode, postalCode, county, region,
countryName, countryCode,province);
}/*arg!=null*/
return retCode;
}
使用包装器
要使用包装器,必须对J2i.Net.WiFiPositioning.dll程序集进行引用.如果将此引用添加到项目中,部署该项目,然后尝试调用WPS函数,则会收到有关找不到WPSPROXY.dll的错误.您需要手动将该程序集复制到设备上的目标文件夹.执行完此操作后,再次尝试运行项目,即使文件存在,也会出现完全相同的错误.发生此错误的原因是,除非有可用的WpsProxy.dll文件,否则无法加载.您还需要将WPSAPI.DLL文件复制到目标设备. 如果满足上述要求,则可以通过实例化WPS对象然后调用一个函数来获取位置来获取位置.在名为SimpleClient的项目中演示了此基本功能.该项目的代码如下所示.
// From the project SimpleClient
Wps _wps = new Wps(_userName, _realm);
void UpdatePosition(WiFiLocation position)
{
//...this function list out the values of the fields on the WiFiLocation
//object passed to it.
}
private void miFullAddress_Click(object sender, EventArgs e)
{
WiFiLocation loc = _wps.GetWiFiLocation(StreetAddressLookupType.FullStreet);
this.UpdatePosition(loc);
}
void miPosition_Click()
{
WiFiLocation loc = _wps.GetWiFiLocation(StreetAddressLookupType.NoStreet);
this.UpdatePosition(loc);
}
WPS类具有可以调用的其他方法.在这里,我没有详细说明所有方法的目的.该信息可以在Skyhook无线站点上的WPSAPI文档中找到.我在WPS类上放置的方法名称与WPSAPI库中的函数名称并不完全相同,但是它们是相似的,并且不难将每个” Wps"类方法映射到WPSAPI函数.在接下来的几周中,我将在Skyhook Wireless讨论组中提供更详细的文档.
两种重载方法(” WPS.GetIPLocation"和" Wps.GetWiFiLocation")允许使用两种样式处理无法获取位置的情况.您可以选择通过返回代码或通过处理异常来检测这些情况.
//These methods will throw an exception if a location cannot be acquired
public WiFiLocation GetWiFiLocation(StreetAddressLookupType lookupType);
public IPLocation GetIPLocation(StreetAddressLookupType lookupType)
//These methods will return an error code if a location cannot be acquired
public ResultCode GetWiFiLocation(StreetAddressLookupType lookupType,
out WiFiLocation location)
public ResultCode GetIPLocation(StreetAddressLookupType lookupType,
out IPLocation location)
上面的函数调用WiFi
Location``的返回实例. WiFi Location和IP Location都是从Location类派生的类.这两个类的WPSAPI等效项彼此独立,但是对于我的包装器实现,我使它们派生自一个公共类.
如果需要定期更新用户的位置,则可以让WPSAPI合并用户的位置,并根据您选择的频率将其位置报告给您.我已经通过BeginPeriodicLocation
方法访问了此功能.这是一个阻塞的呼叫;直到WPS对象没有其他要报告的位置,呼叫者才可以重新获得控制权.如果您不想阻塞主线程,则需要为定期更新创建自己的线程.
ResultCode rc = _wps.BeginPeriodicLocation(
StreetAddressLookupType.NoStreet, //leve of positioning detail required
5000, //period (in milliseconds) desired between updates
10 //The number of times that a position is to be reported
);
缓存位置数据
Skyhook Wireless通过称为"平铺"的功能使路由器ID及其位置的缓存可用.开发人员可以指定整个缓存可以消耗多少内存,以及可以在单个会话中从Skyhook Wireless服务器下载多少数据.您将需要指定将在其中保存缓存数据的文件路径.以下内容将在" \ temp"文件夹中最多存储少于4兆的缓存数据.每个会话最多下载500,000字节.
rc = _wps.BeginTiling("\\temp\\", 500000, 4000000);
请注意,仅当您仅请求位置且没有地址数据时,平铺才起作用.请求地址数据将始终导致对Skyhook Wireless服务器的回叫.
Microsoft的基于位置的Web服务
有很多基于位置的Web服务供您使用.实时搜索Web服务是最容易使用的服务之一.我不会在this文章中描述实时搜索,但是如果您要开始使用,thisCodeProject文章.最好将实时搜索位置相关功能描述为类似于电话簿.最重要的是,该服务易于使用. MapPoint和Virtual Earth Web服务为您提供了大量信息,但使用起来却需要更多的努力. MapPoint和Virtual Earth服务的开发人员帐户是同一帐户,甚至在某些情况下,我甚至发现我可以将程序从一个服务重定向到另一个服务,并且该程序将继续按设计工作,而无需任何其他代码被改变.
服务部门
虚拟地球和MapPoint Web服务已被划分为功能组,而不是在单个整体Web服务中提供该服务的功能.
- 共同
- 地理编码
- 意象
- 路线
- 搜索 在这里的示例程序中,我对图像,搜索,地理编码和通用服务进行了一些调用.这是虚拟地球WSDL的链接.
使用搜索服务
打开一个名为MapPoint.SimpleClient的解决方案.此项目通过搜索使用NavTech的数据来搜索属于选定类别的企业(该程序已预先配置为可与北美数据源一起使用;如果您在欧洲,则需要将程序的设置更改为请改用欧洲数据源).选择一个搜索类别,然后选择"搜索"按钮,您将看到您所在区域内属于该类别的企业列表.
private void miSearch_Click(object sender, EventArgs e)
{
MapPoint.Service.FindServiceSoap findService =
new MapPoint.SimpleClient.MapPoint.Service.FindServiceSoap();
//To prevent to annoying URI Exception in the debug output. Optional.
findService.Proxy = System.Net.GlobalProxySelection.GetEmptyWebProxy();
//Specify our credentials for authorization
findService.Credentials = new NetworkCredential(
_registrySettings.MapPointAppID,
_registrySettings.MapPointPassword
);
findService.PreAuthenticate = true;
FindFilter findFilter = new FindFilter();
//http://msdn.microsoft.com/en-us/library/cc534903.aspx
findFilter.EntityTypeName = _entityDictionary[dupQuery.Text];
//only display the display name for each search result
findFilter.PropertyNames = new string[]{"DisplayName"};
MapPoint.Service.LatLong latLong = new MapPoint.Service.LatLong();
latLong.Latitude = Double.Parse(txtLatitude.Text);
latLong.Longitude = Double.Parse(txtLongitude.Text);
MapPoint.Service.FindNearbySpecification findSpecification =
new MapPoint.Service.FindNearbySpecification();
findSpecification.DataSourceName = _registrySettings.MapPointDataSource;
//If the user has not selected a datasource then we will
//use the NavTech North American datasource
if (findSpecification.DataSourceName.Length == 0)
findSpecification.DataSourceName = "NavTech.NA";
findSpecification.LatLong = latLong;
findSpecification.Distance = 100;
findSpecification.Filter = findFilter;
FindResults resultList = null;
try
{
resultList = findService.FindNearby(findSpecification);
if (resultList.Results != null)
{
DisplayResults(resultList.Results);
}
}
catch(Exception exc)
{
System.Diagnostics.Debug.WriteLine(exc.ToString());
}
}
显示地图
从"虚拟地球"中检索地图是一个多步骤过程.使用通用服务,必须获取令牌并与每个请求一起提交.将令牌视为与会话cookie相同的事物.它必须针对其他许多请求提交,并且在一定时间后过期.获得令牌后,您可以请求地图URI;该呼叫将获取您想要在地图中心的地理位置的坐标,缩放级别以及(可选)要在地图上显示的图钉的坐标.结果是一个字符串,其中包含图像的URI.您获取地图的第三个请求是下载您收到的URL上的图像.要逐步完成这些步骤,请打开项目J2i.Net.MapPoint.RenderMap. J2i.Net.MapPoint.RenderMap具有对两个服务的Web引用.公共服务和影像服务.由于我使用的是开发帐户而不是商业帐户,因此我要求的地图上将带有"登台"水印.
重温FindMe
2007年8月,我发布了一个基于GPS的Windows Mobile程序,当收到特殊短信时,它会通过电子邮件或SMS回复我的职位.该程序的缺点之一是,为了利用程序的响应,您必须有权使用完整的浏览器.即使是现在,完整的浏览器也不是Windows Mobile设备上的标准组件(尽管在接下来的几个月中它将有所变化).在文章的最后,我表示有兴趣在程序中添加地图,以便无需计算机即可使用其响应. 该程序的第一个版本只会发送手机的位置,而永远不会收到任何人的位置信息.在此更新版本中,程序将发送位置并显示其他设备接收到的位置信息.我还取消了请求某人的位置所需的PIN码的使用.位置信息的权限完全通过添加到联系人条目中的属性来控制.
使用仿真器省钱
虽然您家中有几台Windows Mobile手机可用于测试该程序,但是您将需要在模拟器上测试该程序的某些部分.在我看来,对于在真实硬件上进行所有测试,SMS太昂贵了.在美国这里,消息的发送者和接收者都需要付费.目前,一条SMS消息的价格在0.20到0.30美元之间(让我们说它们的价格为0.25美元).在一个电话向另一个电话发送消息然后得到响应的一次交互中,总共要计入其中的四个费用.发送电话在发送请求时会产生费用,接收电话会为接收请求而产生另一笔费用,然后接收电话会做出响应以引起另一笔费用,而原始电话会收到另一种费用.一次互动费用为1.00美元.我的手机上有一个短信计划,允许在我开始收费之前发送有限数量的短信.但是,消耗我分配的消息不会花费太多时间 模拟器在这里有很大帮助.可以使用Windows Mobile Cellular模拟器测试与SMS相关的代码,并且不会产生任何费用. WPSAPI和手机信号塔位置代码无法在仿真器中使用,但是可以通过使用FakeGPS(GPS仿真器)或在仿真器中进行测试之前对位置进行硬编码来轻松解决此问题,然后再对实际硬件进行最终测试.
通过WiFi调试
手机中的WiFi适配器通常是关闭的.在调试该程序时打开它,我遇到了一个令人讨厌的行为.手机中的WiFi适配器已连接到家庭网络,并且当我开始调试时,Visual Studio正在通过WiFi而不是通过USB电缆与设备进行通信.我通过断开USB电缆来验证这一点.断开连接后,我仍然能够单步执行代码并启动和停止程序.这听起来像是一项不错的功能,但是通过WiFi进行调试的速度非常慢.在调试该程序时,我从电话中删除了路由器的设置,以防止出现这种情况.
代码组织
该代码分为三个不同部分:接口,UI类和处理类.我为几个(尽管不是全部)类定义了接口,以确保某些类之间的关系不是太紧密.这使换出实现变得容易一些.例如,有一个名为" IGeoManager"的接口,该接口声明了一些用于地理编码和检索地图的方法. " VirtualEarthGeoManager"实现了此接口,以从Microsoft的Virtual Earth Web Service检索地图.如果我决定对地图和地理编码使用其他提供程序,则只需要定义另一个实现此接口的类并实例化它即可,而不是VirtualEarthGeomanager
.
通常,大多数UI类除了您期望在表单中找到的内容之外,没有太多要说的. MapDisplay类是一个例外.这是一个自定义类,用于以手指友好的方式显示地图.提供给该控件的地图图像通常会大于控件本身的大小,因此您可以在该视图内拖动地图.一些UI类还利用异步加载.列出我们的所有联系人或下载地图之类的任务可能需要一些时间,而以同步方式执行这些任务会导致程序出现冻结状态.在完整的框架上,可以通过委托上的BeginInvoke和EndInvoke方法异步调用方法.这些方法存在于Compact Framework委托中,但未实现.尝试调用它们会导致" NotImplemented"异常.因此,我没有使用BeginInvoke,而是使用ThreadPool类在另一个线程上运行任务,并使用Control.Invoke在任务完成后将UI更改编组回主线程.
处理类都在一个名为Common的文件夹中.该功能分为几个类,因此应该容易找到包含特定功能的类.这里列出了一些最重要的类及其目的.
选择位置
该程序中定义了三个位置提供程序; GPS,WPS和基于蜂窝塔的位置.当Skyhook Wireless SDK的最新版本为2.7时,定义了其中的三个. 2.7版不包含XPS,因此我不得不从这三个来源收集位置信息,然后自行汇总.随着3.0 SDK的发布,WPS将自动聚合这些位置源,因此不使用其他位置提供程序.不过,我决定将它们保留在代码中,特别是对于那些希望使用OpenCellID细胞塔定位系统并需要如何调用它的示例.位置信息已通过名为" HubLocationPRovider"的类进行汇总.从每种定位技术返回的位置信息具有优先级. GPS具有最高的优先级,因为它是最精确的. WPS位居第二,蜂窝塔位置位于最后.来自这些服务中的每一个的位置信息也被分配了到期时间.如果我暂时丢失了GPS信号,那么在短时间内,上一次报告的GPS位置可能比WPS或手机信号塔位置更准确.在优先级较高的提供者过期之前,HubLocationProvider
不会考虑下一个可用的提供者.
//variable for holding our list of providers
ProviderExpiration[] _providerList;
public HubLocationProvider()
{
//Add the providers to the provider list by order of priority
_providerList = new ProviderExpiration[3] ;
_providerList[0] = new ProviderExpiration();
_providerList[0].ExpirationTime = new TimeSpan(0, 0, 30);
_providerList[0].LocationProvider = _gpsProvider;
_providerList[1] = new ProviderExpiration();
_providerList[1].ExpirationTime = new TimeSpan(0, 1, 15);
_providerList[1].LocationProvider = _wifiLocationProvider;
_providerList[2] = new ProviderExpiration();
_providerList[2].ExpirationTime = new TimeSpan(0, 2, 0);
_providerList[2].LocationProvider = _cellProvider;
}
public Location LastLocation
{
get
{
// Go through all of the available location providers by order of
//priority and return the highest priority non-expired value
for (int i = 0; i < _providerList.Length; ++i)
{
if (!_providerList[i].IsExpired())
return _providerList[i].LocationProvider.LastLocation;
}
return null;
}
}
检测位置请求
位置请求通过SMS发送.使用MessageInterceptor类,到达的所有满足一组特定要求的消息都将被路由到FindMe程序.我们感兴趣的消息的属性打包在MessageCondition类的实例中.
除了将消息路由到我的程序之外,如果收到消息时我的程序尚未运行,我还希望它自动启动.如果启用了应用程序启动并提供ID字符串来标识您的应用程序,则MessageInterceptor类将为您解决此问题.如果由于收到短信而启动了程序,则可以使用应用程序的ID字符串通过创建MessageInterceptor
来检索消息,并且在将事件处理程序附加到对象时会接收到该消息.此代码在SmsMessageReceiver
类中.对于我的消息规则,我对任何包含字符串" findme://“的消息都感兴趣.
void EnsureMessageInterceptor()
{
if (_messageInterceptor == null)
{
//If the application launcher is enable then
//the system already has our message interception
//rules. We recreate the message interceptor using our Application ID string
if (MessageInterceptor.IsApplicationLauncherEnabled(ApplicationLaunchID))
{
_messageInterceptor = new MessageInterceptor(ApplicationLaunchID,
_session.UserSettings.EnableAutostart);
}
else
{
//The application launcher isn't enabled, so we need
//to create a new interceptor and give it rules.
_messageInterceptor = new MessageInterceptor();
MessageCondition messageCondition =
new MessageCondition(
MessageProperty.Body,
MessagePropertyComparisonType.Contains,"findme://"
);
_messageInterceptor.InterceptionAction =
InterceptionAction.NotifyAndDelete;
_messageInterceptor.MessageCondition = messageCondition;
//Enable application launching if the user's settings allow for it.
if(_session.UserSettings.EnableAutostart)
_messageInterceptor.EnableApplicationLauncher(ApplicationLaunchID);
}
}
}
收到消息后,我需要决定如何处理.该程序可以接收两种类型的消息:用户的位置,或来自另一个用户的接收位置的请求.如果一条消息包含用户的位置,则将在” findme://“字符串(findme://latitude,longitude,comment)后立即附加纬度,经度和可能的注释来打包该消息.如果用户正在请求位置,则” findme://“字符串将紧随其后的是” whereareyou".为了对这些消息进行解析,我使用了正则表达式.
static Regex LocationRegularExpression =
new Regex(@"findme://(?<<atitude>(\-|\+)?\d+(\.\d*)?),"+
"(?<longitude>(\-|\+)?\d+(\.\d*)?),(?<comment>.*)?",
RegexOptions.IgnoreCase|RegexOptions.Singleline);
static Regex LocationRequestRegularExpression =
new Regex(@"findme://whereareyou(\?format=(?<format>[a-z]*))?",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
通过使用Match结果中的Groups集合,我可以从正则表达式匹配中提取所需的信息.在以下示例代码中,我正在提取接收到的纬度和经度.
Match m = LocationRegularExpression.Match(smsMessage.Body);
if (m.Success)
{
playAlert = true;
double latitude = double.Parse(m.Groups["latitude"].Value);
double longitude = double.Parse(m.Groups["longitude"].Value);
string comment = m.Groups["Comment"].Value;
}
对于findme://whereareyou消息,将立即使用findme://latitude,经度,注释格式将SMS发送回去.该代码还将在消息后检查可选的format参数. format参数的值可以是LocationFormat的任何枚举值(PlainText,LiveMapsLink,ShortLiveMapsLink或FindMeString).如果我想要一个链接到显示用户位置的Microsoft Live Map的链接,我将发送findme://whereareyou?format =LiveMapsLink
.该程序不支持发送这样的请求,但是如果您要访问它,则可以使用该功能.
安全
在不首先考虑应用程序的用户是否希望允许请求者看到他或她的位置之前,不希望这种性质的程序报告信息.我第一次实施该程序时,有两个项目控制程序是否发送响应.第一项是请求者必须知道要发送到设备的密码.第二项是用户必须授予请求者查看其位置的权限.我已经取消了使用图钉的功能,现在完全依靠联系人权限了. Contact类具有一个名为Properties的成员,该成员可用于在用户上存储自定义信息.如果某位联系人被授予查看某个人的位置的权限,我可以通过在该联系人的条目上添加一个自定义属性并将其设置为"是"来保存此信息.清除此值可以撤消该权限.
partial class ContactApproval: IContactApproval
{
const string AllowLocationViewPropertyName = "Approved for Find Me";
const string AllowLocationViewPropertyValue = "Yes";
public void Allow(Contact c)
{
if (!c.Properties.Contains(AllowLocationViewPropertyName))
c.Properties.Add(AllowLocationViewPropertyName);
c.Properties[AllowLocationViewPropertyName] =
AllowLocationViewPropertyValue;
c.Update();
}
public void Revoke(Contact c)
{
if (c.Properties.Contains(AllowLocationViewPropertyName))
c.Properties[AllowLocationViewPropertyName] = null;
}
public bool IsAllowed(Contact c)
{
if ((c==null)||(c.ItemId.ToString().Equals("0")))
return false;
if (c.Properties.Contains(AllowLocationViewPropertyName))
{
object o = c.Properties[AllowLocationViewPropertyName];
string allowSetting = o as string;
if (AllowLocationViewPropertyValue.Equals(allowSetting))
return true;
}
return false;
}
}
记录用户的位置
收到用户的位置时,程序要做的第一件事是将位置保存到日志中.保存位置后,可以通过从程序主菜单中选择"朋友的位置"来查看该位置.该屏幕上的所有信息均直接从日志中提取,但"距离"列除外.距离是根据一个人的当前位置计算的.用于计算距离的公式如下所示:
public static double CalcDistance(double lat1, double lng1, double lat2, double lng2,
double radius /*radius of earth in the unit of your choosing*/)
{
return radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt(
(Math.Pow(Math.Sin((DiffRadian(lat1, lat2)) / 2.0), 2.0) +
Math.Cos(ToRadian(lat1)) * Math.Cos(ToRadian(lat2)) *
Math.Pow(Math.Sin((DiffRadian(lng1, lng2)) / 2.0), 2.0)))));
}
当您选择在地图上查看用户位置的选项时,图钉将同时位于您的位置和用户的位置.根据您的距离,可能需要缩小地图才能看到两个标记.
未来版本
这是我第一次成功创建WPSAPI包装程序,但不是最后一次.包装器提供了我尚未使用的功能,并且需要对该功能进行更多的测试.我还想对内存分配方式进行一些更改,以减少在使用包装程序的会话生命周期中发生的垃圾回收周期数.在接下来的几周中,我将更多地研究包装器,并计划在Skyhook无线讨论组中提供最终版本.
历史
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
.NET2.0 C#3.0 WinMobile5 .NET3.5 VS2008 C# WinMobile .NETCF .NET Visual-Studio Mobile Dev Intermediate Advanced WinMobile6 新闻 翻译