2013年10月10日 星期四

HTML5 Geolocation API (地理位置) 測試

月底要去上 HTML5 的課, 因此現在有空要把各 API 好好測試一番 (我做甚麼事都是盡量做到有備而來). 這次要從簡單又吸引人的 Geolocation API 著手. 這個東東為什麼炫呢?
  1. 它是社群互動最常用的功能 (聚會地點)
  2. 手機平板電腦的瀏覽器都有支援 (桌上型瀏覽器幾乎都已支援, IE9+ 也支援)
這個 API 是 W3C 訂定的, 而不是 HTML5 規格的始祖 WHATWG 工作小組. 其規格可在 W3C 網站取得 : http://dev.w3.org/geo/api/spec-source.html.

Geolocation 是一個很簡短的介面 (interface), 只定義了三個 Javascript 方法來規範 GPS 接收器傳回值的格式. 不過這個功能已經被 W3C 移出 HTML5 核心規格, 成為一個獨立的 API. 此 API 會從兩種來源取得裝置所在的地理資訊 :
  1. GPS 訊號 (所使用之裝置必須具有 GPS 接收器)
  2. IP 位址 (所使用之裝置必須具有網路卡, 且連上網路)
當終端設備搭載 GPS 接收器時, Geolocation API 會從 GPS 取得定位資訊, 否則就會從 IP 位址來推測. 當然, 從 GPS 所取得的地理資訊 (經緯度) 較精確, 而從 IP 所取得的只是推估值, 誤差可能很大, 因為 IP 係網路服務業者所指派, 所以推估出來的經緯度約略是業者機房的位置.

使用 Geolocation API 當然要對地理定位有基本的認識, 也就是要搞清楚以經緯度為基礎的地理座標系統. 簡言之, 把地球從北極到南極縱切的線稱為經線, 以英國格林威治天文臺為起點, 向東為東經, 向西為西經, 兩者大約在太平洋的國際換日線處交會, 該處是東經 180 度, 也是西經 180 度. 而緯度則是與赤道平行的橫切線, 範圍是 0~90 度, 赤道為 0 度, 赤道以北為北緯, 以南為南緯, 北極為北緯 90 度, 而南極則是南緯 90 度. 度又可以再除以 60 為 1 分, 1 分再除 60 為 1 秒. 詳細說明請參考維基百科. 台北的經緯度大約是 E121 N25, 高雄大約是 E120 N22, 可以在 http://card.url.com.tw/realads/map_latlng.php 這個網站查得. 
Geolocation API 實作於瀏覽器的 window.navigator 物件中, 因此要確認瀏覽器是否有支援此 API, 可用如下程式碼來檢查相容性 (window 可省略不寫) :

if (window.navigator.geolocation==undefined) {
    alert("此瀏覽器不支援地理定位功能!");
    }
else {
    var geolocation=window.navigator.geolocation; //取得 Geolocation 物件
    //地理定位程式碼
    }

或是 

if (window.navigator.geolocation) {
    var geolocation=window.navigator.geolocation; //取得 Geolocation 物件
    //地理定位程式碼
    }
else {
    alert("此瀏覽器不支援地理定位功能!");
    }

Geolocation 物件定義了三個方法, 其中 getCurrentPosition() 與 watchPosition() 用來取得位置資訊, getCurrentPosition() 取得目前位置 (僅一次, 沒有傳回值), 而 watchPosition() 則是會依照所設定之頻率, 持續取得裝置所在位置 (傳回值為 ID); 而 clearWatch() 則是用來停止 watchPosition(), 這兩個方法是一個配對, 正如 Javascript 的 setInterval() 與 clearInterval() 一樣. 呼叫 watchPosition() 時會傳回一個獨一無二的追蹤編號, 當要停止持續定位時, 就把此編號當作參數傳入 clearWatch() 即可.

getCurrentPosition() 與 watchPosition() 這兩個方法介面一樣 :

navigator.geolocation.getCurrentPosition(successCallback, [errorCallback [,options]])
navigator.geolocation.watchPosition(successCallback, [errorCallback [,options]])

必要參數 successCallback 與非必要參數 errorCallback 都是回傳函式, 當成功取得位置資訊時就呼叫 successCallback, 並傳入一個 Position 物件, 位置資訊即包含在此物件的屬性 coords 中 (此屬性是一個 Coordinates 座標物件). 當使用者拒絕或無法取得位置資訊時, 就呼叫 errorCallback, 並傳入一個 PositionError 物件, 錯誤資訊即包含在此物件中. 而 options 是 JSON 選項物件, 用來設定精密度, 逾時計時器, 以及持續取得位置的頻率.

Geolocation API 傳入 successCallback 的參數 Position 物件結構如下  :

Position 物件的屬性說明 
 coords Coordinates 座標物件, 存放傳回之位置資訊
 timestamp 取得位置資訊之時間 (毫秒, 自 1970/1/1 起)

Coordinates 物件的屬性說明 
 latitude 緯度 (單位 degree)
 longitude 經度 (單位 degree)
 altitude 高度 (單位 meter), 若裝置不支援則傳回 0
 accuracy 位置誤差  (單位 meter)
 altitudeAccuracy 高度誤差 (單位 meter), 若裝置不支援則傳回 0
 heading 移動方向 (單位 degree)
 speed 移動速度 (單位 meter/second)

而當錯誤發生時傳入 errorCallback 的參數 PositionError 物件則具有如下屬性  :

PositionError 物件的屬性說明 
 code錯誤碼, 有四個值 (整數) :
UNKNOWN_ERROR (=0) : 不明原因錯誤
PERMISSION_DENIED (=1) : 用戶拒絕位置服務, 或瀏覽器設定不允許使用位置服務
POSITION_UNAVAILABLE (=2) : 無法取得位置資訊 (例如在隧道或地下室)
TIMEOUT (=3) : 在計時器逾時仍未傳回位置資訊
 message錯誤訊息 (字串)

根據 code 屬性值, 我們可以顯示錯誤訊息字串給用戶, 告知無法取得定位資訊的原因. 而 message 屬性是程式開發階段除錯用的, 通常不直接顯示給用戶看. 常用的錯誤處理函式如下 :

    function errorCallback(error) {
      var errorTypes={
            0:"不明原因錯誤",
            1:"使用者拒絕提供位置資訊",
            2:"無法取得位置資訊",
            3:"位置查詢逾時"
            };
      alert(errorTypes[error.code]);
      //alert(error.message);  //測試時用
      }

API 會傳回一個 PositionError 物件, 我們須將其作為參數 error 傳入錯誤處理函式. 此處我們使用一個物件實體 errorTypes 來儲存錯誤碼與其解釋字串, 以錯誤碼 error.code 作為索引.

而選項物件 options 則定義了三個屬性如下 :

options 物件的屬性說明 
 enableHighAccuracy設定取得高精確度之位置資訊 (true/false, 預設 false). 設為 true 時, 瀏覽器會盡可能取得最高精確度位置, 通常會優先啟動 GPS 感測器, 若裝置沒有 GPS, 就會以 WIFI 熱點或行動基地台三角定位來計算位置, 因此設為 true 會加速耗用行動裝置電池.
 maximumAge設定上一次取得之位置資訊的有效期限 (單位毫秒, 預設為 0), 每次取得位置資訊時都會保存在記憶體中, 再次呼叫位置查詢時 API 會先查看記憶體中所保存之上一次查詢紀錄是否已經超過 maximumAge, 是的話就重新查詢, 否則就直接傳回記憶體中的舊資料, 不會重新查詢. 此屬性值大小也會決定 watchPosition 重新查詢的頻率, 越小越頻繁, 若設為 0 表示每次呼叫都必定重新取得新位置. 
 timeout逾時計時器 (單位毫秒), 預設無限大 (infinity), 若超過此時間仍未取得位置資訊, 將會觸發錯誤事件.

接下來, 我們就來測試一下裝置所在位置的定位資訊 :

測試範例 1 : http://tony1966.xyz/test/html5test/geolocation_1.htm [看原始碼]

範例一中, 我們把三個參數都傳進 getCurrentPosition 中, maximumAge 設為 0 表示每次執行都會重新查詢目前位置, timeout 設為 600000 表示查詢等候時間可容許至 10 分鐘. 傳回的位置資訊我們把它放在表格中, 每一列的第二格都設定 id 名稱為各屬性名稱, 利用 $i 函數來設定位置資訊 :

    if (navigator.geolocation) {
        var geo=navigator.geolocation;
        var option={
              enableAcuracy:false,
              maximumAge:0,
              timeout:600000
              };
        geo.getCurrentPosition(successCallback,
                               errorCallback,
                               option
                               );
        }
    else {alert("此瀏覽器不支援地理定位功能!");}

    function successCallback(position) {
      $i("latitude").innerHTML=position.coords.latitude;
      $i("longitude").innerHTML=position.coords.longitude;
      $i("altitude").innerHTML=position.coords.altitude;
      $i("accuracy").innerHTML=position.coords.accuracy;
      $i("altitudeAccuracy").innerHTML=position.coords.altitudeAccuracy;
      $i("heading").innerHTML=position.coords.heading;
      $i("speed").innerHTML=position.coords.speed;
      $i("timestamp").innerHTML=position.timestamp;
      }


Chrome 30.0.1599

Firefox 24.0

IE9.0.8112

以上是使用筆電在 wifi 連線情況下的結果,  注意, IE8 不支援 Geolocation, 須 IE9+ 才有. 如果是用桌電以社區網路 (LAN) 固網連線, 那麼在 Chrome 就無法查得位置, 傳回錯誤碼 2 (position unavailable) 如下圖 :

Chrome 在 LAN 連網下無法查得位置 

在 IE9 仍可正常顯示位置資訊, 但是精確度卻低得嚇人,測試位置在高雄, 621 公里是在太平洋耶!

IE9 在 LAN 連網下精確度降低
 
如果是 Firefox 在 LAN 連網下則是會超過 timeout 計時器造成逾時, 回覆 code=3 (timeout).
依據 HTML5 規格, 呼叫 getCurrentPosition 或 watchPosition 方法時, 瀏覽器必須預設詢問使用者是否同意提供所在位置資訊, 以保護隱私權. 各瀏覽器之定位資訊設定如下 :


Chrome : 設定/隱私權/內容設定

Chrome : 內容設定/位置


Firefox : 工具/頁面資訊

Firefox : 透露地理位置

IE 9 : 網際網路選項/隱私權 

可見, 詢問選項應該被列為預設值, 但如果使用者要永遠都透露自己的位置, 也是可以的. 注意, IE 9 中若勾選 "永遠不允許網站要求您的所在位置" 的話, 那呼叫 Geolocation 方法時就會永遠傳回錯誤 : "使用者不允許透露位置", 所以預設是不勾選, 這樣 IE 就會詢問了.


Chrome 詢問視窗


Firefox 詢問視窗

IE9 詢問視窗

在 W3C 的 Geolocation 規格草案中提到 : "Future versions of the API may allow additional attributes that provide other information about this position (e.g. street addresses).". 亦即, Position 物件將來還會有一個 address 屬性. 在博碩出版的 "一定要學會的 HTML5+CSS3 網頁設計應用" 一書中, 也有列出 Position 物件的 address 屬性 (為一 nslDOMGeoPositionAddress 物件) 如下 :

nslDOMGeoPositionAddress 物件說明 
 country 國家
 countryCode 國碼
 postalCode 郵遞區號
 region 地區
 premises 區域 
 county 州郡 
 city 市 
 street 街道名
 streetNumber 街道號碼

但事實上目前還沒有瀏覽器實做 address 屬性, 下列範例二我們用 JSON.stringify() 方法來將 Position 物件變成字串就可以看出瀏覽器有沒有實做 address 屬性了 :

測試範例 2 : http://tony1966.xyz/test/html5test/geolocation_2.htm [看原始碼]

範例二裡, 我們在頁面上放了一個 p 元素來輸出結果, 並改寫了 successCallback :

    <p id="output"></p>
    function successCallback(position) {
      $i("output").innerHTML=JSON.stringify(position);
      }

下面是在 Chrome 瀏覽器執行的結果, 可見 Chrome 並未實做 address 屬性 : 


{"timestamp":1381453563427,"coords":{"speed":null,"heading":null,"altitudeAccuracy":null,"accuracy":34,"altitude":null,"longitude":120.3156676,"latitude":22.6336712}}

範例一中, 我們於表格裡一一列舉 coords 的屬性, 其實這可以用物件迴圈來做, 以一個陣列 arr 來儲存要輸出的屬性及其值, 最後再以跳行 br 串接起來 :  

    <p id="output"></p>
    function successCallback(position) {
      var arr=new Array();
      for (var i in position.coords) {
           arr.push(i + "=" + position.coords[i]);
           }
      $i("output").innerHTML=arr.join("<br>");
      }

測試範例 3 : http://tony1966.xyz/test/html5test/geolocation_3.htm [看原始碼]

在 Chrome 瀏覽器顯示 : 

speed=null
heading=null
altitudeAccuracy=null
accuracy=101
altitude=null
longitude=120.30913050000001
latitude=22.6608414

接著來測試持續監視位置的方法 watchPosition(), 在下列範例四中, 我們加上兩個按鈕, 按下 id 為 start 的按鈕啟動持續監視, 而按下 id 為 stop 者則停止監視 :

測試範例 4 : http://tony1966.xyz/test/html5test/geolocation_4.htm [看原始碼]

    <input type="button" id="start" value="開始">
    <input type="button" id="stop" value="停止">

    var geolocation=navigator.geolocation; //取得 Geolocation 物件
   $i("start").onclick=function() {
        var option={
              enableAcuracy:false,
              maximumAge:1000,
              timeout:60000
              };
        watchID=geolocation.watchPosition(successCallback,
                                          errorCallback,
                                          option
                                          );
$i("start").value="位置監視中"; //變更按鈕顯示
        }; //end of onclick
 $i("stop").onclick=function() {
        geolocation.clearWatch(watchID); //停止監視
        $i("start").value="開始"; //恢復按鈕顯示
        }; //end of onclick
 }; //end of window.onload
   function successCallback(position) {
      var arr=new Array();
      for (var i in position.coords) {
           arr.push(i + "=" + position.coords[i]);
           }
      arr.push(position.timestamp);
     $i("output").innerHTML=arr.join("<br>");
      }

範例四必須用行動裝置測試, 只要偵測到位置有變動便會再次查詢位置資訊, 網頁就會立即更新, 我特地騎腳踏車去全聯買東西, 半路上停下來看, 果真只要走一小段路就會自動更新網頁 (當然要先開啟行動網路打開網頁, 之後可以關掉只開 GPS). 如果用在家用筆電測試, 只有啟動時抓到一次之後就不變了, 我猜是因為沒有切換 wifi 信號源, 所以位置沒變就不會再次查詢.

測試範例 5 : http://tony1966.xyz/test/html5test/geolocation_5.htm [看原始碼]

Google Map Javascript API  v3


~~未完待續

沒有留言:

張貼留言