第四章 Geolocation API
让我们设想一个场景,有一个Web 应用程序,它可以向用户提供附近不远处某商店运动鞋的打折优惠信息。使用HTML5 Geolocation API (地理定位API),你可以请求用户共享他们的位置,如果他们同意,应用程序就可以向其提供相关信息,告诉用户去附近哪家商店可以挑选到打折的鞋子。
HTML5 Geolocation技术的另一个应用场景是构建计算行走(跑步)路程的应用程序。想象一下,在开始跑步时通过手机浏览器启动应用程序的记录功能。在用户移动过程中,应用程序会记录已跑过的距离,还可以把跑步过程对应的坐标显示在地图上,甚至可以显示出海拔信息。如果用户正在和其他选手一起参加跑步比赛,应用程序甚至可以显示其对手的位置。
再有一种HTML5Geolocation应用场景是基于GPS导航的社交网络应用,可以用它看到好友们当前所处的位置,比如知道了好友的方位,就可以挑选合适的咖啡馆。此外,还有很多特殊的应用。
本章,我们会探讨HTML5 Geolocation API,它允许用户在Web应用程序中共享他们的位置,使其能够享受位置感知服务。首先,我们了解一下HTML5Geolocation位置信息的来源:纬度、经度和其他特性,以及获取这些数据的途径(GPS、Wi-Fi 和蜂窝站点等)。然后,我们将讨论HTML5地理定位数据的隐私问题,以及浏览器如何使用这些数据.
在此之后,我们将深入探讨HTML5Geolocation API 在实际中的应用。目前有两种类型的定位请求API: 单次定位请求和重复性的位置更新请求。我们会分别介绍这两种请求的使用方式和适用场景。接下来,我们会演示如何使用它们构建实用的HTML5 Geolocation应用程序。最后,我们讨论其他一些用例和使用技巧。
4.1 位置信息
HTML5 Geolocation API的使用方提相当简单.请求一个位置信息,如果用户同意,浏览器就会返回位置信息,该位置信息是通过支持HTML5地理定位功能的底层设备(例如,笔记本电脑或手机)提供给浏览器的。位置信息由纬度、经度坐标和一些其他元数据组成,有了这些位置信息就可以构建引人注目前位置感知类应用程序。
4.1.1 纬度和经度坐标
位置信息主要由下例所示的一对纬度和经度坐标组成,.以美国最美丽的高山湖泊一一塔霍湖边的塔霍城坐标为例:
纬度: 39.17222 ,经度: -120 .13778
在这里,纬度(距离赤道以北或以南的数值表示)是39.172 22 ,经度(距离英国格林威治以东或以西的数值表示)是-120.13778 。
经纬度坐标可以用以下两种方式表示:
- 十进制格式(例如,39.17222);
- DMS (Degree Minute Second ,角度)格式(例如,390 10′ 20′) 。
提示 HTML5 Geolocation API返回坐标的格式为十进制格式。
除了纬度和经度坐标, HTML5 Geolocation还提供位置坐标的准确度,除此之外,它还可能会提供其他一些元数据,具体情况取决于浏览器所在的硬件设备,这些元数据包括海拔、海拔准确度、行驶方向和速度等。如果这些无数据不存在刷返回null。
4.1.2 位置信息从何而来
HTML5 Geolocation API不指定设备使用哪种底层技术来定位应用程序的用户。相反, 它只是用于检索位置信息的API,而且通过该API检索到的数据只具有某种程度的准确性。它并不能保证设备返回的实际位置是精确的。
关于位置
“有一个很有趣的例子。我家用的是元钱网络,当我在Firefox中打开本章中的HTML5 地理定位示例应用程序时,它显示我在萨克拉门托 (约偏离我的实际位置121km (1 mile=1.6 km)) 。这个结果是错的,但也不足为奇,因为我的ISP (lnternet Service Provider ,互联网服务提供商)位于萨克拉门托市中心。
然后,我让我的儿子.,Sean 和Rocky,在他们的iPodTouch 上(使用同一个W?-Fi 网络)浏览同一页面,在Safari浏览器中,显示他们在加利福尼亚州一个叫做Marysville的小镇,距离萨克拉门托48km。想想为什么会出现这种情况吧!”
—-Peter
设备可以使用下列数据源。
- IP 地址
- 三维坐标
- GPS ( Global Positioning System,全球定位系统)。
- 从RFID、Wi-Fi和蓝牙到Wi-Fi 的MAC地址。
- GSM或CDMA手机的ID。
- 用户自定义数据
为了保证更高的准确度,许多设备使用1个或多个数据源的组合。这些方式各有优缺点,下面会详细介绍。
4.1.3 IP 地址地理定位数据
过去,基于lP地址的地理定位方法是获得位置信息的唯一方式,但其返回的位置信息通常并不靠谱,基于IP地址的地理定位的实现方式是:自动查找用户的IP 地址,然后检索其注册的物理地址。因此,如果用户的IP地址是ISP提供的,其位置往往就由服务供应商的物理地址决定,该地址可能距离用户数千米。表4-1是基于IP地址的地理定位优缺点。
优点 | 缺点 |
任何地方都可用 | 不精确(经常出错,一般精确到城市级) |
在服务器端处理 | 运算代价大 |
许多网站会根据由IP地址得到的位置信息来做广告,所以在实际中可能会遇到这样的情况:你到其他国家族行,访问非本地网站时却突然看到了本地广告(基于你访问网站所在国家或地区的IP地址)。
4.1.4 GPS 地理定位数据
只要可以看到天空的地方.,GPS就可以提供非常精确的定位结果。GPS定位是通过收集运行在地球周围的多个GPS卫星的信号实现的。但是,它的定位时间可能较长,因此它不适合需要快速晌应的应用程序。
因为获取GPS定位数据所需时间可能较长,所以开发人员可能需要异步查询用户位置。可以添加一个状态控以显示正在重新获取应用程序用户的位置。表4-2 是基于GPS的地理定位数据的优缺点。
优点 | 缺点 |
很精确 | 定位时间长,用户耗电量大 室内效果不好 需要额外硬件设备 |
4.1.5 Wi-Fi 地理定位数据
基于Wi-Fi的地理定位信息是通过三角距离计算得出的,这个三角距离指的是用户当前位置到已知的多个WI-Fi 接入点(几乎都在城市里)的距离。不同于GPS,Wi-Fi在室内也非常准确。
优点 | 缺点 |
精确可在室内使用 可以简单、快捷定位 | 在乡村这些无线接入点较少的地区效果不好 |
4.1.6 手机地理定位数据
基于手机的地理定位信息是通过用户到一些基站的三角距离确定的。这种方法可提供相当准确的位置结果。这种方法通常同基于Wi-Fi 和基于GPS 的地理定位信息结合使用。表4-4是基于手机的地理定位数据的优缺点。
优点 | 缺点 |
相当准确 可在室内使用 可以简单、快捷定位 | 需要能够访问手机或其modem的设备 在基站较少的偏远地区效果不好 |
4.1.7 用户自定义的地理定位数据
除了通过编程计算出用户的位置外,也可以允许用户自定义其位置。 应用程序可能允许用户输入他们的地址、邮政编码和其他一些详细信息。应用程序可以利用这些信息来提供位置感知服务。 表4-5 是用户自定义的地理定位数据的优缺点。
优点 | 缺点 |
用户可以获得比程序定位服务更准确的位置数据 允许地理定位服务的结果作为备用位置信息 用户自行输入可能自动检测更快 | 可能很不准确,特别是当用户位置变更后 |
4.2 HTML5 Geolocation 的浏览器支持情况
各浏览器对HTML5 Geolocation的支持程度不同,并且还在不断更新.在HTML5的所有功能中,HTML5 Geolocation是第一批被全部接受和实现的功能之一,这对于开发人员来说是个好消息。相关规范已达到一个非常成熟的阶段,不大可能做太大改变。如表4-6 所示,在本书编写时,很多浏览器已经支持HTML5 Geolocation。
浏览器 | 支持情况 |
Chrome | 在带有Gears的第2版Google Chrome中被支持 |
Firefox | 3.5及以上版本支持 |
Internet Explorer | 用过Gears插件支持 |
Opera | 计划在版本10中支持,已经在开发版本中进行测试 |
Safari | 在版本4中支持以实现在iPhone上可用 |
由于浏览器对它的支持程度不同。在使用之前最好先检查浏览器是否支持HTMLS Geolocation API。 4.4.1 节将讲解如何以编程方式检查浏览器是否支持此功能。
4.3 隐私
HTML5 Geolocation规范提供了一套保护用户隐私的机制。除非得到用户明确许可,否则不可在取位置信息。
这个隐私处理机制很合理,也解释了用户一直问到的HTML5 Geolocation应用程序中的老大难问题。不过,从可接触到的HTML5 Geolocation应用程序示例中可以看到,通常会鼓励用户共享这些信息。例如,用户正在某咖啡厅喝咖啡,如果应用程序可以让他们得知该咖啡店附近的商店中有少见的五折跑鞋,那么用户就会觉得共享他们的位置信息是可以接受的。让我们仔细看看图4- 1 所示浏览器和设备的隐私架构。
图4-1 HIML5地理定位浏览器和设备的隐私架构
图中标识的步骤如下。
- 用户从浏览器中打开位置感知应用程序。
- 应用程序Web 页面加载,然后通过Geolocation 函数调用请求位置坐标。浏览器拦截这一请求, 然后请求用户授权。我们假设用户同意。
- 浏览器从其宿主设备中检索坐标信息。例如,IP地址、Wi-Fi或GPS坐标。这是浏览器的内部功能。
- 浏览器将坐标发送给受信任的外部定位服务,它返回一个详细位置信息,并将该位置信息发回给HTML5Geolocation应用程序。
重要提示 应用程序不能直接访问设备,它只能请求浏览器来代表它访问设备。
4.3.1 触发隐私保护机制
访问使用HTML5GeolocationAPI的页面时,会触发隐私保护机制。图4-2 显示了在Firefox 3.5中触发隐私保护机制的页面。
图4-2 在Firefox中调用HTML5GeolocationAPI时触发的通知栏
执行HTML5Geolocation代码时会触发这一机制。如果仅仅是添加HTML5Geolocation代码,而不被任何方法调用(例如,在onload方法中调用),则不会触发隐私保护机制。只要所添加的HTML5Geolocation代码被执行,浏览器就会提示用户应用程序要共享他们的位置。执行HTML5Geolocation的方式很多,例如调用navigator . geolocation.getCurrentPosition 方法等(后面将会有更详细的解释)。图4-3 显示了iPhone 中Safari上的运行结果。
图4-3 HTML5 Geolocation API调用时在safari中触发的通知对话框
除了询问用户是否允许共享其位置之外,Firefox等一些浏览器还可以让用户选择记住该网站的位置服务权限,以便下次坊间的时候不再弹出提示框,类似于在浏览器中记住某些网站的密码。
提示 :在Firefox中,如果用户已经允许其向某同站发送位置信息,但后来又改变主意了,那么很简单,返回到该网站,选择Tool菜单的Page Info , 更改Pennission选项卡中的Share Location设置即可。
4.3.2 处理位置信息
因为位置数据属于敏感信息,所以接收到之后,必须小心地处理、存储和重传。如果用户没有授权存储这些数据,那么应用程序应该在相应任务完成后立即删除它。
如果要重传位置数据,建议先对其进行加密,在收集地理定位数据时肘,应用程序应该着重提示用户以下内容:
- 会收集位置数据;
- 为什么收集位置数据;
- 位置数据将保存多久;
- 怎样保证数据的安全;
- 位置数据怎样共享(如果同意共享);
- 用户怎样检查和更新他们的位置数据;
4.4 使用HTML5 Geolocation API
本节将更详细地探讨HTML5 Geolocation API的使用方法,为了更好地解择说明,我们创建了一个简单的浏览器页面Geolocation.html。所有代码都可以从apress.com本书页面中下载。
4.4.1 浏览器支持性检查
开发人民在调用HTML5 Geolocation API 函数前,需要确保浏览器支持其所要完成的所有工作。这样,当浏览器不支持时,就可以提供一些替代文本,以提示用户升级浏览器或安装插件(如Gears)来增强现有浏览器功能。代码清单4- 1是浏览器支持性检查的一种途径。
代码清单4- 1 检测浏览器支持性
function loadDemo() { if(navigator.geolocation) { document.getElementById("support").innerHTML = "HTML5 Geolocation supported."; } else { document.getElementById("support").innerHTML = "HTML5 Geolocation is not supported in your browser."; } }
在示例中,loadDemo函数测试了浏览嚣的支持情况,这个函数是在页面加载的时候被调用的。如果存在地理定位对象,navígator.geolocation 调用将返回该对象,否则将触发错误。页面上预先定义的support元素会根据检测结果显示支持情况的提示信息。
4.4.2 位置请求
目前,有两种类型的位置要求:
- 单次定位请求;
- 重复性的位置更新请求;
单次定位请求
在许多应用中,只检索或请求一次用户位置即可。例如,如果要查询在接下来的一个小时内放映某大片的最近的电影院,就可以使用代码清单4-2 所示的简单的HTML5 Geolocation API。
代码清单4-2 单次定位请求
void getCurrentPosition(in PositionCallback successCallback, in optional PositionErrorCallback errorCallback, in optional PositionOptions options);
让我们详细分析下这个核心函数调用。
首先,因为这个函数是通过navigator . geolocation对象调用的,所以在脚本中需要先取得此对象。如前所述,应确保有一个合适的后备函数,用来应对浏览器不支持HTML5 Geolocation的情况。
这个函数接受一个必选参数和两个可选参数。
- 函数参数successCal1back为浏览器指明位置数据可用时应调用的函数。因为像获取 位置数据这样的操作可能需要较长时间才能完成,所以这个参数很重要。没有用户 希望在检索位置时浏览器被锁定,也没有开发人员希望他的程序无限期暂停(特别是 要成功取得位置信息,经常必须等待用户的许可)successCallback是收到实际位置信 息并进行处理的地方。
- 跟绝大多数编程场景一样.最好提前准备出错处理。位置信息请求很可能因为一些不可控因素失败,对于这些情况,你可能需要提供一个用于跟用户解释或者提示其重试的errorCallback函数。虽然此参数是可选的,不过建议选用。
- 最后,options对象可以调整HTML5 Geolocation服务的数据收集方式。这是一个可 选参数,我们随后会详细介绍它。
假设在页面上已经创建了一个名为updateLocation()的JavàScript 函数,它使用最新的位置数据更新页面内容。同样地,也创建了一个handleLocationError()函数来处理错误情况。接下来,我们将研究这些函数的细节,而请求访问用户位置的核心代码如下所示:
navigator.geolocation.getCurrentPosition(updateLocation, handleLocationError);
- updateLocation()函数
updateLocation()函数是什么的呢?实际上非常简单。只要浏览器具备访问位置信息的条件,就会调用updateLocation() 函数,该函数只接受一个参数:位置对象。这个对象包含坐标(coords特性)和一个获取位置数据时的时间戳。在实际开发中不一定需要时间戳,重要的位置数据都包含在了cpords 特性中。
坐标总是有多个特性,但是浏览器和用户的硬件设备会决定这些特性值是否有意义。以下是前三个特性:
- latitude(纬度)
- longitude(经度)
- accuracy(准确度)
毋庸置疑,这些特性的数据是必需的。Latitudu和1ongitude将包含HTMLS5 Geolocation 服务测定的最佳的十进制用户位置。accuracy将以m为单位指定纬度和经度值与实际位置间阔的差距,置信度为95%。局限于HTMLS5 Geolocation的实现方式,位置只能是粗略的近似值。在呈现返回值前请一定要检查返回值的准确度。如果推荐的所谓”附近的”鞋店,其实要艳费用户几小时的路程,可能会产生意想不到的后果。
坐标还有一些其他特性,不能保证浏览器都为其提供支持,但如果不支持就会返回null:
- altitude 一一用户位置的海拔高度,以m为单位;
- altitudeAccuracy 一一海拔高度的准确度,也是以m为单位,如果不支持altitude特性也会返回null;
- heading —一行进方向,相对于正北而言;
- speed一一地面遗度,以m/s为单位。
除非确定用户的设备能够访问这些信息,否则建议应用程序不要过于依赖于它们。全球定位设备可能提供这种细节倍息,而网络三角定位则不会。
现在,让我们了解一下updateLocat1on() 函数的实现代码(参见代码清单4-3),该函数根据坐标信息执行具体的更新操作。
代码清单4-3 updateLocat1on() 函数使用示例
function updateLocation(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; var accuracy = position.coords.accuracy; document.getElementById("latitude").innerHTML = latitude; document.getElementById("longitude").innerHTML = longitude; document.getElementById(“accuracy”).innerHTML = “This location is accurate within “ + accuracy + “ meters.” }
示倒中,updateLocat1on()用来更新lITML页面上三个控件元素的文本内容。我们把1ong1tude参数的值赋给经度(1ongtitude)元素,将latitude参数的值赋绘纬度(latitude) 元素。
- handleLocationError()函数
因为位置计算服务很可能出差错,所以对于HTML5 Geolocation应用程序来说错误处理非常重要。幸运的是,该API定义了所有需要处理的错误情况的错误编号。错误编号设置在错误对象中,错误对象作为code参数传递给错模处理程序。让我们依次来看着这些错误编号。
- UNKNOWN_ERROR(错误编号为0) 一一不包括在其他错误编号中的错误。需要通过message参数查找错误的更多详细信息。
- PERMISSION_DENIED(错提编号为1) -一用户选择拒绝浏览器获得其位置信息。
- POSITION_UNAVAILABLE(错模编号为2) -一尝试获取用户位置数据。但失败了。
- TIMEOUT(错误编号为3)设置了可选的timeout值。尝试确定用户位置的过程超时。
在这些情况下,你可能想让用户知道应用程序运行出了问题,而在获取失败和请求超时的时候。你可能希望重试。
代码清单4-4是一个错误处理示例程序。
function handleLocationError(error) { switch(error.code){ case 0: updateStatus("There was an error while retrieving your location: " + error.message); break; case 1: updateStatus("The user prevented this page from retrieving a location."); break; case 2: updateStatus("The browser was unable to determine your location: " + error.message); break; case 3: updateStatus("The browser timed out before retrieving the location."); break; } }
访问error对象的code参数可以得到错去编号,而message特性可提供更详细的问题说明。在所有情况下,我们都调用自己的程序来使用自定义的信息且更新页面状态。
- 可选的地理定位请求特征
如果要同时处理正常情况和错误情况,就应该把注意力集中到三个可选参数(enableHigh_Accuracy、timeout和maximumAge)上,将这三个可选参数传递给HTML5 Geolcation服务以调整数据收集方式。请注意,这三个参数可以使用JSON对象传递,这样更便于舔加到HTML5 Geolocation请求调用中。
- enableHighAccuracy:如果启用该参数,则通知浏览器启用HTML5 Geolocation服务的高精确度模式。参数的默认值为false。如果启用此司在散参数,可能没有任何差别,也可能会导致机器花费更多的时间和资源来确定位置,所以应谨慎使用。
提示:需要注意点是,高精度的设置只是一个切换操作: true或false. APl不允许把精度设置为不同的等级或一个数字范圈。也许,这将在未来版本的规范中得到解决。 - timeout:可选值,单位为ms,告诉浏览器计算当前位置所允许的最长时间。如果在这个时间段内未完成计算,就会调用错误处理程序. 其默认值为Infinity,即无穷大或无限制。
- maximumAge:这个值表示浏览器重新计算位置的时间间隔。 它也是一个以ms为单位的值。此值默认为零,这意味着浏览器每次请求时必须立即重新计算位置。
提示:大家可能会感到困,timeout和maximumAge两者有什么区别呢?虽然它们的名称相似,但用法不同。tlmeout是指计算位置数据所用的时间,而maximumAge涉及计算位位置I数据的频率。任何超过timeout的单次计算时间,都会触发错误。不过,若浏览器没有在maximumAge设定时间之内更新过的数据,它就必须重新获取。 这里的极限情况:如果将maximumAge设置为“0”则浏览器在每次请求时都需要重新获取数据, 如果将其设置为Infinity则意味着不再重新获取数。
但是,请注意API不允许我们为浏览器指定多长时间重新计算一次位置信息。这是完全由浏览器的实现所决定的。我们所能做的就是告诉浏览器maximumAge的返回值是什么。实际颜率是一个我们无法控制的细节。
现在更新我们的位置请求,让其包含一个使用JSON对象表示的可选参数,如下所示:
navigator.geolocation.getCurrentPosition(updateLocation,handleLocationError, {timeout:10000});
这个调用告诉HTML5 Geolocation,任何处理时闹超过10s (10000ms)的位置请求都应该触发-个错误,在这种情况下,将调用handlelocation Error函数处理编号对应TIMEOUT的错误。
重复性的位置更新请求
有时候,仅更新一次是不够的。还好,Geolocation 服务的设计者使应用程序可以在单次请求用户位置和以既定间隔多次请求用户位置间相互转换。事实上,转换的方式很简单,只需要变换请求函数即可,如下所示:
navigator.geolocation.getCurrentPosition(updateLocation, handleLocationError); navigator.geolocation.watchPosition(updateLocation, handleLocationError);
通过对代码进行简单的修改.只要用户位置发生变化,Geolocation服务就会调用updatelocation 处理程序。它的效果就像是程序在监视用户的位置,并会在其变化时及时通知用户一样。
这样做有什么意义呢?
假设有一个页面,随着管擦着在城镇周围的移动,网页上的方向指示也随之改变。在假设一个关于加油站的页面,随着用户开车在高速公路上持续行驶,页面不断更新显示最近的加油站。另外,还可以在一个页面中记录和传送用户位置来实现回溯已走路线。如果位置信息一发生变化就能传递给应用程序,那么前面所假设的所有服务都会变得很容易实现。
关闭更新也很简单。如果应用程序不再需要接收有关用户的持续位置更新,则只需调用
clearWatch()函数,如下所示:
navigator.geolocation.clearWatch(watchId);
这个函数会通知Geolocation服务,程序不想再接收用户的位置更新。但是,watchID 是什么,它从何而来呢?其实它是watchPosition()函数的返回值。watchID表示一个唯一的监视请求以便将采取消监视。所以,如果应用程序要停止接收位置重新信息,可以参照代码清单4-5编写代码。
代码清单4-5 watchPostion的使用
Var watchId = nacigator.geolocation.watchPostion(updateLocation.handleLocationError); //基于持续更新的位置信息实现一些功能 //....... //ok,现在我们可以停止接收位置更新信息了 navigator.geolocation.clearQatch(watchID);
4.5 使用HTML5 Geolocation 构建实时应用
到目前为止,我们主要讨论了单次定位请求。接下来我们使用多次请求特性构建一个简单有用的Web应用程序一一距离跟踪嚣,通过此应用程序可以了解到HTML5 Geolocation API的强大之处。
要想快速确定在一定时间内的行走距离, 通常可以使用GPS导航系统或计步器这样的专用设备。基于HTML5 Geolocation提供的强大服务,开发人员可以创建一个网页来跟踪从网页被加载的地方到目前所在位置所经过的距离。虽然它在台式机上不大实用,但是对于手机是很理想的,而目前可接入网络的支持全球定位的手机有数百万部。
只要在手机浏览器中打开这个示例页面并授予其位置访问的权限,每隔几秒钟,应用程序就会根据刚才走过的距离更新,并将其增加到总距离中(如图4-4所示)。
图4-4 HTML5 Geolocation示例应用程序运行界面
此示倒使用的watchPosition()函数刚刚讨论过,每当有新的位置返回,就将其与最后保存的位置进行比较以计算距离。距离计算使用了众所周知的Haversine公式来实现, 这个公式能够根据经纬度来计算地球上两点间的距离。如代码的清单4-6所示:
代码的清单4-6
Number.prototype.toRadians = function() { return this * Math.PI / 180; } function distance(latitude1, longitude1, latitude2, longitude2) { //R是地球的半径,以KM为单位 var R = 6371; var deltaLatitude = (latitude2 - latitude1).toRadians(); var deltaLongitude = (longitude2 - longitude1).toRadians(); latitude1 = latitude1.toRadians(), latitude2 = latitude2.toRadians(); var a = Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) + Math.cos(latitude1) * Math.cos(latitude2) * Math.sin(deltaLongitude / 2) * Math.sin(deltaLongitude / 2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); var d = R * c; return d; }
本书不会介绍Haversine公式的原理,不过,我们会演示公式的JavaScript实现。你可以使用这段JavaScript代码来计算地球上两个位置之间的距离(见代码清单4-7)。
代码清单4-7 JavaScript实现的Haversine公式
function toRadians(degree) { return degree * Math.PI / 180; } function distance(latitude1, longitude1, latitude2, longitude2) { // R is the radius of the earth in kilometers var R = 6371; var deltaLatitude = toRadians(latitude2-latitude1); var deltaLongitude = toRadians(longitude2-longitude1); latitude1 =toRadians(latitude1); latitude2 =toRadians(latitude2); var a = Math.sin(deltaLatitude/2) * Math.sin(deltaLatitude/2) + Math.cos(latitude1) * Math.cos(latitude2) * Math.sin(deltaLongitude/2) * Math.sin(deltaLongitude/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; return d; }
对于这个公式的原理,请情查画查阅中学数学数材.针对自标,我们编写了将角度转换成弧度的方法,还提供了distance()函数来计算两个经纬度表示的位置间的距离。
如果定期检查用户的位置并计算移动距离,就可以给出以时间为坐标铀的合理的近似移动距离。这需要基于一种假设,即用户在每个区间上都是直线移动,我们的示例就是这样假设的。
4.5.1 编写HTML显示代码
首先,我们要创建示例程序的HTML显示代码。这部分代码我们做了简化,因为我们真正感兴趣的是处理数据的脚本。我们把所有最新的数据以简单的表格形式展示,分行显示纬度、经度、准确度和以ms为单位的时间数。此外,我们还会显示一些文本类统计信息。以使用户能够看到走过距离的概要情况(见代码清单4-8)。
代码清单4-8 距离跟踪器的HTML网页代码
<h1>HTML5 Geolocation Distance Tracker</h1> <p id="status">HTML5 Geolocation is <strong>not</strong> supported in your browser.</p> <h2>Current Position:</h2> <table border="1"> <tr> <th width="40" scope="col">Latitude</th> <td width="114" id="latitude">?</td> </tr> <tr> <td> Longitude</td> <td id="longitude">?</td> </tr> <tr> <td>Accuracy</td> <td id="accuracy">?</td> </tr> <tr> <td>Last Timestamp</td> <td id="timestamp">?</td> </tr> </table> <h4 id="currDist">Current distance traveled: 0.0 km</h4> <h4 id="totalDist">Total distance traveled: 0.0 km</h4>
表中的数值都是默认值。开始接收到数据后,应用程序会进行相应的更新。
4.5.2 处理Geolocation数据
在Geolocation数据处理部分,第一段JavaScript代码应该看起来很熟悉了。之前我们设置过一个处理程序loadDemo(),它会在页面加载完成的时候执行。这个剧本会检测浏览器是否支持HTML5 Geolocation,然后将检测结果显示在页面上。最后,代码会请求监测用户位置,如代码清单4-9 所示。
代码清单4-9 添加loadDemo()方法
var totalDistance = 0.0; var lastLat; var lastLong; function updateStatus(message) { document.getElementById("status").innerHTML = message; } function loadDemo() { if(navigator.geolocation) { updateStatus("HTML5 Geolocation is supported in your browser."); navigator.geolocation.watchPosition(updateLocation, handleLocationError, {maximumAge:20000}); } } window.addEventListener("load", loadDemo, true);
对于错误处理,我们使用之前提到过的那段代码(对于距离跟踪器而言,这些出错处理已经足够用了)。我们将检查收到的所有错误编号,并更新页面上的状态信息,如代码清单4- 10所示。
代码清单4-10 添加出错处理代码
function handleLocationError(error) { switch (error.code) { case 0: updateStatus("There was an error while retrieving your location: " + error.message); break; case 1: updateStatus("The user prevented this page from retrieving a location."); break; case 2: updateStatus("The browser was unable to determine your location: " + error.message); break; case 3: updateStatus("The browser timed out before retrieving the location."); break; } }
需要注意的是,我们将位置监测中的maximumAge选项设置为:{maximumAge :20000}。这将告诉定位服务,所有缓存的位置数据的生命期都不能大于20s(或20000ms)。设置此选项可以使页面处于定期更新状态。我们可以随意调整参数的大小,并试着同时调整缓存的大小。
我们的大部分工作都将在updatelocat1on()函数中实现,此函数中我们将使用最新数据来更新页面并计算路程,如代码清单4-11所示。
代码清单4-11 添加updateLocation()函数
function updateLocation(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; var accuracy = position.coords.accuracy; var timestamp = position.timestamp; document.getElementById("latitude").innerHTML = latitude; document.getElementById("longitude").innerHTML = longitude; document.getElementById("accuracy").innerHTML = accuracy; document.getElementById("timestamp").innerHTML = timestamp;
和期望的效果一样,收到一组最新坐标数据后,首先要做的是记录所有信息。我们收集纬度、经度、准确度和时间戳。然后将这些数据更新到表格中。
应用程序可能选择不显示时间戳。时间戳主要供程序使用,它对最终用户没有多大意义,所以可以替换成更便于用户识别的时间指示器,或将其完全删除。
准确度是以m为单位的,乍一看它似乎没有什么使用价值。然而,任何数据都依赖于其准确度。即使不显示给用户,也应该在代码中考虑准确度。显示不准确的值会向用户提供错误的位置信息。因此,我们将过滤掉所有低精度的位置更新数据,如代码清单4-12所示。
代码清单4-12 忽略不准确的更新
// sanity test... don't calculate distance if accuracy // value too large if (accuracy >= 500) { updateStatus("Need more accurate values to calculate distance."); return; }
最简单的旅行方式
“保持位置跟踪的精度至关重要。作为开发人员,无法选择浏览器计算位至所使用的方法,但是可以保证数据准确度。所以推荐使用accuracy特性!
煽懒的下午,我坐在后院吊床上,在支持地理定位功能的手机浏览器上监测我的位置。我很惊讶地看到,在几分钟内,我只是身体倾斜了一下,但手机上却显示我已经以不同的速度行走了半公里。这听起来可能很可笑,但却提醒我们,数据的准确性与其来源密切相关。”
——Brian
最后,我们将计算移动的距离。假设我们前面已经至少收到了-个准确的位置。我们将更新移动的总距离并将其显示给用户,同时还将储存当前值以备后面做比较。为了使我们的界面不会太混乱,在计算数值时可采用四舍五入或截断的方式,如代码清单4-13所示。
代码清单4-13 添加计算距离的代码
if ((lastLat !== null) && (lastLong !== null)) { var currentDistance = distance(latitude, longitude, lastLat, lastLong); document.getElementById("currDist").innerHTML = "Current distance traveled: " + currentDistance.toFixed(4) + " km"; totalDistance += currentDistance; document.getElementById("totalDist").innerHTML = "Total distance traveled: " + totalDistance.toFixed(4) + " km"; } lastLat = latitude; lastLong = longitude; updateStatus("Location successfully updated."); }
示倒代码的内容就这么多!包括错误处理在内,代码总共不到200行,在这么简短的HTML和脚本中,我们构建了一个能够持续监测用户位置变化的示倒应用程序,几乎完整地演示了
Geolocation API的使用。尽管示例不适用于台式机,但是不妨在支持地理定位功能的手机或移动设备上实践一下,看看你一天大概能走多远的路。
4.5.3 最终代码
<!DOCTYPE html> <html> <title>HTML5 Geolocation Odometer</title> <h1>HTML5 Geolocation Distance Tracker</h1> <p id="status">HTML5 Geolocation is <strong>not</strong> supported in your browser.</p> <h2>Current Position:</h2> <table border="1"> <tr> <th width="40" scope="col">Latitude</th> <td width="114" id="latitude">?</td> </tr> <tr> <td> Longitude</td> <td id="longitude">?</td> </tr> <tr> <td>Accuracy</td> <td id="accuracy">?</td> </tr> <tr> <td>Last Timestamp</td> <td id="timestamp">?</td> </tr> </table> <h4 id="currDist">Current distance traveled: 0.0 km</h4> <h4 id="totalDist">Total distance traveled: 0.0 km</h4> <script type="text/javascript"> var totalDistance = 0.0; var lastLat; var lastLong; function toRadians(degree) { return degree * Math.PI / 180; } function distance(latitude1, longitude1, latitude2, longitude2) { // R is the radius of the earth in kilometers var R = 6371; var deltaLatitude = toRadians(latitude2-latitude1); var deltaLongitude = toRadians(longitude2-longitude1); latitude1 = toRadians(latitude1); latitude2 = toRadians(latitude2); var a = Math.sin(deltaLatitude/2) * Math.sin(deltaLatitude/2) + Math.cos(latitude1) * Math.cos(latitude2) * Math.sin(deltaLongitude/2) * Math.sin(deltaLongitude/2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; return d; } function updateStatus(message) { document.getElementById("status").innerHTML = message; } function loadDemo() { if(navigator.geolocation) { updateStatus("HTML5 Geolocation is supported in your browser."); navigator.geolocation.watchPosition(updateLocation, handleLocationError, {maximumAge:20000}); } } window.addEventListener("load", loadDemo, true); function updateLocation(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; var accuracy = position.coords.accuracy; var timestamp = position.timestamp; document.getElementById("latitude").innerHTML = latitude; document.getElementById("longitude").innerHTML = longitude; document.getElementById("accuracy").innerHTML = accuracy; document.getElementById("timestamp").innerHTML = timestamp; // sanity test... don't calculate distance if accuracy // value too large if (accuracy >= 500) { updateStatus("Need more accurate values to calculate distance."); return; } // calculate distance if ((lastLat !== null) && (lastLong !== null)) { var currentDistance = distance(latitude, longitude, lastLat, lastLong); document.getElementById("currDist").innerHTML = "Current distance traveled: " + currentDistance.toFixed(4) + " km"; totalDistance += currentDistance; document.getElementById("totalDist").innerHTML = "Total distance traveled: " + totalDistance.toFixed(4) + " km"; } lastLat = latitude; lastLong = longitude; updateStatus("Location successfully updated."); } function handleLocationError(error) { switch (error.code) { case 0: updateStatus("There was an error while retrieving your location: " + error.message); break; case 1: updateStatus("The user prevented this page from retrieving a location."); break; case 2: updateStatus("The browser was unable to determine your location: " + error.message); break; case 3: updateStatus("The browser timed out before retrieving the location."); break; } } </script> </html>
4.6 进阶功能
尽管有些技巧我们在示例中用不上,但这并不妨碍它们在多种类型的HTML 5Web应用中发挥作用。本节,我们就来介绍一些简单实用的进阶功能。
4.6.1 现在的状态是什么
大家可能已经注意到了, HTML5 Geolocation API通常涉及计时。不足为奇,因为手机主角测量、GPS、IP查询等定位技术都可能需要较长时间才能完成。幸运的是,API为开发人员提供了大量的信息,可以利用这些信息为用户刨建一个合理的状态显示在。
如果开发人员在定位函数中设置了可造的timeout值,那么地理定位服务在取位置信息超时的时候,就会向用户弹出错误提示信息。更合理的效果是当请求还在进行肘,在用户页面上告知用户当前的状态倍息。状在从提出请求开始,不论请求成功或失败,状态的结束都与timeout的值对应。
在代码清单4-15中,我们启用JavScript 间隔计时器以定期使用新的进度值更新显示的状态。
代码清单4-15 添加状态栏
function updateStatus(message) { document.getElementById("status").innerHTML = message; } function endRequest() { updateStatus("Done."); } function updateLocation(position) { endRequest(); // handle the position data } function handleLocationError(error) { endRequest(); // handle any errors } navigator.geolocation.getCurrentPosition(updateLocation, handleLocationError, {timeout:10000}); // 10 second timeout value updateStatus(“Requesting location data…”);
我们详细分析一下这段代码。同以前一样,我们已经有了用于更新页面状态值的函数,如下所示。
function updateStatus(message) { document.getElementById("status").innerHTML = message; }
这里使用了简单的文本方式来显示状态信息(参见代码清单4-16),实际应用中可以使用更吸引人的图形来显示状态.
代码清单4-16 显示状态
navigator.geolocation.getCurrentPosition(updateLocation, handleLocationError, {timeout:10000}); // 10 second timeout value updateStatus(“Requesting location data…”);
就们再次使用Geoloccation API来获取用户的当前位置,但此时将timeout设置为10s。10s后,由于timeout选项的原因,结果可能是成功或者失败。
我们会马上更新状态的文本显示,告诉用户该位置请求正在进行中。然后,如果请求在10s内完成了,或者请求未完成但时间超过了10s。都会使用回调方法来重置状态文本,如代码清单4- 17所示。
代码清单4-17 重置状态文本
function endRequest() { updateStatus("Done."); } function updateLocation(position) { endRequest(); // handle the position data }
这个示例功能虽然简单,但易于扩展。
调用getCurrentPosition()会启动单次定位查找请求。开发人员可以轻易地决定何时发起位置查找请求。不过,在通过watchPosition()重复查找位置的情况下,开发人员就无法控制何时发起每个位置请求了。
此外,计时是从用户授权访问位置数据开始的。由于无法在用户授权时立即获知其准确时间,所以也就无法做到精确显示状态。
4.6.2 在Goolge Map上显示”我在这里”
一个非常常见的利用位置数据的应用是在诸如Goolge Map中显示用户的位置。由于实际需求量很大,Goolge将HTML5 Geolocation内置到了其用户界面中。只需按下“show my Location”按钮(见图4-5) ,Goolge Ma就将使用Geolocation API(如果可用)在地图上检测并显示用户的位置。
开发入员也可以自己实现同样的功能.Geolocation API超出了本书范围,但是我们只需知道它的经纬度位置表示方式也是按照十进制格式设计的(并非巧合)。因此,开发人员可以将查找到的用户位置结果传递给Geolocation API,如代码清单4-18 所示。更多此方面的内容可以阅读Beginning Google Maps Application Second Edition(Apress,2010)。
图4-5 Google地图中的“Show My Location”按钮
代码清单4-18 将位置传递给Goole Map API
// Create a Google Map… see Google API for more detail var map = new google.maps.Map(document.getElementById("map")); function updateLocation(position) { //pass the position to the Google Map and center it map.setCenter(new google.maps.LatLng(position.coords.latitude, position.coords.longitude); } navigator.geolocation.getCurrentPosition(updateLocation, handleLocationError);
4.7 小结
本章讨论了HTML5 Geolocation,讲述了HTML5 Geolocation的位置信息一一纬度、经度和其他特性,以及获取它们的途径。我们还探讨了由HTML5 Geolocation引发的隐私问题。最后演示了如何使用HTML5 Geolocation API创建引人注目的位置感知Web 应用程序。
下一章,我们将演示如何基于HTML5 Communication实现标签页间及窗口间通信,以及如何实现页面和服务嚣的跨越通信。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论