Budując aplikację, w której jedną z funkcjonalności jest wykorzystanie mapy, z dużym prawdopodobieństwem przyjdzie nam zmierzyć się z konwersją współrzędnych geograficznych na adres lub odwrotnie. W tym wpisie chciałem poruszyć problem jaki napotkałem tworząc tego rodzaju rozwiązanie.

Całość oparta jest o mapy Google. Nie chcę tutaj rozważać, czy to dobre rozwiązanie (ze względu np. na potencjalne koszty). To, czy korzystać z Google Maps, czy alternatywnych rozwiązań np. OpenStreetMap pozostawiam do samodzielnego rozważenia.

Ja wybrałem Google i udostępniane dla map API. W ramach dostępnych funkcjonalności interesowały mnie te z zakresu Geocoding i Reverse Geocoding. Pozwalają one właśnie na „tłumaczenie” adresu na współrzędne geograficzne i odwrotnie. Mnie interesowało bardziej odczytywanie adresu na podstawie położenia. Oczywiście jest to zawsze obarczone pewnym błędem, wynikającym nie tylko z występujących czasem zaburzeń pomiaru położenia, ale również z faktu, że adres nie zawsze jest odzwierciedleniem położenia (np. w sytuacji gdy odległa brama wjazdowa znajduje się na sąsiedniej ulicy).

Przekazując do API poprzez google.maps.Geocoder współrzędne geograficzne otrzymujemy w odpowiedzi obiekt GeocoderResult zawierający odpowiednie właściwości określające odpowiadający im adres.

results[]: {
  types[]: string,
  formatted_address: string,
 	address_components[]: {
   		short_name: string,
   		long_name: string,
   		postcode_localities[]: string,
   		types[]: string
  },
 	partial_match: boolean,
 	place_id: string,
  postcode_localities[]: string,
  geometry: {
    location: LatLng,
   		location_type: GeocoderLocationType
   		viewport: LatLngBounds,
   		bounds: LatLngBounds
 	}
}

Nie chciałbym tu omawiać każdej tych właściwości, a jedynie dwie z nich, ściśle związane z adresem. Pierwsza z nich to formatted_address, będąca string’iem. Zawiera adres w formacie łatwym do odczytania dla człowieka. Najczęściej odpowiada adresowi pocztowemu danego miejsca. Jego postać jest różna zależnie od kraju (niektóre kraje nakładają pewne restrykcje związane z publikowaniem adresów). Składa się z kilku elementów zdefiniowanych we właściwości address_components.

Właściwości formatted_address, address_components

Wykorzystując formatted_address mamy więc możliwość pobrania sformatowanego adresu dla danego miejsca. W takiej formie (string) możemy go zatem wyświetlić użytkownikowi lub zapisać. Obróbka raczej nie wchodzi w rachubę. Druga właściwość to właśnie address_components i jeden z jej kluczy types będący tablicą. Zawiera ona dość sporo kluczy definiujących elementy adresu jako odpowiednie składowe. Wymienię je jedynie, dokładny opis można znaleźć w dokumentacji Google Maps API.

administrative_area_level_1
administrative_area_level_2
administrative_area_level_3
administrative_area_level_4
administrative_area_level_5
colloquial_area
country
establishment
finance
floor
food
general_contractor
geocode
health
intersection
locality
natural_feature
neighborhood
place_of_worship
political
point_of_interest
post_box
postal_code
postal_code_prefix
postal_code_suffix
postal_town
premise
room
route
street_address
street_number
sublocality
sublocality_level_4
sublocality_level_5
sublocality_level_3
sublocality_level_2
sublocality_level_1
subpremise

Posiadając zawartość tablicy address_componets jesteśmy więc w stanie samodzielnie sformatować adres, który może okazać się bardziej rozbudowany niż zwracany w ramach formatted_address. Należy mieć na uwadze, że nie wszystkie klucze omawianej tablicy mogą być wypełnione. Zależy to od samego adresu oraz sposobu w jaki w danym kraju adresy są opisywane.

Formatowanie adresów

I tutaj właśnie dochodzimy do miejsca w którym napotkałem pewien problem z tym związany. Z zapisaniem do bazy danych składowych adresu z tablicy nie ma problemu. Pojawia się on w sytuacji, gdy chcemy na podstawie tej tablicy adres pokazać. O ile operujemy w ramach jednego, ewentualnie kilku krajów, jesteśmy w stanie odpowiednio zakodować schematy pokazywania tych danych. Operując jednak globalnie i dopuszczając adresy z całego świata, jest to właściwie nie tyle niemożliwe, co nieprawdopodobnie pracochłonne.

Rozsądniejsze wydawałoby się użycie formatted_address – posiada jednak swoje ograniczenia – string. Zyskujemy jednak odpowiednio sformatowany adres.

Problemy zaczynają się piętrzyć, gdy zaczynamy właśnie rozważać zapisywanie adresów i ich wyświetlanie w trybie offline. We właściwości formatted_address nie zawsze muszą znaleźć się elementy pozwalające na wyszukiwanie adresów w jakimś regionie. Nie rozważamy tu wyszukiwania w ramach jakiegoś zasięgu, bo do tego możemy wykorzystać współrzędne. Dodatkowo bazę zapisanych adresów będziemy mieli w języku w jakim pobraliśmy te dane z API. Dotyczy to obu omawianych właściwości. Budując aplikację mulitilanguage, gdzie użytkownik ustawia swój język, należałoby to brać pod uwagę. Właściwość formatted_address raczej nie daje tu pola manewru – address_components już tak – chociażby oprogramowując nazwę kraju w adresie w języku użytkownika. Nazwy ulic już raczej nie są tłumaczone.

Oczywiście możemy się zdecydować na zapisywanie do bazy adresów np. w języku angielskim. Czy jednak jest to najbardziej optymalne rozwiązanie dla aplikacji wielojęzycznych?

Jak działa nasza aplikacja?

Reasumując – już na etapie opracowywania założeń funkcjonalnych warto wziąć pod uwagę powyższe punkty i odpowiedzieć na przynajmniej kilka pytań:

  1. Czy nasza aplikacja będzie od początku/z czasem wielojęzyczna?
  2. Czy obsługujemy cały świat?
  3. Czy baza dostępnych punktów będzie dostępna dla użytkowników w trybie offline?

Znając odpowiedzi na te pytania, można zacząć się zastanawiać nad sposobem w jaki rozwiążemy wyżej opisane problemy, które możemy napotkać budując aplikację posiadającą funkcjonalności związane z geolokalizacją. W kolejnym wpisie postaram się opisać kilka możliwych rozwiązań. Przy każdym z nich pojawi się najprawdopodobniej jakieś „ale”, niemniej jednak czasem nie ma wyjścia i trzeba zdecydować się na jakiś kompromis.