HTML5地理定位

Published on 2016 - 11 - 11

Geolocation可以让我们确定访客在地球的什么位置。注意,这可不是说判断用户在哪个国家或者哪个城市,而是说确定用户在城市的哪条街道上,甚至是用户正拿着手机在哪里上网,给出这个地点的坐标位置。

表1 列出了支持地理定位的浏览器

要求 IE Firefox Chrome Safari Opera Safari iOS Android
最低版本 9 3.5 5 5 10.6 3.2 2.1

地理定位的基本原理

只要不是神经有问题,人们都会提出类似这样的问题:几行代码就能确定我现在在哪个咖啡馆里面?是不是有什么隐藏的程序在跟踪我呀?屋外的白色面包里车里坐的是什么人?

放心吧,地理定位跟“黑社会老大”真扯不上半点关系。因为就算浏览器支持地理定位,如果你不允许,它也不会把你的行踪透露给浏览器(参见图1)。

网页正想加载位置数据,Firefox询问你是允许一次(单击Share Location),还是以后都允许(Always Share),抑或永远不允许(Never Share)。这可不是Firefox有礼貌,而是Geolocation标准要求浏览器必须征得用户同意,才能允许网站访问其数据

为了得到用户的位置信息,浏览器会争取位置提供商(location provider)的帮助。比如,Firefox浏览器使用的是Google Location Services。位置提供商为查找位置要付出很大努力,而且要想尽各种办法。

对于通过网线(不是无线)上网的桌面计算机来说,办法很简单,但结果不太准确。用户一上网,他的信息就会通过双绞线在计算机或本地网上传输,进入电话线,或者拨号连接(令人恐惧),最终到达连接因特网的高层硬件设备。这个设备有一个唯一的IP 地址,靠这个就能在因特网上找到它。与IP地址对应,还有一个现实中的邮政地址。

位置提供商会把这两个信息综合起来。首先,它找到你连接的IP地址,然后,确定使用该IP地址的路由器的位置。因为这个信息是间接的,所以使用桌面计算机时的地理定位并不准确。比如,你在芝加哥西郊的某个地方上网,而你的位置可能会出现在离市中心不远的地方。不过,即便是如此不准确的结果也还是有用的。如果你正在某个地图应用上寻找附近的披萨店,那你可以一下就跳到自己感兴趣的区域——你们家附近,即使离你家还有一段距离。

注意 IP地址定位是最粗略的地理定位方法。如果还有更好的数据源,位置提供商会择优选用。

如果你在使用笔记本或移动设备无线上网,那位置提供商会寻找你附近的无线接入点。理想情况下,位置提供商会查询一个大型数据库,以确定你周围几个接入点的确切位置,然后再使用三角测量法算出你的位置。

要是你在用手机上网,位置提供商还会采用类似的三角测量法,但使用的是信号发射塔的位置。经过迅速而相对准确的计算,最终得到的位置误差大约在1000米。(中心城区等繁华地带的信号发射塔相对较多,因此地理定位的结果也更准确。)

最后,很多移动设备都配有专用的GPS组件。GPS可以使用卫星定位,误差只有几米。但GPS的缺点是速度慢,耗电多。而且,GPS在高楼林立的地区也不好使,因为高大的建筑物会屏蔽信号。当然,到底用不用GPS完全取决于你自己,这一点将在后面介绍。

当然了,还有其他技术可以用于地理定位。位置提供商会想更多办法获得位置信息,比如RFID芯片、蓝牙设备,以及由Google Maps设置的cookie等。

提示 还可以使用另一个工具来修改自己的位置。比如,有个Firefox插件叫Geolocater(http://addons.mozilla.org/en-us/firefox/addon/geolocater),它可以在网站想共享你的位置时,告诉Firefox提供哪里的位置信息。利用这个工具甚至可以伪造位置,比如假装自己在德国洛瓦,而实际上是在荷兰。

关键在于,无论你通过什么方式上网——就算是你使用台式机,地理定位都可以大概地找到你。而如果你使用能接收电话信号或者配有GPS芯片的设备,地理定位的坐标将惊人地精确。

查找访客的坐标

地理定位功能实际上是非常简单的。说到底,主要就是navigator.geolocation对象的三个方法:getCurrentPosition()、watchPosition()和clearWatch()。

注意 可能有读者对navigator对象不熟悉,它只是JavaScript众多对象中的一个,它的属性中保存着当前浏览器及其能力的信息。其中,最有用的属性莫过于navigator.userAgent,包含了关于浏览器的所有细节,如版本号、所在操作系统等。

要取得访客的位置,可以调用getCurrentPosition()方法。当然,查找位置不会立即返回结果,浏览器也不想锁定页面等待位置数据。所以,getCurrentPosition()方法是异步的,它会立即执行,但不会阻塞其他代码。完成地理定位后,它会触发另一段代码来处理返回的结果。

噢,地理定位会通过一个事件来告诉我们它完成了工作,这与图片加载完毕或文本文件读取完毕时触发onLoad事件类似,对吗?你错了,JavaScript在这里有点不一致。换句话说,在调用getCurrentPosition()时,你要提供一个完成函数。

下面就是一个例子:

navigator.geolocation.getCurrentPosition(
 function(position) {
   alert("You were last spotted at (" + position.coords.latitude +
    "," + position.coords.longitude + ")");
 }
);

运行这段代码,会调用getCurrentPosition()并传给它一个函数。浏览器确定了位置之后,会触发传入的函数,然后显示一个对话框。图2展示了Internet Explorer中看到的结果。

上:首先,要同意浏览器向Web服务器透露你的位置信息。下:结果是你在地球上的坐标

为了让代码更清晰,可以把完成函数的定义挪到getCurrentPosition()调用语句的外面来,如下所示:

function geolocationSuccess(position) {
  alert("You were last spotted at (" + position.coords.latitude +
   "," + position.coords.longitude + ")");
}

然后,在调用getCurrentPosition()时再传入函数名即可:

navigator.geolocation.getCurrentPosition(geolocationSuccess);

测试的时候,需要使用支持地理定位的浏览器,并且允许网页访问你的数据。另外,建议你把页面上传到测试服务器,然后再通过浏览器打开。否则,有可能遇到奇怪的现象(例如,地理定位无法处理错误),而有些浏览器(比如Chrome)根本不会检测你的位置。

确定地理定位的精确程度

调用的getCurrentPosition()成功返回后,position对象会包含两个属性:timestamp(确定地理位置的时间)和coords(地理坐标)。

从上面的例子可以看到,coords属性是一个对象,包含latitude(纬度)和longitude(经度)属性,用以确定你在地球上的位置。不过,coords属性还有另外一些信息,比如altitude(海拔高度)、heading(移动方向)和speed(移动速度),但目前尚未有浏览器支持它们。

更有意思的是accuracy(精度)属性,用米为单位表示地理定位信息的准确程度。(不要搞混,accuracy属性的值越小,表示地理定位越精确。)例如,accuracy等于2135米,约1.3英里,表示地理定位在该范围内找到了当前访客的位置。为了好理解,可以想象一个圆,圆心是返回的地理坐标,半径是2135米,而访客可能就在这个圆形区域中的某个地方。

利用accuracy属性可以确定哪些地理定位结果不能用。比如,一个结果的精确度为几万米,那跟没有定位也差不多了。

if (position.coords.accuracy > 50000) {
  results.innerHTML = 
     "This guess is all over the map.";
}

此时,可能需要通知用户无法准确定位,或者为用户提供一个文本框,让他自己输入位置信息。

处理错误

要是访客不愿意共享他们的位置数据,那地理定位就不会返回位置信息。在这种情况下,根本不会调用完成函数,而页面也没有办法告诉你浏览器是在继续挖掘数据,还是遇到了错误。为了解决这个问题,可以在调用getCurrentPosition()时传入两个函数。第一个函数在页面成功取得数据时调用,第二个函数在地理定位因错误而终止时调用。

下面就是一个同时传入完成函数和错误函数的例子:

//保存显示结果的页面元素
var results;

window.onload = function() {
  results = document.getElementById("results");

  //如果浏览器支持地理定位,取得访客的位置
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      geolocationSuccess, geolocationFailure
    );
    results.innerHTML = "The search has begun.";
  }
  else {
    results.innerHTML = "This browser doesn't support geolocation.";
  }
};

function geolocationSuccess(position) {
  results.innerHTML = "You were last spotted at (" +
   position.coords.latitude + "," + position.coords.longitude + ")";
}

function geolocationFailure(positionError) {
  results.innerHTML = "Geolocation failed.";
}

调用错误函数时,浏览器会给错误函数传入一个错误对象,这个对象有两个属性:code和message。其中,code属性是一个数值,表示问题类型;而message中包含着对问题的简短描述。一般来说,message属性多用于测试,而code属性用于确定如何进行下一步的处理。

下面是修改后的错误函数,检测了code属性所有可能的值:

function geolocationFailure(positionError) {
  if (positionError.code == 1) {
    results.innerHTML =
     "You decided not to share, but that's OK. We won't ask again.";
  }
  else if (positionError.code == 2) {
    results.innerHTML =
     "The network is down or the positioning service can't be reached.";
  }
  else if (positionError.code == 3) {
    results.innerHTML =
     "The attempt timed out before it could get the location data.";
  }
  else {
    results.innerHTML =
     "This the mystery error. We don’t know what happened.";
  }
}

注意 如果你是在本地计算机(而非真正的Web服务器)上测试页面,那么在拒绝共享位置信息后,不会触发错误函数。

设置地理定位选项

到目前为止,我们已经知道调用getCurrentPosition()时可以传入两个参数:一个成功函数,一个失败函数。实际上,还可以再传入一个参数,这个参数是一个对象,用于设置某些地理定位选项。

可以设置三个选项,每个选项对应地理定位选项对象的一个不同的属性。这三个选项可以只设置一个,也可以设置多个。下面这个例子设置了名为enableHighAccuracy的选项:

navigator.geolocation.getCurrentPosition(geolocationSuccess,
 geolocationFailure, {enableHighAccuracy: true});

下面这个例子则设置了三个选项:

navigator.geolocation.getCurrentPosition(
 geolocationSuccess, geolocationFailure,
 {enableHighAccuracy: true,
 timeout: 10000,
 maximumAge: 60000}
);

好了,这些属性都是什么意思呢?enableHighAccuracy属性要求高精度的GPS位置检测,只要设置支持(而且用户同意)。除非确实需要精确的坐标,否则不要设置这个选项,因为这个过程很费电。而enableHighAccuracy的默认值,也就是不设置它时的值,是false。

第二个timeout属性用于设置在最终放弃之前,等待位置数据的时间,以毫秒计。这里的10 000毫秒的意思就是最多等10秒钟。用户按下同意共享数据的按钮时,计时开始。默认情况下,timeout的值是0,也就是页面会无限期地等下去,不会触发超时错误。

最后一个maximumAge属性用于缓存位置数据。比如,把maximunAge设置为60 000毫秒,之前的数据最多保存1分钟。这样就可以减少重复调用地理定位的次数,但在用户移动的时候,时间越长结果也就越不精确。默认情况下,maximumAge的值是0,意思是不使用缓存的地理数据。(也可以将其设置为一个特殊的值Infinity,表示只要有缓存的数据,就使用该数据,无论时间多久都行。)

跟踪访客移动

我们的例子一直在使用getCurrentPosition()方法,这个方法可以说是地理定位的“心脏”。而除这个方法之外,地理定位对象还有两个方法,用于跟踪访客的位置,让你的页面在用户位置改变时能收到通知。

首先是watchPosition()方法,它与getCurrentPosition()看起来极为相似,也接收三个参数:成功函数(唯一必需的参数)、失败函数和选项对象:

navigator.geolocation.watchPosition(geolocationSuccess, geolocationFailure);

但watchPosition()与getCurrentPosition()的区别在于,前者可能会多次触发成功函数——不仅初始取得位置时会触发,而且以后每次检测到新位置都会触发。(但你无法控制设备多长时间检测一次新位置。你只要知道,位置不改变,设备不会给你发通知,只有位置改变它才会给你发通知。)对桌面计算机而言,因为它不会动,所以watchPosition()与getCurrentPosition()方法实际上作用相同。

与getCurrentPosition()不同,watchPosition()返回一个数值。如果你不想再关注位置变化,可以把它返回的这个数值传给clearWatch()方法。当然,你也可以不这么做,而在用户切换到其他页面之前一直接收通知:

var watch = navigator.geolocation.watchPosition(geolocationSuccess,
 geolocationFailure);
...

navigator.geolocation.clearWatch(watch); 

参考文档