diff --git a/docs/user/shared/Geolocation.dox b/docs/user/shared/Geolocation.dox new file mode 100644 index 0000000000..9876a5f613 --- /dev/null +++ b/docs/user/shared/Geolocation.dox @@ -0,0 +1,89 @@ +/* + * Copyright 2019 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Adrien Destugues, pulkomandy@pulkomandy.tk + * + * Corresponds to: + * headers/private/shared/Geolocation.h hrev53489 + * src/kits/shared/Geolocation.cpp hrev53489 + */ + + +/*! + \file Geolocation.h + \ingroup shared + \brief Geolocation and geocoding services. +*/ + + +/*! + \class BGeolocation + \ingroup shared + \brief Geolocation and geocoding services. + + Geolocation allows to know where the computer is currently located. + This is currently done by sending a list of Wifi networks within reach to + a 3rd party webservice (Mozilla location services), which uses a database + of known networks around the world to return an estimate position. + + Geocoding is about identifying places in a way more convenient than + latitude and longitude. This can include postal codes, country names, + street or building names, etc. This class allows to perform both geocoding + (finding a latitude and a longitude from a name) and reverse geocoding + (finding a suitable name for a given position). This service is provided + to haiku by the 3rd-party geonames.org + + Note that most of the methods in this class perform HTTPS requests to + 3rd-parties. When using it in your application, it is good practise to + first inform the user about what will be shared with these services, and + why that's needed. Of course, this also means using these services requires + internet access, and you may want to limit the number of calls, both to + save bandwidth and time, and to not overuse the provided API keys. If you + are going to do more than a few request per application run, you should + provide your own API keys instead. + + \since Haiku R1 +*/ + + +/*! + \fn BGeolocation::BGeolocation() + \brief Initialize a BGeolocation using the default services and API keys. + + Official builds of Haiku include a shared API key for both Mozilla Location + Services and geonames.org. You can use this constructor to use these keys + in your application. +*/ + + +/*! + \fn BGeolocation::BGeolocation(BUrl& geolocationService, + BUrl& geocodingService) + \brief Initialize a BGeolocation using custom services or API keys. + + You can use this constructor to provide your own URL to a webservice to + use for the requests. The services have to implement the MLS and geonames + API, respectively. + + If you want to override only one of the two URLs, the other can be passed + as an empty BUrl object. +*/ + + +/*! + \fn status_t BGeolocation::LocateSelf(float& latitude, float& longitude) + \brief Locate the computer by detecting nearby WiFi networks. + + This method makes a request to Mozilla Location Services. +*/ + + +/*! + \fn status_t BGeolocation::Country(float latitude, float longitude, + BCountry& country) + \brief Find which country a given set of coordinates is in. + + This method makes a request to geonames.org. +*/ diff --git a/headers/private/shared/Geolocation.h b/headers/private/shared/Geolocation.h index cad1e176bd..825f696228 100644 --- a/headers/private/shared/Geolocation.h +++ b/headers/private/shared/Geolocation.h @@ -1,11 +1,12 @@ /* - * Copyright 2014, Haiku, Inc. All Rights Reserved. + * Copyright 2014-2019, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. */ #ifndef _GEOLOCATION_H #define _GEOLOCATION_H +#include #include @@ -15,17 +16,24 @@ namespace BPrivate { class BGeolocation { public: BGeolocation(); - BGeolocation(const BUrl& service); + BGeolocation(const BUrl& geolocationService, + const BUrl& geocodingService); status_t LocateSelf(float& latitude, float& longitude); status_t Locate(const BString placeName, float& latitude, float& longitude); + status_t Name(const float latitude, const float longitude, BString& name); + status_t Country(const float latitude, const float longitude, + BCountry& country); private: - BUrl fService; - static const char* kDefaultService; + BUrl fGeolocationService; + BUrl fGeocodingService; + + static const char* kDefaultGeolocationService; + static const char* kDefaultGeocodingService; }; diff --git a/src/kits/shared/Geolocation.cpp b/src/kits/shared/Geolocation.cpp index 3086abbb41..cade498884 100644 --- a/src/kits/shared/Geolocation.cpp +++ b/src/kits/shared/Geolocation.cpp @@ -1,5 +1,6 @@ /* * Copyright 2014, Haiku, Inc. All Rights Reserved. + * Copyright 2019, Adrien Destugues, pulkomandy@pulkomandy.tk * Distributed under the terms of the MIT License. */ @@ -19,15 +20,57 @@ namespace BPrivate { +class GeolocationListener: public BUrlProtocolListener +{ + public: + GeolocationListener() + { + pthread_cond_init(&fCompletion, NULL); + pthread_mutex_init(&fLock, NULL); + } + + virtual ~GeolocationListener() { + pthread_cond_destroy(&fCompletion); + pthread_mutex_destroy(&fLock); + } + + void ConnectionOpened(BUrlRequest* caller) + { + pthread_mutex_lock(&fLock); + } + + void DataReceived(BUrlRequest*, const char* data, off_t position, + ssize_t size) { + fResult.WriteAt(position, data, size); + } + + void RequestCompleted(BUrlRequest* caller, bool success) { + pthread_cond_signal(&fCompletion); + pthread_mutex_unlock(&fLock); + } + + BMallocIO fResult; + pthread_cond_t fCompletion; + pthread_mutex_t fLock; +}; + + BGeolocation::BGeolocation() - : fService(kDefaultService) + : fGeolocationService(kDefaultGeolocationService), + fGeocodingService(kDefaultGeocodingService) { } -BGeolocation::BGeolocation(const BUrl& service) - : fService(service) +BGeolocation::BGeolocation(const BUrl& geolocationService, + const BUrl& geocodingService) + : fGeolocationService(geolocationService), + fGeocodingService(geocodingService) { + if (!fGeolocationService.IsValid()) + fGeolocationService.SetUrlString(kDefaultGeolocationService); + if (!fGeocodingService.IsValid()) + fGeocodingService.SetUrlString(kDefaultGeocodingService); } @@ -72,22 +115,11 @@ BGeolocation::LocateSelf(float& latitude, float& longitude) if (count < 2) return B_DEVICE_NOT_FOUND; - class GeolocationListener: public BUrlProtocolListener - { - public: - virtual ~GeolocationListener() {}; - - void DataReceived(BUrlRequest*, const char* data, off_t position, - ssize_t size) { - result.WriteAt(position, data, size); - } - BMallocIO result; - }; - GeolocationListener listener; // Send Request (POST JSON message) - BUrlRequest* request = BUrlProtocolRoster::MakeRequest(fService, &listener); + BUrlRequest* request = BUrlProtocolRoster::MakeRequest(fGeolocationService, + &listener); if (request == NULL) return B_BAD_DATA; @@ -107,8 +139,10 @@ BGeolocation::LocateSelf(float& latitude, float& longitude) return result; } + pthread_mutex_lock(&listener.fLock); while (http->IsRunning()) - snooze(10000); + pthread_cond_wait(&listener.fCompletion, &listener.fLock); + pthread_mutex_unlock(&listener.fLock); // Parse reply const BHttpResult& reply = (const BHttpResult&)http->Result(); @@ -118,7 +152,7 @@ BGeolocation::LocateSelf(float& latitude, float& longitude) } BMessage data; - result = BJson::Parse((char*)listener.result.Buffer(), data); + result = BJson::Parse((char*)listener.fResult.Buffer(), data); delete http; if (result != B_OK) { return result; @@ -144,6 +178,57 @@ BGeolocation::LocateSelf(float& latitude, float& longitude) } +status_t +BGeolocation::Country(const float latitude, const float longitude, + BCountry& country) +{ + // Prepare the request URL + BUrl url(fGeocodingService); + BString requestString; + requestString.SetToFormat("%s&lat=%f&lng=%f", url.Request().String(), latitude, + longitude); + url.SetPath("/countryCode"); + url.SetRequest(requestString); + + GeolocationListener listener; + BUrlRequest* request = BUrlProtocolRoster::MakeRequest(url, + &listener); + if (request == NULL) + return B_BAD_DATA; + + BHttpRequest* http = dynamic_cast(request); + if (http == NULL) { + delete request; + return B_BAD_DATA; + } + + status_t result = http->Run(); + if (result < 0) { + delete http; + return result; + } + + pthread_mutex_lock(&listener.fLock); + while (http->IsRunning()) { + pthread_cond_wait(&listener.fCompletion, &listener.fLock); + } + pthread_mutex_unlock(&listener.fLock); + + // Parse reply + const BHttpResult& reply = (const BHttpResult&)http->Result(); + if (reply.StatusCode() != 200) { + delete http; + return B_ERROR; + } + + off_t length = 0; + listener.fResult.GetSize(&length); + length -= 2; // Remove \r\n from response + BString countryCode((char*)listener.fResult.Buffer(), (int32)length); + return country.SetTo(countryCode); +} + + #ifdef HAVE_DEFAULT_GEOLOCATION_SERVICE_KEY #include "DefaultGeolocationServiceKey.h" @@ -152,9 +237,14 @@ const char* BGeolocation::kDefaultService = "https://location.services.mozilla.com/v1/geolocate?key=" DEFAULT_GEOLOCATION_SERVICE_KEY; +const char* BGeolocation::kDefaultGeocodingService + = "https://secure.geonames.org/?username=" + DEFAULT_GEOCODING_SERVICE_KEY; + #else -const char* BGeolocation::kDefaultService = ""; +const char* BGeolocation::kDefaultGeolocationService = ""; +const char* BGeolocation::kDefaultGeocodingService = ""; #endif diff --git a/src/tests/kits/shared/GeolocationTest.cpp b/src/tests/kits/shared/GeolocationTest.cpp index adcc5e7571..1f58bce955 100644 --- a/src/tests/kits/shared/GeolocationTest.cpp +++ b/src/tests/kits/shared/GeolocationTest.cpp @@ -24,7 +24,7 @@ void GeolocationTest::TestLocateSelf() { BGeolocation locator(BUrl( - "https://location.services.mozilla.com/v1/geolocate?key=test")); + "https://location.services.mozilla.com/v1/geolocate?key=test"), BUrl()); float latitude, longitude; status_t result = locator.LocateSelf(latitude, longitude); @@ -33,6 +33,18 @@ GeolocationTest::TestLocateSelf() } +void +GeolocationTest::TestCountry() +{ + BGeolocation geocoder(BUrl(""), BUrl("https://secure.geonames.org/?username=demo")); + BCountry country; + status_t result = geocoder.Country(47.03f, 10.2f, country); + CPPUNIT_ASSERT_EQUAL(B_OK, result); + CPPUNIT_ASSERT_EQUAL(B_OK, country.InitCheck()); + CPPUNIT_ASSERT_EQUAL(BString("AT"), BString(country.Code())); +} + + /* static */ void GeolocationTest::AddTests(BTestSuite& parent) { @@ -40,6 +52,8 @@ GeolocationTest::AddTests(BTestSuite& parent) suite.addTest(new CppUnit::TestCaller( "GeolocationTest::LocateSelf", &GeolocationTest::TestLocateSelf)); + suite.addTest(new CppUnit::TestCaller( + "GeolocationTest::Country", &GeolocationTest::TestCountry)); parent.addTest("GeolocationTest", &suite); }; diff --git a/src/tests/kits/shared/GeolocationTest.h b/src/tests/kits/shared/GeolocationTest.h index 271412aee7..805ff8d169 100644 --- a/src/tests/kits/shared/GeolocationTest.h +++ b/src/tests/kits/shared/GeolocationTest.h @@ -15,6 +15,7 @@ public: GeolocationTest(); void TestLocateSelf(); + void TestCountry(); static void AddTests(BTestSuite& suite); };