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ń:
- Czy nasza aplikacja będzie od początku/z czasem wielojęzyczna?
- Czy obsługujemy cały świat?
- 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.