From bb4ccd8dd822ed7cd2027c774d81298b74e9b52f Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 12 Feb 2021 15:59:00 -0600 Subject: [PATCH] Add NominatimGeocoder --- .../java/org/tasks/injection/FlavorModule.kt | 4 +- .../org/tasks/location/NominatimGeocoder.kt | 64 +++++++++++++++++++ .../tasks/location/NominatimGeocoderTest.kt | 33 ++++++++++ app/src/test/resources/nominatim/house.json | 41 ++++++++++++ app/src/test/resources/nominatim/pitch.json | 45 +++++++++++++ 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/tasks/location/NominatimGeocoder.kt create mode 100644 app/src/test/java/org/tasks/location/NominatimGeocoderTest.kt create mode 100644 app/src/test/resources/nominatim/house.json create mode 100644 app/src/test/resources/nominatim/pitch.json diff --git a/app/src/generic/java/org/tasks/injection/FlavorModule.kt b/app/src/generic/java/org/tasks/injection/FlavorModule.kt index a79651246..9553a9d04 100644 --- a/app/src/generic/java/org/tasks/injection/FlavorModule.kt +++ b/app/src/generic/java/org/tasks/injection/FlavorModule.kt @@ -7,13 +7,13 @@ import dagger.hilt.components.SingletonComponent import org.tasks.location.AndroidGeofencing import org.tasks.location.Geocoder import org.tasks.location.Geofencing -import org.tasks.location.MapboxGeocoder +import org.tasks.location.NominatimGeocoder @Module @InstallIn(SingletonComponent::class) class FlavorModule { @Provides - fun getGeocoder(geocoder: MapboxGeocoder): Geocoder = geocoder + fun getGeocoder(geocoder: NominatimGeocoder): Geocoder = geocoder @Provides fun getGeofencing(geofencing: AndroidGeofencing): Geofencing = geofencing diff --git a/app/src/main/java/org/tasks/location/NominatimGeocoder.kt b/app/src/main/java/org/tasks/location/NominatimGeocoder.kt new file mode 100644 index 000000000..a589c4b9a --- /dev/null +++ b/app/src/main/java/org/tasks/location/NominatimGeocoder.kt @@ -0,0 +1,64 @@ +package org.tasks.location + +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.tasks.DebugNetworkInterceptor +import org.tasks.data.Place +import org.tasks.data.Place.Companion.newPlace +import org.tasks.preferences.Preferences +import java.io.IOException +import javax.inject.Inject + +class NominatimGeocoder @Inject constructor( + private val preferences: Preferences, + private val interceptor: DebugNetworkInterceptor, +) : Geocoder { + override suspend fun reverseGeocode(mapPosition: MapPosition): Place? = + withContext(Dispatchers.IO) { + val builder = OkHttpClient().newBuilder() + if (preferences.isFlipperEnabled) { + interceptor.apply(builder) + } + val client = builder.build() + val url = "https://nominatim.openstreetmap.org/reverse?format=geocodejson&lat=${mapPosition.latitude}&lon=${mapPosition.longitude}" + val response = client.newCall(Request.Builder().get().url(url).build()).execute() + if (response.isSuccessful) { + response.body?.string()?.let { jsonToPlace(it) } + } else { + throw IOException("${response.code} ${response.message}") + } + } + + companion object { + internal fun jsonToPlace(json: String): Place? = + JsonParser + .parseString(json).asJsonObject.getAsJsonArray("features") + .takeIf { it.size() > 0 }?.get(0)?.asJsonObject + ?.let { feature -> + val geocoding = feature + .get("properties").asJsonObject + .get("geocoding").asJsonObject + val geometry = feature.get("geometry").asJsonObject + newPlace().apply { + val type = geocoding.get("type").asString + name = if (type.equals("house")) { + "${geocoding.get("housenumber").asString} ${geocoding.get("street").asString}" + } else { + geocoding.get("name").asString + } + address = geocoding.get("label").asString + geometry.get("coordinates").asCoordinates.let { + longitude = it.first + latitude = it.second + } + } + } + + private val JsonElement.asCoordinates: Pair + get() = asJsonArray.let { Pair(it[0].asDouble, it[1].asDouble) } + } +} \ No newline at end of file diff --git a/app/src/test/java/org/tasks/location/NominatimGeocoderTest.kt b/app/src/test/java/org/tasks/location/NominatimGeocoderTest.kt new file mode 100644 index 000000000..c45483c30 --- /dev/null +++ b/app/src/test/java/org/tasks/location/NominatimGeocoderTest.kt @@ -0,0 +1,33 @@ +package org.tasks.location + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.tasks.TestUtilities.readFile + +class NominatimGeocoderTest { + @Test + fun pitchGeocode() { + val place = NominatimGeocoder.jsonToPlace(readFile("nominatim/pitch.json"))!! + + assertEquals("Guaranteed Rate Field", place.name) + assertEquals(-87.63362064328714, place.longitude, 0.0) + assertEquals(41.82982845, place.latitude, 0.0) + assertEquals( + "Guaranteed Rate Field, West 36th Street, Armour Square, Chicago, Cook County, Illinois, 60616, United States", + place.address + ) + } + + @Test + fun houseGeocode() { + val place = NominatimGeocoder.jsonToPlace(readFile("nominatim/house.json"))!! + + assertEquals("1 Løvenbergvegen", place.name) + assertEquals(11.1658572, place.longitude, 0.0) + assertEquals(60.2301296, place.latitude, 0.0) + assertEquals( + "1, Løvenbergvegen, Mogreina, Ullensaker, Viken, 2054, Norge", + place.address + ) + } +} \ No newline at end of file diff --git a/app/src/test/resources/nominatim/house.json b/app/src/test/resources/nominatim/house.json new file mode 100644 index 000000000..2a990c283 --- /dev/null +++ b/app/src/test/resources/nominatim/house.json @@ -0,0 +1,41 @@ +{ + "type": "FeatureCollection", + "geocoding": { + "version": "0.1.0", + "attribution": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright", + "licence": "ODbL", + "query": "60.2301296,11.1658572" + }, + "features": [ + { + "type": "Feature", + "properties": { + "geocoding": { + "place_id": 39928913, + "osm_type": "node", + "osm_id": 3110596255, + "type": "house", + "accuracy": 0, + "label": "1, Løvenbergvegen, Mogreina, Ullensaker, Viken, 2054, Norge", + "country": "Norge", + "postcode": "2054", + "county": "Viken", + "city": "Mogreina", + "street": "Løvenbergvegen", + "housenumber": "1", + "admin": { + "level4": "Viken", + "level7": "Ullensaker" + } + } + }, + "geometry": { + "type": "Point", + "coordinates": [ + 11.1658572, + 60.2301296 + ] + } + } + ] +} \ No newline at end of file diff --git a/app/src/test/resources/nominatim/pitch.json b/app/src/test/resources/nominatim/pitch.json new file mode 100644 index 000000000..0b5fbedc9 --- /dev/null +++ b/app/src/test/resources/nominatim/pitch.json @@ -0,0 +1,45 @@ +{ + "type": "FeatureCollection", + "geocoding": { + "version": "0.1.0", + "attribution": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright", + "licence": "ODbL", + "query": "41.8299365,-87.633806" + }, + "features": [ + { + "type": "Feature", + "properties": { + "geocoding": { + "place_id": 258870464, + "osm_type": "relation", + "osm_id": 3834547, + "type": "pitch", + "accuracy": 0, + "label": "Guaranteed Rate Field, West 36th Street, Armour Square, Chicago, Cook County, Illinois, 60616, United States", + "name": "Guaranteed Rate Field", + "country": "United States", + "postcode": "60616", + "state": "Illinois", + "county": "Cook County", + "city": "Chicago", + "district": "Armour Square", + "street": "West 36th Street", + "admin": { + "level4": "Illinois", + "level6": "Cook County", + "level8": "Chicago", + "level10": "Armour Square" + } + } + }, + "geometry": { + "type": "Point", + "coordinates": [ + -87.63362064328714, + 41.82982845 + ] + } + } + ] +} \ No newline at end of file