Use Mapbox for reverse geocoding

pull/795/head
Alex Baker 5 years ago
parent c350314cd2
commit 71c2d125c2

@ -0,0 +1,24 @@
package org.tasks.injection;
import android.content.Context;
import dagger.Module;
import dagger.Provides;
import org.tasks.location.MapFragment;
import org.tasks.location.MapboxMapFragment;
import org.tasks.location.MapboxSearchProvider;
import org.tasks.location.PlaceSearchProvider;
@Module
public class LocationModule {
@Provides
@ActivityScope
public PlaceSearchProvider getPlaceSearchProvider(@ForApplication Context context) {
return new MapboxSearchProvider(context);
}
@Provides
@ActivityScope
public MapFragment getMapFragment(@ForApplication Context context) {
return new MapboxMapFragment(context);
}
}

@ -1,33 +0,0 @@
package org.tasks.location;
import android.content.Context;
import androidx.fragment.app.FragmentManager;
import java.util.List;
import org.tasks.data.Place;
public class GoogleMapFragment implements MapFragment {
public GoogleMapFragment(Context context) {}
@Override
public void init(FragmentManager fragmentManager, MapFragmentCallback callback, boolean dark) {}
@Override
public MapPosition getMapPosition() {
return null;
}
@Override
public void movePosition(MapPosition mapPosition, boolean animate) {}
@Override
public void setMarkers(List<Place> places) {}
@Override
public void showMyLocation() {}
@Override
public int getMarkerId() {
return 0;
}
}

@ -1,34 +0,0 @@
package org.tasks.location;
import android.content.Context;
import android.os.Bundle;
import java.util.List;
import org.tasks.Callback;
import org.tasks.data.Place;
public class GooglePlacesSearchProvider implements PlaceSearchProvider {
public GooglePlacesSearchProvider(Context context) {}
@Override
public void restoreState(Bundle savedInstanceState) {}
@Override
public void saveState(Bundle outState) {}
@Override
public int getAttributionRes(boolean dark) {
return 0;
}
@Override
public void search(
String query,
MapPosition bias,
Callback<List<PlaceSearchResult>> onSuccess,
Callback<String> onError) {}
@Override
public void fetch(
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError) {}
}

@ -0,0 +1,24 @@
package org.tasks.injection;
import android.content.Context;
import dagger.Module;
import dagger.Provides;
import org.tasks.location.MapFragment;
import org.tasks.location.MapboxMapFragment;
import org.tasks.location.MapboxSearchProvider;
import org.tasks.location.PlaceSearchProvider;
@Module
public class LocationModule {
@Provides
@ActivityScope
public PlaceSearchProvider getPlaceSearchProvider(@ForApplication Context context) {
return new MapboxSearchProvider(context);
}
@Provides
@ActivityScope
public MapFragment getMapFragment(@ForApplication Context context) {
return new MapboxMapFragment(context);
}
}

@ -1,33 +0,0 @@
package org.tasks.location;
import android.content.Context;
import androidx.fragment.app.FragmentManager;
import java.util.List;
import org.tasks.data.Place;
public class GoogleMapFragment implements MapFragment {
public GoogleMapFragment(Context context) {}
@Override
public void init(FragmentManager fragmentManager, MapFragmentCallback callback, boolean dark) {}
@Override
public MapPosition getMapPosition() {
return null;
}
@Override
public void movePosition(MapPosition mapPosition, boolean animate) {}
@Override
public void setMarkers(List<Place> places) {}
@Override
public void showMyLocation() {}
@Override
public int getMarkerId() {
return 0;
}
}

@ -1,34 +0,0 @@
package org.tasks.location;
import android.content.Context;
import android.os.Bundle;
import java.util.List;
import org.tasks.Callback;
import org.tasks.data.Place;
public class GooglePlacesSearchProvider implements PlaceSearchProvider {
public GooglePlacesSearchProvider(Context context) {}
@Override
public void restoreState(Bundle savedInstanceState) {}
@Override
public void saveState(Bundle outState) {}
@Override
public int getAttributionRes(boolean dark) {
return 0;
}
@Override
public void search(
String query,
MapPosition bias,
Callback<List<PlaceSearchResult>> onSuccess,
Callback<String> onError) {}
@Override
public void fetch(
PlaceSearchResult placeSearchResult, Callback<Place> onSuccess, Callback<String> onError) {}
}

@ -0,0 +1,40 @@
package org.tasks.injection;
import android.content.Context;
import dagger.Module;
import dagger.Provides;
import org.tasks.billing.Inventory;
import org.tasks.gtasks.PlayServices;
import org.tasks.location.GoogleMapFragment;
import org.tasks.location.GooglePlacesSearchProvider;
import org.tasks.location.MapFragment;
import org.tasks.location.MapboxMapFragment;
import org.tasks.location.MapboxSearchProvider;
import org.tasks.location.PlaceSearchProvider;
import org.tasks.preferences.Preferences;
@Module
public class LocationModule {
@Provides
@ActivityScope
public PlaceSearchProvider getPlaceSearchProvider(
@ForApplication Context context,
Preferences preferences,
PlayServices playServices,
Inventory inventory) {
return preferences.useGooglePlaces()
&& playServices.isPlayServicesAvailable()
&& inventory.hasPro()
? new GooglePlacesSearchProvider(context)
: new MapboxSearchProvider(context);
}
@Provides
@ActivityScope
public MapFragment getMapFragment(@ForApplication Context context, Preferences preferences) {
return preferences.useGoogleMaps()
? new GoogleMapFragment(context)
: new MapboxMapFragment(context);
}
}

@ -1,5 +1,8 @@
package org.tasks.data;
import static com.mapbox.api.geocoding.v5.GeocodingCriteria.TYPE_ADDRESS;
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@ -8,8 +11,11 @@ import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import com.google.common.base.Strings;
import com.mapbox.api.geocoding.v5.models.CarmenFeature;
import com.todoroo.astrid.helper.UUIDHelper;
import java.io.Serializable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.tasks.location.MapPosition;
@ -28,7 +34,7 @@ public class Place implements Serializable, Parcelable {
return new Place[size];
}
};
private static final Pattern pattern = Pattern.compile("(\\d+):(\\d+):(\\d+\\.\\d+)");
private static final Pattern COORDS =
Pattern.compile("^\\d+°\\d+'\\d+\\.\\d+\"[NS] \\d+°\\d+'\\d+\\.\\d+\"[EW]$");
@ -83,6 +89,50 @@ public class Place implements Serializable, Parcelable {
longitude = parcel.readDouble();
}
private static String formatCoordinates(org.tasks.data.Place place) {
return String.format(
"%s %s",
formatCoordinate(place.getLatitude(), true), formatCoordinate(place.getLongitude(), false));
}
private static String formatCoordinate(double coordinates, boolean latitude) {
String output =
android.location.Location.convert(Math.abs(coordinates), Location.FORMAT_SECONDS);
Matcher matcher = pattern.matcher(output);
if (matcher.matches()) {
return String.format(
"%s°%s'%s\"%s",
matcher.group(1),
matcher.group(2),
matcher.group(3),
latitude ? (coordinates > 0 ? "N" : "S") : (coordinates > 0 ? "E" : "W"));
} else {
return Double.toString(coordinates);
}
}
public static Place newPlace(MapPosition mapPosition) {
Place place = newPlace();
place.setLatitude(mapPosition.getLatitude());
place.setLongitude(mapPosition.getLongitude());
place.setName(formatCoordinates(place));
return place;
}
public static Place newPlace(CarmenFeature feature) {
String address = feature.placeName();
List<String> types = feature.placeType();
Place place = newPlace();
place.setName(
types != null && types.contains(TYPE_ADDRESS)
? String.format("%s %s", feature.address(), feature.text())
: feature.text());
place.setAddress(address);
place.setLatitude(feature.center().latitude());
place.setLongitude(feature.center().longitude());
return place;
}
public static Place newPlace() {
Place place = new Place();
place.setUid(UUIDHelper.newUUID());
@ -161,9 +211,7 @@ public class Place implements Serializable, Parcelable {
}
public String getDisplayAddress() {
return Strings.isNullOrEmpty(address)
? null
: address.replace(String.format("%s, ", name), "");
return Strings.isNullOrEmpty(address) ? null : address.replace(String.format("%s, ", name), "");
}
String getGeoUri() {

@ -11,7 +11,6 @@ import com.todoroo.astrid.gcal.CalendarReminderActivity;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import com.todoroo.astrid.reminders.ReminderPreferences;
import dagger.Subcomponent;
import org.tasks.location.LocationPickerActivity;
import org.tasks.activities.CalendarSelectionActivity;
import org.tasks.activities.CameraActivity;
import org.tasks.activities.ColorPickerActivity;
@ -31,6 +30,7 @@ import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
import org.tasks.locale.ui.activity.TaskerCreateTaskActivity;
import org.tasks.locale.ui.activity.TaskerSettingsActivity;
import org.tasks.location.LocationPickerActivity;
import org.tasks.preferences.AppearancePreferences;
import org.tasks.preferences.BasicPreferences;
import org.tasks.preferences.DateTimePreferences;
@ -47,7 +47,7 @@ import org.tasks.widget.WidgetClickActivity;
import org.tasks.widget.WidgetConfigActivity;
@ActivityScope
@Subcomponent(modules = ActivityModule.class)
@Subcomponent(modules = {ActivityModule.class, LocationModule.class})
public interface ActivityComponent {
void inject(SynchronizationPreferences synchronizationPreferences);

@ -5,14 +5,8 @@ import android.content.Context;
import dagger.Module;
import dagger.Provides;
import org.tasks.R;
import org.tasks.billing.Inventory;
import org.tasks.gtasks.PlayServices;
import org.tasks.location.GoogleMapFragment;
import org.tasks.location.GooglePlacesSearchProvider;
import org.tasks.location.MapFragment;
import org.tasks.location.MapboxMapFragment;
import org.tasks.location.MapboxSearchProvider;
import org.tasks.location.PlaceSearchProvider;
import org.tasks.location.Geocoder;
import org.tasks.location.MapboxGeocoder;
import org.tasks.preferences.Preferences;
import org.tasks.themes.ThemeAccent;
import org.tasks.themes.ThemeBase;
@ -59,20 +53,7 @@ public class ActivityModule {
@Provides
@ActivityScope
public PlaceSearchProvider getPlaceSearchProvider(
Preferences preferences, Inventory inventory, PlayServices playServices) {
return preferences.useGooglePlaces()
&& playServices.isPlayServicesAvailable()
&& inventory.hasPro()
? new GooglePlacesSearchProvider(activity)
: new MapboxSearchProvider(activity);
}
@Provides
@ActivityScope
public MapFragment getMapFragment(Preferences preferences) {
return preferences.useGoogleMaps()
? new GoogleMapFragment(activity)
: new MapboxMapFragment(activity);
public Geocoder getGeocoder(@ForApplication Context context) {
return new MapboxGeocoder(context);
}
}

@ -0,0 +1,50 @@
package org.tasks.location;
import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread;
import static org.tasks.data.Place.newPlace;
import android.content.Context;
import android.location.Address;
import java.io.IOException;
import java.util.List;
import org.tasks.data.Place;
@SuppressWarnings("unused")
public class AndroidGeocoder implements Geocoder {
private final Context context;
public AndroidGeocoder(Context context) {
this.context = context;
}
@Override
public Place reverseGeocode(MapPosition mapPosition) throws IOException {
assertNotMainThread();
android.location.Geocoder geocoder = new android.location.Geocoder(context);
List<Address> addresses =
geocoder.getFromLocation(mapPosition.getLatitude(), mapPosition.getLongitude(), 1);
Place place = newPlace(mapPosition);
if (addresses.isEmpty()) {
return place;
}
Address address = addresses.get(0);
if (address.getMaxAddressLineIndex() >= 0) {
place.setName(address.getAddressLine(0));
StringBuilder builder = new StringBuilder(place.getName());
for (int i = 1; i <= address.getMaxAddressLineIndex(); i++) {
builder.append(", ").append(address.getAddressLine(i));
}
place.setAddress(builder.toString());
}
if (address.hasLatitude() && address.hasLongitude()) {
place.setLatitude(address.getLatitude());
place.setLongitude(address.getLongitude());
}
place.setPhone(address.getPhone());
place.setUrl(address.getUrl());
return place;
}
}

@ -0,0 +1,8 @@
package org.tasks.location;
import java.io.IOException;
import org.tasks.data.Place;
public interface Geocoder {
Place reverseGeocode(MapPosition mapPosition) throws IOException;
}

@ -4,14 +4,11 @@ import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.todoroo.andlib.utility.AndroidUtilities.hideKeyboard;
import static org.tasks.PermissionUtil.verifyPermissions;
import static org.tasks.data.Place.newPlace;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Parcelable;
@ -48,8 +45,6 @@ import io.reactivex.subjects.PublishSubject;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.tasks.Event;
import org.tasks.R;
@ -84,7 +79,6 @@ public class LocationPickerActivity extends InjectingAppCompatActivity
public static final String EXTRA_PLACE = "extra_place";
private static final String EXTRA_MAP_POSITION = "extra_map_position";
private static final String EXTRA_APPBAR_OFFSET = "extra_appbar_offset";
private static final Pattern pattern = Pattern.compile("(\\d+):(\\d+):(\\d+\\.\\d+)");
private static final int SEARCH_DEBOUNCE_TIMEOUT = 300;
@BindView(R.id.toolbar)
@ -118,6 +112,7 @@ public class LocationPickerActivity extends InjectingAppCompatActivity
@Inject ActivityPermissionRequestor permissionRequestor;
@Inject DialogBuilder dialogBuilder;
@Inject MapFragment map;
@Inject Geocoder geocoder;
private CompositeDisposable disposables;
@Nullable private MapPosition mapPosition;
@ -129,27 +124,6 @@ public class LocationPickerActivity extends InjectingAppCompatActivity
private PublishSubject<String> searchSubject = PublishSubject.create();
private PlaceSearchViewModel viewModel;
private static String formatCoordinates(org.tasks.data.Place place) {
return String.format(
"%s %s",
formatCoordinate(place.getLatitude(), true), formatCoordinate(place.getLongitude(), false));
}
private static String formatCoordinate(double coordinates, boolean latitude) {
String output = Location.convert(Math.abs(coordinates), Location.FORMAT_SECONDS);
Matcher matcher = pattern.matcher(output);
if (matcher.matches()) {
return String.format(
"%s°%s'%s\"%s",
matcher.group(1),
matcher.group(2),
matcher.group(3),
latitude ? (coordinates > 0 ? "N" : "S") : (coordinates > 0 ? "E" : "W"));
} else {
return Double.toString(coordinates);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -312,40 +286,11 @@ public class LocationPickerActivity extends InjectingAppCompatActivity
loadingIndicator.setVisibility(View.VISIBLE);
MapPosition mapPosition = map.getMapPosition();
disposables.add(
Single.fromCallable(
() -> {
Geocoder geocoder = new Geocoder(this);
return geocoder.getFromLocation(
mapPosition.getLatitude(), mapPosition.getLongitude(), 1);
})
Single.fromCallable(() -> geocoder.reverseGeocode(mapPosition))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(() -> loadingIndicator.setVisibility(View.GONE))
.subscribe(
addresses -> {
org.tasks.data.Place place = newPlace();
if (addresses.isEmpty()) {
place.setLatitude(mapPosition.getLatitude());
place.setLongitude(mapPosition.getLongitude());
} else {
Address address = addresses.get(0);
place.setLatitude(address.getLatitude());
place.setLongitude(address.getLongitude());
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
stringBuilder.append(address.getAddressLine(i)).append("\n");
}
place.setPhone(address.getPhone());
place.setAddress(stringBuilder.toString().trim());
String url = address.getUrl();
if (!Strings.isNullOrEmpty(url)) {
place.setUrl(url);
}
}
place.setName(formatCoordinates(place));
returnPlace(place);
},
e -> toaster.longToast(e.getMessage())));
.subscribe(this::returnPlace, e -> toaster.longToast(e.getMessage())));
}
@OnClick(R.id.search)

@ -0,0 +1,60 @@
package org.tasks.location;
import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread;
import static org.tasks.data.Place.newPlace;
import android.content.Context;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import com.mapbox.api.geocoding.v5.MapboxGeocoding;
import com.mapbox.api.geocoding.v5.models.CarmenFeature;
import com.mapbox.api.geocoding.v5.models.GeocodingResponse;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.Mapbox;
import java.io.IOException;
import java.util.List;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.data.Place;
import retrofit2.Response;
import timber.log.Timber;
public class MapboxGeocoder implements Geocoder {
private final String token;
public MapboxGeocoder(Context context) {
token = context.getString(R.string.mapbox_key);
Mapbox.getInstance(context, token);
}
private static String prettyPrint(String json) {
if (BuildConfig.DEBUG) {
return new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json));
}
return json;
}
@Override
public Place reverseGeocode(MapPosition mapPosition) throws IOException {
assertNotMainThread();
Response<GeocodingResponse> response =
MapboxGeocoding.builder()
.accessToken(token)
.query(Point.fromLngLat(mapPosition.getLongitude(), mapPosition.getLatitude()))
.build()
.executeCall();
GeocodingResponse body = response.body();
if (response.isSuccessful() && body != null) {
Timber.d(prettyPrint(body.toJson()));
List<CarmenFeature> features = body.features();
if (features.size() > 0) {
return newPlace(features.get(0));
}
} else {
Timber.e(response.errorBody().string());
}
return newPlace(mapPosition);
}
}

@ -1,6 +1,5 @@
package org.tasks.location;
import static com.mapbox.api.geocoding.v5.GeocodingCriteria.TYPE_ADDRESS;
import static org.tasks.data.Place.newPlace;
import android.content.Context;
@ -84,16 +83,7 @@ public class MapboxSearchProvider implements PlaceSearchProvider {
}
private PlaceSearchResult toSearchResult(CarmenFeature feature) {
String address = feature.placeName();
List<String> types = feature.placeType();
Place place = newPlace();
place.setName(
types != null && types.contains(TYPE_ADDRESS)
? String.format("%s %s", feature.address(), feature.text())
: feature.text());
place.setAddress(address);
place.setLatitude(feature.center().latitude());
place.setLongitude(feature.center().longitude());
Place place = newPlace(feature);
return new PlaceSearchResult(feature.id(), place.getName(), place.getDisplayAddress(), place);
}
}

Loading…
Cancel
Save