移动端页面中的定位

本来页面中定位是一件很单纯的事,直接调用HTML5接口就是。

navigator.geolocation.getCurrentPosition(
    function (position) {
        // ...
    },
    onerror,
    options
);

但是这可能是浏览器的原生接口中最脆弱的一个,会因为各种原因失败,会提供不了足够的信息。然而对于移动web页面来说,定位往往是刚需,原生的接口不能满足需求时,必须有额外的复杂度多出来。所以这里说移动端页面中的定位,不是介绍如何使用HTML5的定位接口,而是讨论一个相对可靠、强大的移动端页面获取位置的解决方案,从提高成功率,提升定位速度以及丰富信息三个角度来说。

提高成功率

HTML5定位接口的成功率往往不高,绝大部分阵亡在请求用户授权上,即使这里通过了,还有一定概率无法获取位置(GPS没开,开了没用,用了没信号,有信号没成功)或者定位超时。所以可以从以下两个角度提高成功率:

  • 更多定位手段

    1. native应用辅助定位

    自家的app提供增强功能,当然这个只对自家app装机量高的大厂才有意义。这里有两个思路:

    1. web服务
    app启动一个本地的web服务,监听http请求,提供定位服务;移动端页面通过jsonp方式与本地的web服务器通信,调用定位功能。这个在ios上是行不通的,在android上就很有意义。
    
    1. cookie
    app定位成功后把结果写入webview的cookie中,这样在webview中打开页面就可以读到位置信息。具体怎么操作不太懂,ios能不能实现也不清楚。
    
    1. 服务端定位

    服务端定位只能依据IP。WIFI环境下通过IP定位相对靠谱。非wifi环境下,根据运营商提供的IP地址是可以定位到所在城市的,但没法更精确了。另好像某运营商比较坑爹,会一直提供手机号所在城市对应的IP地址;这意味着如果你是(该运营商)上海的手机号,你不管去哪,服务器拿到的你的IP都是上海的。

    1. 特殊环境中特殊手段

    如微信中、百度框中、支付宝服务窗等特殊环境,环境本身提供了定位能力,可以优先使用之。

    1. cookie共享

    以上通过任何方式定位成功的结果可以在cookie中保存起来,短时间内可以重复使用。这是基于用户这段时间内没有位移很多的假设的,所以这个时间间隔不能设置太久,不然就是bug了。

  • 引导用户授权共享位置信息

    通过各种方式(包括让用户对你的站点持有好感)鼓励用户允许使用浏览器的定位功能。

    如果用户点击了拒绝,定位的回调函数中是能捕捉到这个信息的,说服用户另作选择。(这太难了,这个时候浏览器已经记住了用户的选择,即使说服了用户,让用户手动去清楚浏览器中关于本站点的记忆也是很麻烦的,何况不同浏览器的操作方式还不一样)

提升速度

  • 灵活组合定位手段

    不是所有环境下都应该把所有手段用上,很多手段都可以通过一些简单的判断排除掉。比如IOS下就别尝试native辅助的方式了,不然坐等jsonp请求超时,怎么也得几秒吧。再如上述更多定位手段中的第三点“特殊环境”,是最容易被排除的,一般通过尝试读取某个全局变量就可以马上得出结论。

  • 耗时行为并行化

    现在你有了不止一个定位手段,不要挨个尝试,读cookie这种同步的行为除外。大部分定位方法,比如HTML5定位,native应用辅助定位,以及微信、支付宝服务窗定位接口等,都是需要耗时的过程。HTML5定位即使在较顺利的情况下都需要几百毫秒来完成。所以把这些需要耗时的手段一起调用起来,哪个先完成(成功的回调函数执行)就用哪个的结果。

丰富信息

HTML5定位是只能获取到当前的经纬度信息的:

{
  coords: {
    latitude: ...,
    longitude: ...,
    accuracy: ...
  }
}

如果你期望的是这样的:

{
  coords: {
    latitude: ...,
    longitude: ...,
    accuracy: ...
  },
  address: {
    city: ...,
    district: ...,
    street: ...
  }
}

就需要通过经纬度确定具体位置的服务(专业术语叫逆地理编码。。),大多数的地图服务会提供。比如 http://api.map.baidu.com/geocoder/v2/?ak=E4805d16520de693a3fe707cdc962045&callback=renderReverse&location=39.983424,116.322987&output=json&pois=1

要注意的是,大多数时候我们会把定位功能封装成一个独立的模块,由需要定位的地方调用。如果这个模块集成了通过第三方地图服务丰富位置信息的功能,这会导致定位行为中多出了一个串行的ajax步骤(有时候如果你需要的信息不能通过一次请求获取到时,甚至需要更多的ajax请求)。然而这个步骤是非必须的,计算两点的距离(很常见的场景,比如将对象按照离我最近排序),经纬度足以。在非wifi环境下,一次ajax请求的代价是不容忽视的,做好功能的裁剪,或者是通过一个标志所需信息内容的参数,或者是通过更细的模块划分,很有必要。

依照上述的思路实现一个通用的定位模块,其实是个挺考验代码能力的事,并行、串行行为流的维护,错误的捕捉处理,单次周期内多次调用的处理,行为的裁剪,不同级别结果的缓存等,并且要写得清晰易懂才好维护。