Rozdział 7. Geolokalizacja

> Dodaj do ulubionych

Do rzeczy

Geolokalizacja to sztuka określania swojego położenia na ziemi i ewentualnie dzielenia się tą informacją z zaufanymi osobami. Istnieje kilka sposobów na sprawdzenie gdzie jesteś — można posłużyć się adresem IP, informacjami dotyczącymi bezprzewodowego połączenia internetowego, sprawdzić, z którą wieżą telefonii komórkowej komunikował się twój telefon komórkowy albo użyć urządzenia GPS obliczającego szerokość i długość geograficzną na podstawie informacji otrzymanych z krążącego nad ziemią satelity.

Pytanie do profesora Kodeckiego

P: Geolokalizacja brzmi groźnie. Czy da się to wyłączyć?
O: Kiedy jest mowa o udostępnianiu informacji o swoim fizycznym położeniu wspólnemu serwerowi, zawsze jest podnoszona kwestia prywatności. W API geolokalizacji napisano: „Aplikacje klienckie nie mogą wysyłać informacji lokalizacyjnych do witryn internetowych bez wyraźnego pozwolenia od użytkownika”. Innymi słowy, jeśli informacje o twoim miejscu pobytu mają zostać ujawnione, program musi uzyskać twoją zgodę. Jeśli nie chcesz, nie musisz jej dawać.

API geolokalizacji

API geolokalizacji umożliwia udostępnianie informacji o miejscu pobytu zaufanym serwisom internetowym. Szerokość i długość geograficzna są na stronie dostępne poprzez JavaScript. Za pomocą skryptów informacje te można wysłać do serwera, aby je jakoś ciekawie wykorzystać, np. znaleźć pobliskie firmy albo wyświetlić mapę okolicy.

W poniższej tabeli widać, że API geolokalizacji jest obsługiwane przez większość przeglądarek i urządzeń mobilnych. Ponadto przy użyciu specjalnych bibliotek do współpracy da się też namówić niektóre starsze przeglądarki i urządzenia. O tym będzie mowa w dalszej części rozdziału.

Obsługa API geolokalizacji
IEFirefoxSafariChromeOperaiPhoneAndroid
9.0+3.5+5.0+5.0+10.6+3.0+2.0+

Platformy mobilne oprócz standardowego API geolokalizacji obsługują jeszcze wiele różnych własnych API. Więcej na ten temat dowiesz się w dalszej części rozdziału.

Pokaż mi jakiś kod

API geolokalizacji jest zbudowane wokół nowej własności globalnego obiektu navigator: navigator.geolocation.

Poniżej przedstawiony jest najprostszy sposób jego wykorzystania:

function get_location() {
  navigator.geolocation.getCurrentPosition(show_map);
}

Nic tu nie wykrywamy, nie obsługujemy błędów i nie ma żadnych opcji. Twoja aplikacja powinna zawierać implementację przynajmniej pierwszych dwóch z wymienionych składników. Aby sprawdzić, czy API geolokalizacji jest obsługiwane, można użyć biblioteki Modernizr:

JA UMIEĆ LOKALIZOWAĆ?

function get_location() {
  if (Modernizr.geolocation) {
    navigator.geolocation.getCurrentPosition(show_map);
  } else {
    // Brak standardowej obsługi, więc trzeba zastosować jakieś wyjście awaryjne
  }
}

Co zrobisz wiedząc, że przeglądarka obsługuje geolokalizację zależy od ciebie. Technikę awaryjną z użyciem JavaScriptu objaśnię nieco dalej, a na razie napiszę, co się dzieje podczas wywołania metody getCurrentPosition(). Jak napisałem na początku tego rozdziału, na geolokalizację musi zgodzić się użytkownik. To znaczy, że przeglądarka nigdy cię nie zmusi do ujawnienia serwerowi swojego miejsca pobytu. Pytanie o pozwolenie na udostępnienie tych informacji wygląda różnie w przeglądarkach. Przykładowo w przeglądarce Mozilla Firefox, wywołanie metody getCurrentPosition() API geolokalizacyjnego powoduje wyświetlenie paska informacyjnego w górnej części okna programu.

Całkiem sporo tu się dzieje. Użytkownik

  • zostaje poinformowany, że strona chce poznać jego lokalizację;
  • zostaje poinformowany która strona chce znać te informacje;
  • może wejść na stronę „Location-Aware Browsing” Mozilli, na której znajduje się wyjaśnienie o co w ogóle chodzi (krótko: Google udostępnia lokalizację i zapisuje twoje dane zgodnie z zasadami Location Service Privacy Policy)
  • może pozwolić na udostępnienie informacji o miejscu pobytu;
  • może nie pozwolić na udostępnienie informacji o miejscu pobytu;
  • może nakazać przeglądarce zapamiętać swój wybór (pozwolenie albo brak pozwolenia), aby więcej nie wyświetlała tego pytania na tej stronie

Ponadto pasek informacyjny jest:

  • niemodalny, a więc nie uniemożliwi przejścia na inną kartę lub do innego okna;
  • przypisany do karty, a więc zniknie po przejściu do innego okna lub innej karty i pojawi się znowu po powrocie do pierwszej karty;
  • bezwarunkowy, a więc nie ma sposobu na pominięcie jego wyświetlenia;
  • blokujący, a więc przeglądarka nie może sprawdzić lokalizacji użytkownika podczas oczekiwana na odpowiedź.

Przed chwilą widziałeś kod JavaScript powodujący wyświetlenie paska informacyjnego. Jest to jedna funkcja pobierająca jako argument funkcję zwrotną (przeze mnie nazwaną show_map). Wywołanie metody getCurrentPosition() spowoduje jej natychmiastowe wykonanie, ale to nie znaczy, że od razu będziemy mieli dostęp do danych lokalizacyjnych. Pierwsze miejsce, w którym mamy gwarancje otrzymania informacji lokalizacyjnych jest funkcja zwrotna. Kod tej funkcji jest następujący:

function show_map(position) {
  var latitude = position.coords.latitude;
  var longitude = position.coords.longitude;
  // Pokażmy mapę albo zróbmy coś innego równie fajnego!
}

Funkcja zwrotna zostanie wywołana z jednym parametrem w postaci obiektu z dwiema własnościami: coords i timestamp. Własność timestamp określa datę i godzinę, kiedy obliczono położenie. (Jako że wszystko odbywa sie asynchronicznie, nie można z góry przewidzieć kiedy będzie miało miejsce. Może upłynąć trochę czasu zanim użytkownik przeczyta informację i zgodzi się na udostępnienie informacji o swojej lokalizacji. Urządzenia GPS mogą potrzebować trochę czasu na połączenie się z satelitą GPS. Itd.) Obiekt coords ma własności latitude i longitude, które oznaczają szerokość i długość geograficzną, a więc fizyczne położenie użytkownika na ziemi.

Obiekt Position
WłasnośćTypUwagi
coords.latitudedoublestopnie dziesiętne
coords.longitudedoublestopnie dziesiętne
coords.altitudedouble lub nullmetry nad elipsoidą odniesienia
coords.accuracydoublemetry
coords.altitudeAccuracydouble lub nullmetry
coords.headingdouble lub nullstopnie zgodnie z ruchem wskazówek zegara od prawdziwej północy
coords.speeddouble lub nullmetry/sekundy
timestampDOMTimeStampjak obiekt Date()

Gwarantowane są tylko trzy z tych własności: coords.latitude, coords.longitude oraz coords.accuracy. Reszta może mieć wartość null, chociaż zależy to od możliwości urządzenia i serwera pozycjonowania. Własności heading i speed są w razie możliwości obliczane na podstawie poprzedniej lokalizacji użytkownika.

Obsługa błędów

Geolokalizacja to skomplikowane zagadnienie. Wiele rzeczy może się nie udać. Wspomniałem już o konieczności uzyskania zgody użytkownika. Jeśli nie uda się jej uzyskać, nasza aplikacja jest uziemiona. W tym przypadku użytkownik rządzi. Jak sobie z tym radzić? Należy wykorzystać drugi argument funkcji getCurrentPosition(): jest to funkcja zwrotna do obsługi błędów.

navigator.geolocation.getCurrentPosition(
  show_map, handle_error)

Jeśli coś się nie uda, zostanie wywołana funkcja obsługi błędów z obiektem PositionError.

Obiekt PositionError
WłasnośćTypUwagi
codeshortwartość wyliczeniowa
messageDOMStringnie przeznaczone dla użytkowników końcowych

Wartością własności code może być:

  • PERMISSION_DENIED (1) jeśli użytkownik kliknął przycisk „Nigdy nie udostępniaj położenia” albo w jakiś inny sposób odmówił udzielenia informacji o swoim miejscu pobytu.
  • POSITION_UNAVAILABLE (2) jeśli sieć nie działa albo nie można nawiązać połączenia z satelitą pozycjonowania.
  • TIMEOUT (3) jeśli sieć działa, ale obliczenie położenia użytkownika zajmuje zbyt dużo czasu. A ile to jest „zbyt dużo czasu”? W następnej części pokażę ci jak zdefiniować tę wartość.

Przegrywaj z honorem

function handle_error(err) {
  if (err.code == 1) {
    // Użytkownik powiedział nie!
  }
}

Pytanie do profesora Kodeckiego

P: Czy API geolokalizacji działa w Międzynarodowej Stacji Kosmicznej, na księżycu i na innych planetach?
O: W specyfikacji geolokalizacji napisano, że: „W atrybutach tego interfejsu wykorzystywany jest geograficzny układ współrzędnych odniesienia WGS 84. Żaden inny układ odniesienia nie jest obsługiwany”. Międzynarodowa stacja kosmiczna krąży na orbicie Ziemi, a więc astronauci w stacji mogą określać swoje położenie posługując się szerokością i długością geograficzną oraz wysokością nad poziomem morza. Układ WGS 84 dotyczy jednak tylko Ziemi, a więc nie można go używać do opisu miejsc na księżycu ani planetach poza Ziemią.

Chcę mieć wybór!

Niektóre powszechnie używane urządzenia mobilne — takie jak iPhone i telefony z Androidem — obsługują dwie metody sprawdzania pozycji. Pierwsza polega na wyznaczeniu położenia metodą triangulacji na podstawie względnej odległości od różnych wież telefonii komórkowej należących do operatora, z którego usług korzysta użytkownik. Metoda ta jest szybka i nie wymaga stosowania żadnego specjalnego sprzętu GPS, ale jest też mało dokładna. W zależności od liczby wież telefonii komórkowej w okolicy ta niedokładność może być równa rozmiarowi bloku w mieście jak również wynosić kilometr w każdym kierunku.

Druga metoda polega na wykorzystaniu specjalnego sprzętu GPS w urządzeniu do połączenia się z satelitami pozycjonowania GPS krążącymi po ziemskiej atmosferze. Za pomocą GPS-u można określić czyjeś położenie z dokładnością do paru metrów. Wadą tego rozwiązania jest to, że układ GPS pochłania dużo energii, przez co telefony i inne urządzenia wyłączają go dopóki nie jest potrzebny. To znaczy, że od chwili uruchomienia układu do nawiązania połączenia z satelitami GPS będzie musiało upłynąć trochę czasu. Jeśli kiedykolwiek używałeś Map Google na iPhonie lub w innym smartfonie, to zapewne wiesz jak wyglądają oba opisane procesy. Najpierw widoczny jest duży okrąg pokazujący w przybliżeniu twoją pozycję (po znalezieniu najbliższej wieży komórkowej), a potem pokazuje się mniejsze kółko (triangulacja z innymi wieżami), aż w końcu pojawia się kropka oznaczająca dokładną pozycję (uzyskaną dzięki satelitom GPS).

Piszę o tym, bo może w niektórych przypadkach nie będziesz potrzebować wysokiej dokładności. Jeśli chcesz znaleźć ofertę pobliskich kin, to przybliżone informacje o twojej pozycji będą wystarczające. Nie ma tak dużo kin, nawet w gęsto zabudowanych miastach, a poza tym i tak pewnie sprawdzisz ofertę kilku z nich. Jeśli jednak chcesz udzielić komuś dokładnych wskazówek, jak gdzieś dotrzeć, to musisz dokładnie wiedzieć, gdzie ten ktoś się znajduje, aby móc mu powiedzieć „za 20 metrów skręć w lewo” itd.

Funkcja getCurrentPosition() opcjonalnie przyjmuje jeszcze trzeci argument, obiekt PositionOptions. W obiekcie tym można ustawić trzy własności i wszystkie są opcjonalne. Można ustawić dowolną jedną, wszystkie albo nie ustawiać żadnej.

Obiekt PositionOptions
WłasnośćTypWartość domyślnaUwagi
enableHighAccuracyLogicznafalsetrue może być wolniejsza
timeoutlong(brak domyślnej)w milisekundach
maximumAgelong0w milisekundach

Własność enableHighAccuracy służy do włączania modułu precyzyjnego określania położenia: jeśli ma wartość true i urządzenie ją obsługuje, a użytkownik zgodzi się na podanie swojego dokładnego położenia, to urządzenie spróbuje podać te informacje. W iPhone’ach i telefonach z Androidem osobno wyraża się zgodę na określenie przybliżonej i dokładnej pozycji, a więc istnieje możliwość, że wywołanie getCurrentPosition() z własnością enableHighAccuracy:true zakończy się niepowodzeniem, a z własnością enableHighAccuracy:false — powodzeniem.

Własność timeout określa liczbę milisekund, jaką aplikacja może czekać na otrzymanie informacji o położeniu. Licznik ten zaczyna odmierzać czas od momentu gdy użytkownik wyrazi zgodę na próbę obliczenia jego pozycji. Ograniczenie nie dotyczy użytkownika, tylko sieci.

Własność maximumAge umożliwia urządzeniu natychmiastowe wysłanie odpowiedzi z bufora. Wyobraźmy sobie na przykład, że wywołujemy funkcję getCurrentPosition() pierwszy raz, użytkownik zezwala na podanie jego pozycji i następuje wywołanie funkcji zwrotnej powodzenia z pozycją obliczoną dokładnie o godzinie 10:00 rano. Dokładnie minutę później, czyli o 10:01, ponownie wywołujemy funkcję getCurrentPosition() z własnością maximumAge ustawioną na 75000.

navigator.geolocation.getCurrentPosition(
  success_callback, error_callback, {maximumAge: 75000});

Informujemy system, że niekoniecznie interesuje nas bieżące położenie użytkownika. Wystarczy nam wiedzieć, gdzie się znajdował 75 sekund temu (75000 milisekund). Urządzenie wie, gdzie użytkownik znajdował się 60 sekund temu (60000 milisekund), ponieważ obliczyło jego pozycję przy pierwszym wywołaniu funkcji getCurrentPosition(). W związku z tym nie wykonuje tych samych obliczeń jeszcze raz, tylko zwraca te same informacje, co za poprzednim razem: taką samą szerokość i długość geograficzną, dokładność oraz ten sam znacznik czasu (10:00 rano).

Zanim poprosisz o udostępnienie danych o pozycji użytkownika, powinieneś przemyśleć jak wysoka dokładność jest ci potrzebna i odpowiednio ustawić własność enableHighAccuracy. Jeśli informacji tych będziesz potrzebować więcej niż raz, musisz zastanowić się jak długo dane zdobyte za pierwszym razem będą nadawały się do użytku i odpowiednio ustawić własność maximumAge. Jeśli musisz sprawdzać pozycję na bieżąco, to nie używaj metody getCurrentPosition(). Do tego służy metoda watchPosition().

Funkcja ta ma taką samą strukturę jak getCurrentPosition(). Pobiera jako argumenty dwie funkcje zwrotne. Jedna jest wymagana i dotyczy powodzenia operacji, a druga jest niewymagana i dotyczy obsługi błędów. Ponadto jako argument można przekazać jeszcze obiekt PositionOptions zawierający takie same własności, jak opisane wcześniej. Różnica polega na tym, że w tym przypadku funkcja zwrotna jest wywoływana przy każdej zmianie pozycji użytkownika. Nie ma potrzeby ciągle wysyłać zapytań. Urządzenie automatycznie określi najlepszy odstęp czasowy zapytań i wywoła funkcję zwrotną przy każdej zmianie pozycji użytkownika. Uzyskanych w ten sposób informacji można używać do przesuwania znacznika na mapie, dostarczania wskazówek kierunkowych itd. Możesz z nimi zrobić co chcesz.

Funkcja watchPosition() zwraca liczbę. Liczbę tę powinno się gdzieś zapisać. Gdy będziesz chciał przestać śledzić użytkownika, możesz wywołać metodę clearWatch() i przekazać jej tę liczbę. To spowoduje, że urządzenie przestanie wywoływać naszą funkcję zwrotną. Jeśli używałeś kiedyś funkcji JavaScript setInterval() i clearInterval(), to wiesz jak to działa.

Kwestia Internet Explorera

Internet Explorer do wersji 9 (9.0RC1) nie obsługuje API geolokalizacji W3C, które opisałem w tym rozdziale. Ale nie panikuj! W przeglądarkach tych można posiłkować się specjalnym skryptem JavaScript. Nie jest to dokładnie to samo, co API geolokalizacji W3C, ale ma takie same zastosowanie.

Skoro już jesteśmy przy temacie starych platform, należy podkreślić, że wiele starych telefonów komórkowych miało własne API geolokalizacyjne, np. BlackBerry, Nokia, Palm oraz OMTP BONDI. Oczywiście każde z nich jest inne i różni się od API W3C. Uff!

Jedyny ratunek w skrypcie geoPosition.js

geoPositon.js to otwarta i dostępny na licencji MIT biblioteka JavaScript zacierająca różnice między API lokalizacyjnym W3C, a usługami geolokalizacyjnymi IP i API platform mobilnych. Aby z niej skorzystać, należy na dole strony umieścić element <script>. (W istocie można go umieścić w dowolnym miejscu, ale skrypty znajdujące się w części <head> spowalniają ładowanie strony. Dlatego nie rób tego!)

Nie pozwól wejść sobie na głowę

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Dive Into HTML5</title>
</head>
<body>
  ...
  <script src="geoPosition.js"></script>
</body>
</html>

Teraz możesz używać dowolnego API geolokalizacji, jakie jest dostępne.

if (geoPosition.init()) {
  geoPosition.getCurrentPosition(geoSuccess, geoError);
}

Przeanalizujemy ten kod instrukcja po instrukcji. Najpierw wywołujemy funkcję init(). Funkcja ta zwraca true, gdy dostępne jest jakieś obsługiwane API geolokalizacji.

if (geoPosition.init()) {

Wywołanie funkcji init() nie powoduje znalezienia pozycji. Pozwala jedynie przekona się, czy zdobycie tych informacji jest możliwe. Aby sprawdzić lokalizację, należy wywołać funkcję getCurrentPosition().

  geoPosition.getCurrentPosition(geoSuccess, geo_error);

Funkcja getCurrentPosition() uruchamia w przeglądarce algorytm wyświetlający pytanie do użytkownika o pozwolenie na sprawdzenie jego pozycji. Jeśli przeglądarka standardowo obsługuje API geolokalizacji, wyświetli to zapytanie w typowy dla siebie sposób.

Funkcja getCurrentPosition() pobiera jako argumenty dwie funkcje zwrotne. Jeżeli funkcji getCurrentPosition() uda się określić położenie użytkownika — tzn. użytkownik wyrazi zgodę i API geolokalizacji poprawnie zadziała — wywoła pierwszą funkcję zwrotną. W tym przypadku funkcja zwrotna powodzenia ma nazwę geo_success.

  geoPosition.getCurrentPosition(geoSuccess, geoError);

Funkcja ta pobiera jeden argument zawierający informację o położeniu.

Funkcja zwrotna powodzenia

function geoSuccess(p) {
  alert("Jesteś w miejscu o szerokości geograficznej " + p.coords.latitude +
        " i długości " + p.coords.longitude);
}

Jeżeli funkcji getCurrentPosition() nie uda się określić położenia użytkownika — bo użytkownik nie wyrazi zgody albo nie zadziała API geolokalizacji — wywoła drugą funkcję zwrotną. W tym przypadku funkcja zwrotna niepowodzenia ma nazwę geoError.

  geoPosition.getCurrentPosition(geoSuccess, geoError);

Funkcja zwrotna błędu przyjmuje dwa argumenty.

Funkcja zwrotna niepowodzenia

function geoError() {
  alert("Nie udało się ciebie znaleźć!");
}

Aktualnie biblioteka geoPosition.js nie obsługuje funkcji watchPosition(). Jeśli chcesz kogoś śledzić na bieżąco, musisz samodzielnie wielokrotnie wywoływać funkcję getCurrentPosition().

Kompletny realny przykład

W tej części rozdziału przedstawiam przykład użycia skryptu geoPosition.js do sprawdzenia pozycji użytkownika i wyświetlenia mapy jego najbliższego otoczenia:

Jak to działa? Zobaczmy. Podczas wczytywania strony wywoływana jest metoda geoPosition.init() w celu sprawdzenia, czy dostępny jest którykolwiek interfejs geolokalizacji obsługiwany przez geoPosition.js. Jeśli tak, to wyświetlany jest odnośnik pozwalający użytkownikowi sprawdzić swoje położenie. Kliknięcie tego łącza powoduje wywołanie funkcji lookup_location():

function lookup_location() {
  geoPosition.getCurrentPosition(show_map, show_map_error);
}

Gdy wyrazisz zgodę na śledzenie swojego miejsca pobytu i usługom uda się określić twoje położenie, skrypt geoPosition.js wywoła pierwszą funkcję zwrotną (show_map()) z argumentem loc. Obiekt loc ma własność coords zawierającą szerokość geograficzną, długość geograficzną oraz informację o dokładności pomiaru. (W tym przykładzie informacja o dokładności nie jest wykorzystywana.) Dalej w funkcji show_map() znajduje się kod wyświetlający mapę przy użyciu API Map Google.

function show_map(loc) {
  $("#geo-wrapper").css({'width':'320px','height':'350px'});
  var map = new GMap2(document.getElementById("geo-wrapper"));
  var center = new GLatLng(loc.coords.latitude, loc.coords.longitude);
  map.setCenter(center, 14);
  map.addControl(new GSmallMapControl());
  map.addControl(new GMapTypeControl());
  map.addOverlay(new GMarker(center, {draggable: false, title: "Jesteś tutaj (tak mniej więcej)"}));
}

Jeśli skrypt geoPosition.js nie może określić położenia użytkownika, wywołuje drugą funkcję zwrotną, o nazwie show_map_error().

function show_map_error() {
  $("#live-geolocation").html('Nie można określić twojego położenia.');
}

Lektura uzupełniająca

Autor: Mark Pilgrim

Źródło: http://diveintohtml5.info/

Tłumaczenie: Łukasz Piwko

Treść tej strony jest dostępna na zasadach licencji CC BY 3.0