获取用户位置,并进行个性化推荐,是一个位置应用非常重要的功能,也是目前 LBS 应用的基石。如果没有用户的位置,例如百度地图等应用就不能进行周边搜索,更不用说要持续跟踪用户位置的导航功能。
目前使用浏览器获得设备地理位置,有两种方法:第一种是使用 JavaScript Geolocation API 直接编程获取;第二种是首先获取设备的 IP 地址,然后通过查询相应的 IP 与地理位置的数据库,得到用户的地理位置。
下面,我们来分别介绍这两种方法。
目前 JavaScript 原生的 API 能直接通过浏览器获取用户地理位置,但必须得到用户许可。w3c 规定了 Geolocation API Specification(目前只是草案),在 这里
可以看到相应的规范详细内容。
关于定位方式,规范中描述如下:
The API itself is agnostic of the underlying location information sources. Common sources of location information include Global Positioning System (GPS) and location inferred from network signals such as IP address, RFID, WiFi and Bluetooth MAC addresses, and GSM/CDMA cell IDs, as well as user input. No guarantee is given that the API returns the device’s actual location.
规范中说,API 本身并不知道位置信息的来源,可能的来源包含 GPS,IP 地址,RFID,WiFi 和 蓝牙 MAC地址,或者 GSM/CDMA cell ID ,或者用户的输入,且不能保证 API 会返回设备的真实地理位置。真实情况可能与浏览器特定的实现有关。
这个规范主要介绍了一些相关的接口和对象,下面就来看看都有那些内容。
规范指定 Geolocation 对象是用来通过脚本编程来获得与用户的设备关联的位置信息, 在 ECMAScript 中,Geolocation 被定义为 Navigator 对象的只读属性:
partial interface Navigator {
readonly attribute Geolocation geolocation;
};
Geolocation 的方法有三个: getCurrentPosition、watchPosition 和 clearWatch,调用 getCurrentPosition 和 watchPosition 都必须得到用户的允许,才能执行后续的操作。
interface Geolocation {
void getCurrentPosition(PositionCallback successCallback,
optional PositionErrorCallback errorCallback,
optional PositionOptions options);
long watchPosition(PositionCallback successCallback,
optional PositionErrorCallback errorCallback,
optional PositionOptions options);
void clearWatch(long watchId);
};
getCurrentPosition 方法接受三个参数,第一个参数是执行成功的回调函数,第二个参数是发生错误时要调用的函数,最后一个参数是函数的选项,后两个参数是可选的。当调用该函数时,它会立刻返回,但是异步地尝试获取设备位置,如果尝试成功,那么第一个参数传入的函数会立刻执行,参数是 Position ,一个包含 latitude 和 longitude 的对象;如果获取位置失败,那么将立刻调用第二个参数传入的函数,同样,传入回调函数的是一个 PositionError 对象,包含错误原因。
执行的步骤是这样的:
watchPosition 是一致监测用户的位置变化,位置变化就会触发相应的事件,这也是位置应用进行导航的基石。函数接受三个参数,接受参数和执行机制和getCurrentPosition 一致,但是执行的步骤有一点不同。
刚才提到,只有在用户允许位置共享时才能继续操作,如果用户没有同意,那么这两个函数会调用 errorCallback 函数,code设置为 PERMISSION_DENIED。
getCurrentPosition 和 watchPosition 都接受 PositionOptions 对象作为第三个参数, 在 ECMAScript 中,它的定义如下:
dictionary PositionOptions {
boolean enableHighAccuracy = false;
[Clamp] unsigned long timeout = 0xFFFFFFFF;
[Clamp] unsigned long maximumAge = 0;
};
这三个参数都是可选的,enableHighAccuracy 属性指定是否提供更准确的位置,如果设置为 true,会获取设备能获取到的最准确的位置,包括其 GPS 获取的位置,但是这样可能会导致反应时间过程,并且耗费更多电量,如果是手机的话,那么这一点就相当重要了;timeout 设置了反应时间的上限,如果达到了 timeout 设置的时间,还没有返回结果,那么就会执行 timer 取消所有与位置获取相关的操作; maximumAge 设置了一个Position 对象的缓存时间。
Position 是 getCurrentPosition 和 watchPosition 成功获取位置后返回的对象,其中包含位置相关信息。Position 在 ECMAScript 中的定义:
interface Position {
readonly attribute Coordinates coords;
readonly attribute DOMTimeStamp timestamp;
};
包含一个 Coordinates 对象,表示位置;一个 DOMTimeStamp 对象,用来表示一个时间戳,指示发生事件的日期和时间(从 epoch 开始的毫秒数),epoch 是一个事件参考点,这里是客户机启动的时间。这个对象和主题相关性不大,这里就不再赘述。
Coordinates 表示坐标相关的信息,Coordinates 在 ECMAScript 中的定义:
interface Coordinates {
readonly attribute double latitude;
readonly attribute double longitude;
readonly attribute double? altitude;
readonly attribute double accuracy;
readonly attribute double? altitudeAccuracy;
readonly attribute double? heading;
readonly attribute double? speed;
};
包含 latitude(纬度)、longitude(经度),是地理坐标形式;altitude(高程),基准面为 WGS 84
平面;accuracy 表示经纬度的准确程度;altitudeAccuracy 表示高程的准确程度;heading 表示设备的行进方向; speed 表示设备行进速度。
PositionError 表示在获取位置发生错误时,返回的有关错误细节的对象。PositionError 在 ECMAScript 中的定义:
interface PositionError {
const unsigned short PERMISSION_DENIED = 1;
const unsigned short POSITION_UNAVAILABLE = 2;
const unsigned short TIMEOUT = 3;
readonly attribute unsigned short code;
readonly attribute DOMString message;
};
前三个参数表示错误类型,上面已经提到,就不再赘述;code 表示错误的类型,是前三个类型之中的一个;message 描述遇到的错误的细节。
// 请求一个 Position。 接受缓冲不超过10分钟(600000毫秒)的 Position
// 如果没有满足条件的缓存 Position,就获取一个新的 Position对象
navigator.geolocation.getCurrentPosition(successCallback,
errorCallback,
{maximumAge:600000});
function successCallback(position) {
// 由于使用了 'maximumAge' 选项, position 对象
// 保证最多已经获取 10 分钟
}
function errorCallback(error) {
// 做一些错误处理
}
在 JavaScript 端获得 IP 是行不通的,必须在服务器端获取,最简单的方法是使用 $_SERVER['REMOTE_ADDR']
变量。
获取了 IP 地址后,可以通过查询 IP 与真实地址的数据库,将 IP 地址转换到现实中的地址,这里的 IP地址数据库,我们使用 freegeoip.net
的服务,使用 JSONP 技术请求其提供的服务。
freegeoip.net 提供对外的查询 IP 地址对应地理位置的 HTTP API,它使用一个 IP 与所属城市对应的数据库,附带对应时区和地理经纬度坐标等相关信息,对外提供服务。
一个人最多允许一个小时进行 10000 次查询,如果达到上限,所有的查询都会返回 HTTP 403 。freegeoip web服务器是免费并且开源的,你可以下载这个数据库,并且在本地配置使用。
一般的异步调用技术,比如 AJAX,是不能跨域请求的,比如你的网站是 example.com
,那么你使用 AJAX 技术访问 example2.com
请求数据,就不能成功返回数据。这里我们调用 freegeoip.net 的服务,使用 AJAX 技术显然不行。而一种跨域访问技术就可以帮我们解决问题: JSONP。
JSONP 由两部分组成,回调函数和数据。回调函数是当响应到来的时候应该在页面中调用的函数,回调函数的名字一般是在请求中指定的,而数据就是传入回调函数中的 JSON 数据。例如下面是一个典型的 JSONP 请求。
http://freegeoip.net/json/userIp/?callback=getAddress"
userIp 就是获取的用户 IP 地址,这里指定的回调函数的名字就是 getAddress( )。
JSONP 是通过动态 元素来使用的,使用时可以为 src 属性指定一个跨域 URL,如上面的 URL,然后将
插入到 HTML 中,就会执行。具体看下面的例子。
function getAddress(address){
alert("IP: " + address.ip + "\n longitude: " + address.longitude + "\n latitude: " + address.latitude);
}
function getUserLocation(){
var script = document.createElement("script"), userIp;
$.ajax({
url: "./getUserIp.php?",
success: function(result){
userIp = JSON.parse(result).userIp;
}
});
script.src = "http://freegeoip.net/json/"+userIp+"/?callback=getAddress";
document.body.appendChild(script);
}
getUserLocation();
getUserLocation 方法是先利用 AJAX 获得服务器端获得的用户 IP 地址,复制给 userIp变量,构建一个跨域请求 URL ,然后动态创建一个 元素,将src 属性赋值 URL,并插入到 HTML 中,即可。getAddress( ) 即是接收到数据时的回调函数,返回的是一个 JSON 对象,可以直接访问。
例如,我自己访问,看看能查询出什么结果?
可以根据以上信息进行访问,获得想要的地理位置信息,并进行一些操作。
通过浏览器获得用户位置的两种方法中,推荐第二种,因为第一种 Geolocation API Specification 比较新, 2014 年 7 月 11 日刚刚发布的最新版本,且并不是所有的浏览器都支持;第二种在任何情况下都能正常执行,只是需要前后端协作,后端获取 IP,传给前端,前端发送请求,查询地理位置,不如第一种简洁。
实际使用中,根据实际情况进行选择。