Windows Mobile

将 GPS 和网站地图用于能感知位置的应用程序

Christopher Mitchell

代码下载位置:MSDN 代码库
在线浏览代码

本文将介绍以下内容:

  • MapPoint Web 服务
  • 缓存任务和地图
  • 获取所需的邻近点
  • 新建任务
本文使用了以下技术:
Windows Mobile 6、MapPoint

目录

使用 MapPoint 进行定位
任务和 Pocket Outlook
应用程序体系结构
获取邻近点
添加任务
一路前行

最近我乔迁新居,一些要好的朋友花费了一整天的时间帮我装箱拆箱、摆放物品和处理其他乏味却又不得不做的搬迁工作。回老房子取最后一个箱子的路上,我差点忘记为他们准备晚餐。我在自己的 Windows Mobile 电话上设置了提醒,但它不能在我正好驾车经过最方便的外卖店时告诉我。

我希望电话能在我到达合适的目标附近时通知我。如果能在合适的时间和地点得到提醒,我们就可以在劳累一天后大饱口福,而不是还要驾车在陌生的地段来回寻觅。

Windows Mobile 提供了许多能够帮助设备了解其环境的界面和功能——所处位置、是否有信号、信号量等。但如何才能将这些功能用于您的应用程序呢?在这些功能中,最突出和最有用的无疑是位置感知。您可以创建各种位置感知应用程序,从目的明确的卫星导航程序到本文将讨论的更为复杂的任务列表。

本文将主要介绍使用此功能时所涉及的问题,以及为开发有用应用程序需要编写的其他代码。在本文中,我将讨论移动应用程序开发环境和实用工具,并向您展示如何构建可以在适合的时间和地点提醒您注意的位置感知任务列表应用程序。

使用 MapPoint 进行定位

我的位置感知任务列表应用程序(名为 wheretodo)需要执行几个核心任务。它需要获取电话地理位置的信息。还需要存储和监视任务。此外,应用程序还需要知道电话的当前位置附近有哪些合用的商店和服务,可以用于解决当前任务。最后,它需要给予电话提醒。应用程序界面如图 1 所示。

fig01.gif

图 1 Wheretodo 应用程序

应用程序首先需要地理数据。考虑本文的目的,我选择使用Microsoft MapPoint Web 服务。此 Web 服务是 Live Search 地图Virtual Earth 的基础技术,可为欧洲和美国提供就近搜索服务。提供的示例代码使用欧洲地图数据设置;您需要根据工作地点来更改此设置。

MapPoint 将 GPS 维度、经度以及特定存储类型的搜索代码作为其参数。安装在仿真器或智能手机上的 FakeGPS 实用工具可提供位置信息。(有关此实用工具的详细信息,请参阅“FakeGPS”侧栏。)

MapPoint 为 XML Web 服务提供了 SOAP API。Web 服务分为四种主要服务:公共服务、查找服务、呈现服务和路线服务。此应用程序最受关注的服务是查找服务,但如果您想为用户或地图指引方向,也可以使用其他服务来扩展功能。

公共服务 (CommonServiceSoap) 包含查找服务、路线服务、呈现服务通用的类、方法和属性,或基本实用工具函数。

您可以使用查找服务 (FindServiceSoap) 找到地址、地理实体、维度和经度坐标以及 MapPoint Web 服务数据的兴趣点 (POI)。您还可以解析地址并针对指定的维度和经度返回位置信息。

您可以使用呈现服务 (RenderServiceSoap) 绘制路线图和位置图、放置图钉、绘制多边形区域、设置地图大小和地图视图、在地图上选点、获取地图上相关点和多边形的位置信息、平移和缩放呈现的地图。

路线服务 (RouteServiceSoap) 可生成路线、行驶方向、根据位置或路标计算出的路线表示(用于在地图上呈现突出显示的路线)、设置路段和首选路线、生成路段和方向的地图视图。

MSDN 上有一整套对象模型类关系图。MapPoint 根据地理区域或所需的信息类型将用于查找服务、路线服务和呈现服务的数据保存在多个不同的数据源中。您在 MSDN 杂志上能够找到全部有关使用服务的 MapPoint 技术文章

任务和 Pocket Outlook

Pocket Outlook 对象模型 (POOM) 允许您将菜单和功能添加至 Windows Mobile 的“任务”和“联系人”应用程序中,并允许处理其关联项和数据。位置感知应用程序有三个主要接口:Iappointment、Itask 和 Icontact。

IAppointment 代表 Calendar 文件夹中的一个约会。一个约会对象可以代表一个会议、一次约会,也可以代表定期约会或会议。

ITask 代表一项在指定时间范围内要完成的已分配、已委派或自派任务。任务项包含在 Tasks 文件夹内。

Icontact 代表 Contacts 文件夹内的联系人。使用其方法可以保存、删除、复制或显示联系人。IPOutlookItemCollection 接口可用于添加新联系人或检索现有联系人。

示例应用程序将使用 Itask。(从功能上来讲,可以使用 Iappointmen,但该接口不太适合于应用程序的直接需要。)POOM 类似于桌面 Outlook 对象模型,您可以在 MSDN 上的“Pocket Outlook 对象模型与 Outlook 对象模型之间的差异”一文中了解更多相关信息。

应用程序体系结构

位置感知应用程序由两个函数链组成。第一个是监视当前任务和问题的系统,如图 2 所示。任务存储在 SQL ServerCompact 数据库表中。此外,还有一组辅助数据存储在另一个名为 geocache 的表中。使用单独的表可限制所需的连接数,我将在本文的稍后部分中加以介绍。

fig02.gif

图 2 监视位置感知任务并发出警报

Microsoft MapPoint Web 服务可提供邻近点的信息。世界不同地区的 MapPoint Web 服务中有不同的数据源,数据源的功能也各不相同。示例应用程序使用的数据源是 NavTech.EU。收集完此类信息后,即可执行一些距离计算并发出警报(如适用)。

第二个是事件链(请参见图 3),可通过 POOM 将任务添加至 Windows Mobile 设备上的任务列表中。该框架还允许访问日历和 SMS 函数。

fig03.gif

图 3 用于向 Wheretodo 应用程序添加任务的系统流

“Tasks and Cache”(任务和缓存)代表事件监视和警报链的第一部分。任务管理器只需从 POOM 获得一个任务列表。它还能管理 geocache,解决对连接有局限性的设备使用 Web 设备时产生的问题。设备连接的级别分为永久性连接、偶尔连接或根本无连接。

为适应多种连接情况,您需要一个合适的 Web 服务数据缓存策略以及一个能够了解如何充分利用连接的简单系统。尽管可以轻松想象出非常复杂的情形,但在本文中制定的是简单的半径缓冲策略。例如,向 MapPoint Web 服务发出一个查询,以现在的位置为中心、在给定半径(比如说 80 公里 (km))内搜索所有中餐外卖店。在您朝任一方向走了 40 km 后,发出一条新的搜索。它遵循的原则是:向 Web 服务发出每一条查询的成本都很高。

它还假设您离开任务输入点的距离不太可能超出 40 km。由于用来控制缓冲操作的距离可能因人和国家/地区而异,因此它被设计为由用户输入。

查询已运行位置的日志存储在 geocachelog 数据库中,在执行任何其他查询之前会检查该日志,以防从 Web 服务请求重复的信息。它还为使用大量信息预填充系统提供了机会。

FakeGPS

sidebarfig.gif

FakeGPS 应用程序

Windows Mobile 6 SDK 包含名为 FakeGPS 的实用工具,此工具允许您使用模拟的 GPS 数据测试应用程序。有关使用 FakeGPS 执行安装和测试的信息,请参阅 MSDN 库文章“使用 FakeGPS 实用工具”。

FakeGPS 读取一组预先记录的 GPS 指令。GPS 数据可以使用原始 GPS 数据记录器从真实的 GPS 设备获得。FakeGPS 设备读取生成的文件并重新运行该文件,模拟更改位置数据,供测试之用。例如,我记录了一次伦敦之行来为我的应用程序收集数据,然后通过 FakeGPS 重新规划了旅程。

获取邻近点

下一个组件定义如何填充 geocache。当应用程序需要查找某个位置时,会调用 GetNearByPOI 函数(请参见图 4)。代码会查询 MapPoint Web 服务。请注意,此处显示的代码为清晰起见做了简化处理,与代码下载中的有所不同。

图 4 获取所需的邻近点

private void GetNearByPOI(
  string KeyWord, LatLong CurrentPosition) {

  FindServiceSoap findService = new FindServiceSoap();
  FindNearbySpecification findNearBySpec = new FindNearbySpecification();
  findService.Credentials = 
    new System.Net.NetworkCredential(myUserName, myPassword);
  findService.PreAuthenticate = true;

  findNearBySpec.Distance = Convert.ToDouble(inputdistance.Text);
  findNearBySpec.LatLong = new LatLong();
  findNearBySpec.LatLong.Latitude = CurrentPosition.Latitude;
  findNearBySpec.LatLong.Longitude = CurrentPosition.Longitude;

  findNearBySpec.Filter = new FindFilter();
  //findNearBySpec.Filter.EntityTypeName = KeyWord;
  findNearBySpec.Filter.EntityTypeName = "FoodType3"; // SIC CODE
  findNearBySpec.DataSourceName = "NavTech.EU";

  FindResults foundResults;
  foundResults = findService.FindNearby(findNearBySpec);

  string connectionString;
  string fileName = System.IO.Path.GetDirectoryName(
    System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + 
    "\\wheretodo.sdf";
  string password = "sa";
  connectionString = string.Format("DataSource=\"{0}\"; Password='{1}'", 
    fileName, password);
  SqlCeConnection cn = new SqlCeConnection(connectionString);
  cn.Open();
  SqlCeCommand cmd;

  //Loop Round and add it to the geocache
  foreach (FindResult fr in foundResults.Results) {
    //This needs to include LongLat in geocache
    string sql = 
      "insert into geocache2 (POI_Name, POI_Address, POI_Tel, POI_Web, " +
      "POI_POST_ZIP, POI_Lat, POI_Long, POI_KeyWord) " +
      "values (@Name, @Address, @Tel, @Web, @POST_ZIP, '" + 
      fr.FoundLocation.LatLong.Latitude.ToString() + 
      "', '" + fr.FoundLocation.LatLong.Longitude.ToString() + "', '" + 
      KeyWord.ToString() + "')";
    cmd = new SqlCeCommand(sql, cn);
    cmd.Parameters.Add("@Name", SqlDbType.NVarChar, 255, "Name").Value = 
      fr.FoundLocation.Entity.Properties[0].Value.ToString();
    cmd.Parameters.Add("@Address", SqlDbType.NVarChar, 255, "Address").Value = 
      fr.FoundLocation.Entity.Properties[1].Value.ToString() + 
      fr.FoundLocation.Entity.Properties[2].Value.ToString();
    cmd.Parameters.Add("@Tel", SqlDbType.NVarChar, 255, "Tel").Value = 
      fr.FoundLocation.Entity.Properties[9].Value.ToString();
    cmd.Parameters.Add("@Web", SqlDbType.NVarChar, 255, "Web").Value = 
      "http://m.live.com";
    cmd.Parameters.Add("@POST_ZIP", SqlDbType.NVarChar, 255, "POST_ZIP").Value = 
      fr.FoundLocation.Entity.Properties[7].Value.ToString();
    cmd.ExecuteNonQuery();

    //Update geocachelog
    sql = "insert into GeoCodeLog (Keyword, Lat, Long ) values ('" + KeyWord + 
      "', '" + fr.FoundLocation.LatLong.Latitude.ToString() + "', '" + 
      fr.FoundLocation.LatLong.Longitude.ToString() + "' )";
    cmd = new SqlCeCommand(sql, cn);
    cmd.ExecuteNonQuery();
  }
}

请看调用了 CurrentPosition.Latitude 和 CurrentPosition.Longitude 的代码段。它基本上衍生自 GPS Intermediate Driver,一个介于应用程序和 GPS 硬件设备驱动程序之间的软件层。该抽象层允许一次性编写应用程序并使之能与多个 GPS 设备结合使用。GPS Intermediate Driver API 通过本机代码库公开。您可以通过使用 Windows Mobile 6 Professional SDK 附带的示例获得从托管代码访问该库的权限,(请参阅“通过托管代码使用 GPS Intermediate Driver)”。

现在有了与任务相关的当前位置和附近的 POI,在此它们由 Navtech.EU 数据源中使用的标准工业分类码 (SIC) 标识,您需要准确地确定出这些点离您当前所在位置的距离。我使用从 Web 服务和 GPS(本例中为 FakeGPS)返回的经度和维度值;图 5 显示了如何将这些值转换成距离。

图 5 计算距离

private double GetLatLongTuppleDistance(
  double Lat1, double Long1, double Lat2, double Long2) {

  //Convert Degress to Radians for Calculations
  double Lat1r = ConvertDegreesToRadians(Lat1);
  double Lat2r = ConvertDegreesToRadians(Lat2);
  double Long1r = ConvertDegreesToRadians(Long1);
  double Long2r = ConvertDegreesToRadians(Long2);

  // Spherical law of cosines formula—ignores the effect of hills
  double R = 6371; // Earth's radius (km)
  double d = Math.Acos(Math.Sin(Lat1r) * Math.Sin(Lat2r) +
    Math.Cos(Lat1r) * Math.Cos(Lat2r) *
    Math.Cos(Long2r—Long1r)) * R;
  //Returns distances in km
  return d;
} 

基于球面余弦定律(它可推论出勾股定理)的球面三角函数提供了可将维度和经度转换成距离(单位 km)的函数。要计算您与所查找位置之间的距离,其中涉及很多复杂的因素,而上述函数明显对这些因素做出了相当程度的假设。因为地球不是一个标准的球体,因此使用这些公式时会存在误差。陆地上的 1 英里 = 1.609344 公里,而海上的 1 海里 = 1.852 公里。

该事件链的最后一部分是在距当前位置一定距离内找到任务的解决方案时向用户发出警报。由于此距离视交通方式(驾车、步行、骑自行车等)而定,因此留给用户自行输入。

添加任务

当然,用户必须先向移动设备添加任务,应用程序才能查找位置。POOM 镜像 Outlook 对象模型,但为适应移动设备的实际情况,缩小了其功能范围。

使用 POOM 可轻松修改和显示约会、任务和联系人项,并能操控包含这些内容的文件夹。创建任务项的代码如下:

OutlookSession outlooksession = new OutlookSession();
Task NewTask = new Task();
NewTask.Body = textBox2.Text.ToString();

string MyString = dateTimePicker2.Value.ToShortDateString() + " " + dateTimePicker1.Text.ToString();
DateTime MyDateTime = new DateTime();
MyDateTime = DateTime.ParseExact(MyString, "M/d/yy h:mm:ss tt", null);
NewTask.DueDate = MyDateTime.ToUniversalTime();
NewTask.Subject = textBox1.Text.ToString();
outlooksession.Tasks.Items.Add(NewTask);

可以通过更改各种参数来设置结束日期和任务正文,然后将其添加至 POOM。

该组件是用于提供 wheretodo 应用程序功能的两个组件链的后一个。系统 UI 本身非常简单,利用它可设置所有相关信息并确定应用程序将找到的 SIC 代码。整个应用程序封装到一个无限循环中,它的作用就是说明如何使用位置感知应用程序。您可以非常轻松地将它作为背景服务添加至移动设备,使其更高效简洁地集成到 Windows Mobile 6 或 Windows Mobile 5 环境中。

移动数据资源

编写同时适用于移动和桌面应用程序的代码

平步青云:适用于 Windows Mobile 的自适应应用程序

数据点:从移动应用程序访问数据

一路前行

您已看到了一种位置感知设备和能轻松开发这些应用程序的工具。可针对 RFID 轻松扩展位置感知任务列表的这一基本概念。

此代码的改进可以应用于任何位置感知应用程序。以上内容可归结为:位置感知如何与在大多数移动电话上运行的其他上下文感知服务紧密地联系在一起。除了您的位置,任务对您还想尝试的其他事情一无所知。您可为任务设定时间和位置,以便在某个特定时间(比如说,下班后驾车回家)接近某个位置时可以触发它们。更棒的是,任务提醒会知道您 10 分钟后有一个会议,还知道即便您在开会的路上会路过邮局,也没有足够的时间去买邮票。

如果您对为本文中的位置感知应用程序或您自己最近更新的位置感知应用程序构建自定义安装程序感兴趣,请参阅我为《MSDN 杂志》2007 年 10 月刊撰写的文章**(调整铃声音量,避免环境噪音)。

谨以此文献给我亲爱的朋友 Tom Passey 和他的妻子,他们多次给予我关心和照顾,为我留下了很多美好的回忆。在此献上最美好的祝愿。

Christopher Mitchell 拥有机器学习和音乐/声音信号处理专业的博士学位,是一名 Kauffman/NCGE 研究员,目前正在英国剑桥大学执教。他是英国剑桥镇安格利亚鲁斯金大学的一名兼职讲师。您可以通过电子邮件 chris.mitchell@anglia.ac.uk与 Chris 取得联系。