HTML5 Geolocation學習

天月發表於2019-02-16

GeolocationAPI學習,我寫的挺枯燥的,直接跳到最後看示例。

5.1 位置資訊

HTML5 Geolocation API的使用方法相當簡單。請求一個位置資訊,如果使用者同意,瀏覽器就會返回位置資訊,該位置資訊是通過支援HTML5地理定位功能的底層裝置(手機、膝上型電腦等)提供給瀏覽器的。位置資訊由維度、經度座標和其他一些後設資料組成。

5.1.1 經度和維度座標

位置資訊主要有一對維度和經度座標組成。
例如座標:維度:39.17222、維度:-120.13778
其中維度(距離赤道以北或以南的數值表示)是39.17222,經度(距離英國格林威治以東或以西的數值表示)是-120.13778。

經緯度可以用以下兩種方式表示:


十進位制格式(例如:39.17222);
DMS(Degree Minute Second,角度)格式(例如,66°33` 38" )

除了經緯度,HTML5 Geolocation還提供了位置座標的準確度,以及根據瀏覽器的硬體裝置是否能夠提供海拔、海拔準確度、形式方向和速度等後設資料,若無法提供則返回null。

5.1.2 位置的由來

Geolocation僅是檢索裝置提供位置資訊的API,並且通過該API檢索到的資料只具有某種程度的準確性。並不能保證裝置返回的實際位置是精確的。

裝置可以使用的資料來源:


IP地址
三維座標
    GPS(Global Positioning System,全球定位系統)
    從RFID,Wi-Fi和藍芽到Wi-Fi的MAC地址:
    GSM或CDMA手機的ID

使用者自定義資料

5.1.3 IP地址地理定位資料

基於IP地址的地理定位的實現方式是:自動查詢使用者的IP地址,然後檢索器註冊的實體地址。因此,如果使用者的IP地址是ISP提供的,其位置往往是由服務供應商的實體地址決定的,該地址可能距離使用者數千米。


表5-1 基於IP地址的地理位置資料的優缺點  
    優點                缺點
任何地方都可用      不精確
在伺服器端處理      運算代價大

5.1.4 GPS地理定位資料

只要可以看到天空的地方,GPS就可以提供非常精確的定位。GPS定位是通過手機執行在地球周圍的多個GPS衛星的訊號實現的。但是,他的定位時間可能較長,因此它不適合需要快速響應的應用程式。

    表5-2 基於GPS的地理定位資料的優缺點
優點                缺點
很精確          定位時間長,使用者耗電量大
                室內效果不好
                需要額外硬體裝置
                

5.1.5 Wi-Fi地理定位資料

基於WiFi的地理定位資訊是通過三角距離計算得出的,這個三角距離值得是使用者當前位置到一隻的多個WiFi接入點的距離。不同於GPS,WiFi在室內也非常準確。

        表5-3 基於Wi-Fi的地理定位的優缺點
    優點                缺點
    精確            在鄉村這些無線接入點較少的地區效果不好
可在室內使用        
可以簡單、快捷定位

5.1.6 手機地理定位資料

基於手機的地理定位資訊是通過使用者到一些基站的三角距離確定的。這種方法可提供相當準確的位置結果。這種方法通常同基於Wi-Fi和基於GPS的地理定位資訊結合使用。表5-4是基於手機的地理定位資料的優缺點。

表5-4               基於手機的地理定位資料的優缺點
    優點                    缺點
    相當準確            需要能夠訪問手機或其modem的裝置
    可在室內使用        在基站較少的偏遠地區效果不好
    可以簡單、快捷定位

5.1.7 使用者自定義的地理定位資料

應用程式可能允許使用者輸入他們的地理、郵政編碼和其他的一些詳細資訊。應用程式可以利用這些資訊來提供位置感知服務。

            表5-5 使用者自定義的地理定位資料的優缺點
    優點                                        缺點
使用者可以獲得比程式定位服務更準確的位置資料  可能很不準確,特別是當使用者位置變更後
允許地理定位服務結果作為備用位置資訊
使用者自行輸入可能比自動檢測更快

5.2 Geolocation的瀏覽器支援情況

目前主流瀏覽器都支援。

5.4 使用HTML5 Geolocation API

5.4.1 瀏覽器支援檢查

function loadDemo(){
    if(navigator.geolocation){
        document.getElementById(`support`).innerHTML = "Geolocation supported";
    }else{
        document.getElementById("support").innerHTML = "Geolocation is not supported in your browser.";
    }
}

5.4.2 位置請求

有兩種型別的位置要求:
1 單次定位的請求
2 重複性的位置更新請求

1 單次定位的請求

1 只檢索或請求一次使用者位置即可。例如,如果要查詢在接下來的一個小時內放映某大片的最近的電影院。就可以使用程式碼清單5-2所示的簡單HTML5 Geolocation API.

單次定位請求

void getCurrentPosition(in PositionCallback successCallback,
                        in optional PositionErrorCallback errorCallback,
                        in optional PositionOptions options);
                        

這個函式接受一個必須引數和兩個可選引數
函式引數successCallback為瀏覽器指明位置資料可用時呼叫的函式。因為像獲取位置資料這樣的操作可能需要較長的時間才能完成,所以這個引數很重要。沒有使用者希望在檢索位置時瀏覽器被鎖定,也沒有開發人員希望它的程式無限期被暫停(特別是要成功取得位置資訊,經常必須等待使用者的許可)。successCallback是收到實際位置資訊並進行處理的地方。

跟絕大多數程式設計場景一樣,最好提前準備出錯處理。位置資訊請求很可能因為一些不可控因素失敗,對於這些情況,你可能需要提供一個跟使用者解釋或者提示其重試的errorCallback函式。雖然此引數是可選的,不過建議選用。

最後,options物件可以調整HTML5 Geolocation服務的資料收集方式。這是一個可選引數,隨後在論。

假設在頁面上已經建立了一個名為updateLocation()的Javascript函式,它使用最新的位置資料更新頁面內容,同樣的,也建立了一個handleLocationError()函式來處理錯誤情況。而請求訪問使用者位置的核心程式碼如下所示:

navigator.geoocation.getCurrentPosition(updateLocation,handleLocationError);

updateLocation()函式:

只要瀏覽器具備訪問位置資訊的條件,就會呼叫updateLocation()函式,該函式只接受一個引數:位置物件.這個物件包含座標(coords特性)和一個獲取位置資料時的時間戳。在實際開發中不一定需要時間戳,重要位置資料都包含在了coords特性中。

以下是前三個特性:
latitude(緯度)
logitude(經度)
accuracy(準確度)
latitude和longitude將包含HTML5Geolocaiton服務測定的最佳十進位制使用者位置。accuracy將以m指定維度和經度值與實際位置的差距,置信度為95%。

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;
}

handleLocationError()函式

因為位置計算服務很可能出錯,對於HTML5 Geolocation應用程式來說處理錯誤非常重要。而該API定義了所有需要處理的錯誤情況的編號。錯誤編號設定在錯誤物件中,錯誤物件作為code引數傳遞給錯誤處理程式。這些錯誤編號為:

PERMISSION_DENIED(錯誤編號為1)—使用者選擇拒絕瀏覽器獲得其位置資訊。
POSITION_UNAVAILABLE(錯誤編號為2)—嘗試獲取使用者位置資料,但失敗了。
TIMEOUT(錯誤編號為3)–設定了可選的timeout值。嘗試確定使用者位置的過程超時。

使用錯誤處理函式
function hadleLocationError(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;
    }
}
可選的地理定位請求特性:

如果要同時處理正常情況和錯誤情況,就應該把注意力集中到三個可選引數(enableHight-Accuracy,timeout和maximumAge)上,將這三個可選引數傳遞給HTML5 Geolocation服務以調整資料收集方式。請注意,這三個引數可以使用JSON物件傳遞,這樣更便於新增到HTML5Geolocation請求呼叫中。

enableHighAccuracy:啟用該引數,則通知瀏覽器啟用HTML5 Geolocation服務的高精度模式。引數的預設值為false。如果啟用該引數,可能沒有任何差別,也可能會導致機器花費更多的時間和資源來確定位置,所以請謹慎。


timeout:可選值,單位為ms,告訴瀏覽器計算當前位置所允許的最長時間。如果在這個時間內未完成計算,就會呼叫錯誤處理程式。其預設值為Infinity,即無窮大或無限制。

maximumAge : 這個值表示瀏覽器重新計算位置的時間間隔。它是一個以ms為單位的值。此值預設為0,這意味著瀏覽器每次請求超時時必須立即重新計算位置。

注意:地理定位API不允許我們為瀏覽器指定多長時間重新計算一次位置資訊。這是完全由瀏覽器的實現所決定的。我們能做的就是告訴瀏覽器maximumAge的返回值是什麼。實際頻率是我們一個無法控制的細節。

現在更新我們的位置請求,讓其包含一個使用JSON物件表示的可選引數,如下所示:

navigator.geolocation.getCurrentPostion(updateLocation,handleLocationError ,{timeout:10000});

這個新呼叫告訴HTML5 Geolocation,任何處理時間超過10s的位置請求都應該呼叫handleLocationError函式處理編號對應的TIMEOUT的錯誤。

2 重複性的位置更新請求

有時候,僅更新一次是不夠的。還好,Geolocation服務的設計者使應用程式可以在單詞請求使用者定位和以既定時間間隔多次請求使用者位置間相互轉換。事實上,轉換的方式很簡單,只需要變換函式請求即可,如下所示:

一次更新:

navigator.geolocation.getCurrentPosition(updateLocation,handleLocationError);

重複更新:

navigator.geolocation.wathcPosition(updateLocation,handleLocationError);
// 只要使用者的位置發生變化,Geolocation服務就會呼叫updateLocaiton處理程式,它的效果就像是程式在監視使用者的位置,並會在其變化時及時通知使用者一樣。

重複更新的好處:如有一個頁面,隨著觀察者在城鎮周圍移動,網頁上的方向指示也隨之改變,再假設關於一個加油站的頁面,隨著使用者開車在高速公路上持續行駛,頁面不斷更新顯示最近的加油站。另外,還可以在一個頁面中記錄和傳送使用者位置來實現回溯已走的路線。如果位置資訊一發生改變,就能傳遞給應用程式,那麼前面所假設的所有服務都會變得很容易實現。

如果應用程式不再需要接受有關使用者的持續位置更新,則只需呼叫clearWatch()函式,如下所示:

navigator.geolocation.clearWatch(watchId);

watchId表示一個唯一的監視請求以便將來取消監視。所以,如果應用程式要停止接收位置更新資訊,可以參照以下程式碼。

var watchId = navigator.geolocation.watchPosition(updateLocation,handleLocationError);
 // 基於持續更新的位置資訊的一些功能
 //......
 // ok,現在我們可以停止接收位置更新資訊了
 navigator.geolocation.clearWatch(watchId);

4.5 使用HTML5 Geolocation構建實時應用

接下來我們使用多次請求特性構建一個簡單有用的Web應用程式—距離跟蹤器。

基於HTML5 Geolocation提供的強大定位服務,開發人員可以建立一個網頁來跟蹤從網頁被載入的地方到目前所在位置所經過的距離。雖然它在桌上型電腦上不大實用,但是對於手機是很理想的,只要在手機瀏覽器中開啟這個示例頁面並授予其位置訪問的許可權,每隔幾秒鐘,應用程式就會根據剛才走過的距離更新,並將其增加到總距離中。

距離計算公式使用Haversine公式來實現,這個公式能夠根據經緯度來計算地球兩點距離。程式碼如下(這個公式後面看看有沒有時間研究一下,有就後續補上):

Number.prototype.toRadians = function() {
      return this * Math.PI / 180;
    }


    function distance(latitude1, longitude1, latitude2, longitude2) {
      // R is the radius of the earth in kilometers
      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;
    }

4.5.1 編寫HTML顯示程式碼

以簡單的表格進行展示,分行顯示緯度、經度、準確度和以ms為單位的時間戳。

<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"><h5>Latitude</h5></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,然後將檢測結果顯示在頁面上。最後,程式碼會請求檢測使用者位置。

新增loadDemo()方法
var totalDistance = 0.0;
var lastLat;
var lastLong;
function updateStatus(message){
    document.getElementById("status").innerHTML = message;
}
function loadDemo(){
    if(navigator.geolocation){
        updateStatus.geolocation(){
            updateStatus("HTML5 Geolocation is supported in your browser");
            navigator.geolocation.watchPosition(updateLocation,handleLocationError,{maximumAge:20000});
        }
    }
}
window.addEventListener(`load`,loadDemo,true);

對於錯誤處理,我們使用之前提到過的那段程式碼,我們將檢查收到的所有錯誤編號,並更新頁面上的狀態資訊。

新增錯誤程式碼

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;
        }
    }

而我們大部分工作都將在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;

        // 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: " + currentDistance.toFixed(4) + " km";
        }


        lastLat = latitude;
        lastLong = longitude;

        updateStatus("Location successfully updated.");
    }

首先獲取座標資料後,記錄所有資訊。即收集經緯度,準確度和時間戳,然後將這些資料更新到表格中。
時間戳對於使用者而言沒有意義,主要是提供程式使用,可以將時間戳替換成便於使用者識別的時間指示器,或者將其完全刪除。

準確度是以米為單位的,顯示不準確的值會向使用者提供錯誤的位置資訊,因此,需要將過濾掉有所有低經度的位置更新資料。

if (accuracy >= 500) {
            updateStatus("Need more accurate values to calculate distance.");
            return;
        }

完整程式碼如下

<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <title>HTML5 Geolocation Odometer</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body onload="loadDemo()">

<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"><h5>Latitude</h5></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;

    Number.prototype.toRadians = function() {
      return this * Math.PI / 180;
    }


    function distance(latitude1, longitude1, latitude2, longitude2) {
      // R is the radius of the earth in kilometers
      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;
    }


    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});
        }
    }

    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: " + currentDistance.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>
</body>
</html>

Geolocation匆匆忙忙結束了,請大家當個鍵盤俠,多多指點,畢竟鍵多識廣。

相關文章