From b38aa145e74d9605311de65d9fa134834c4818c1 Mon Sep 17 00:00:00 2001 From: Sarah Lensing Date: Wed, 14 Dec 2016 07:49:00 -0500 Subject: [PATCH] Places autocomplete example (#215) * Rename module library to mapzen-android-sdk * Rename module sample to mapzen-android-sdk-sample * Add core module * Add places module * Deploy places module * mapzen-android-sdk & mapzen-places-api depend on core module * Create places sample app module * Update sample app deploy scripts * Move sample projects into samples * Add GeoDataApi and autocomplete method * Add Places api and GeoDataApi implementation * Add LatLng * Add checkstyle task * Implement LatLngBounds methods * Better LatLng initialization * Add AutocompletePredictionResult * Return AutocompletePendingResult when GeoDataApi autocomplete called * Add AutocompletePrediction * Add DataBuffer interface * Update AutocompletePredictionBuffer to implement DataBuffer * Map pelias results to AutocompletePrediction objs * Checkstyle and javadocs * Add GeoDataApi autocomplete example * Add verify task to places * Rm gitignores * Fix test * dont abort on lint errors for places module --- config/checkstyle/checkstyle-suppressions.xml | 1 + core/build.gradle | 9 +- mapzen-android-sdk/build.gradle | 3 +- mapzen-places-api/build.gradle | 22 ++- .../mapzen/places/api/AutocompleteFilter.java | 8 + .../places/api/AutocompletePrediction.java | 71 +++++++++ .../api/AutocompletePredictionBuffer.java | 43 ++++++ .../com/mapzen/places/api/DataBuffer.java | 20 +++ .../com/mapzen/places/api/GeoDataApi.java | 20 +++ .../java/com/mapzen/places/api/LatLng.java | 46 ++++++ .../com/mapzen/places/api/LatLngBounds.java | 142 ++++++++++++++++++ .../java/com/mapzen/places/api/Places.java | 10 ++ .../internal/AutocompletePendingResult.java | 101 +++++++++++++ .../places/api/internal/GeoDataApiImpl.java | 22 +++ .../api/AutocompletePredictionBufferTest.java | 59 ++++++++ .../api/AutocompletePredictionTest.java | 42 ++++++ .../mapzen/places/api/LatLngBoundsTest.java | 91 +++++++++++ .../com/mapzen/places/api/LatLngTest.java | 38 +++++ .../AutocompletePendingResultTest.java | 120 +++++++++++++++ .../api/internal/GeoDataApiImplTest.java | 19 +++ .../places/api/internal/PlacesTest.java | 18 +++ .../src/main/AndroidManifest.xml | 4 +- .../places/api/sample/GeoDataApiActivity.java | 112 ++++++++++++++ .../places/api/sample/MainActivity.java | 12 -- .../src/main/res/layout/activity_geodata.xml | 13 ++ .../src/main/res/layout/activity_main.xml | 18 --- .../src/main/res/layout/list_item.xml | 24 +++ .../src/main/res/values/strings.xml | 4 +- settings.gradle | 2 +- 29 files changed, 1056 insertions(+), 38 deletions(-) create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompleteFilter.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePrediction.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePredictionBuffer.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/DataBuffer.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/GeoDataApi.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/LatLng.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/LatLngBounds.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/Places.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/internal/AutocompletePendingResult.java create mode 100644 mapzen-places-api/src/main/java/com/mapzen/places/api/internal/GeoDataApiImpl.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionBufferTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngBoundsTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/internal/AutocompletePendingResultTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/internal/GeoDataApiImplTest.java create mode 100644 mapzen-places-api/src/test/java/com/mapzen/places/api/internal/PlacesTest.java create mode 100644 samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/GeoDataApiActivity.java delete mode 100644 samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/MainActivity.java create mode 100644 samples/mapzen-places-api-sample/src/main/res/layout/activity_geodata.xml delete mode 100644 samples/mapzen-places-api-sample/src/main/res/layout/activity_main.xml create mode 100644 samples/mapzen-places-api-sample/src/main/res/layout/list_item.xml diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml index d594fa81..6e76047e 100644 --- a/config/checkstyle/checkstyle-suppressions.xml +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -12,4 +12,5 @@ + diff --git a/core/build.gradle b/core/build.gradle index a325c3d0..86467cb0 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -82,5 +82,12 @@ task verify(dependsOn: ['compileDebugSources', apply from: rootProject.file('gradle/gradle-mvn-push.gradle') dependencies { - + compile 'com.mapzen.android:lost:2.1.2' + compile 'com.mapzen.android:pelias-android-sdk:1.0.0' + + testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' + testCompile 'org.powermock:powermock:1.6.4' + testCompile 'org.powermock:powermock-module-junit4:1.6.4' + testCompile 'org.powermock:powermock-api-mockito:1.6.4' } diff --git a/mapzen-android-sdk/build.gradle b/mapzen-android-sdk/build.gradle index 92ca35db..722f7e35 100644 --- a/mapzen-android-sdk/build.gradle +++ b/mapzen-android-sdk/build.gradle @@ -101,9 +101,8 @@ dependencies { compile 'com.mapzen:mapzen-core:0.0.1-SNAPSHOT' compile "com.mapzen.tangram:tangram:$tangram_version" - compile 'com.mapzen.android:lost:2.1.2' compile 'com.mapzen:on-the-road:1.1.1' - compile 'com.mapzen.android:pelias-android-sdk:1.0.0' + compile 'com.google.dagger:dagger:2.0' compile 'javax.annotation:javax.annotation-api:1.2' diff --git a/mapzen-places-api/build.gradle b/mapzen-places-api/build.gradle index 0d4cb4d0..48230636 100644 --- a/mapzen-places-api/build.gradle +++ b/mapzen-places-api/build.gradle @@ -31,7 +31,6 @@ release { afterReleaseBuild.dependsOn uploadArchives - android { compileSdkVersion 24 buildToolsVersion "24.0.3" @@ -51,6 +50,9 @@ android { testOptions { unitTests.returnDefaultValues = true } + lintOptions { + abortOnError false + } } tasks.withType(Test) { @@ -61,13 +63,29 @@ tasks.withType(Test) { } } +task checkstyle(type: Checkstyle) { + configFile file("${project.rootDir}/config/checkstyle/checkstyle.xml") + source 'src' + include '**/*.java' + exclude '**/gen/**' + + classpath = files() +} + +task verify(dependsOn: ['compileDebugSources', + 'test', + 'checkstyle', + 'lint']) + dependencies { compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.mapzen:mapzen-core:0.0.1-SNAPSHOT' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:1.7.1' + testCompile 'org.powermock:powermock:1.6.4' + testCompile 'org.powermock:powermock-module-junit4:1.6.4' + testCompile 'org.powermock:powermock-api-mockito:1.6.4' } - apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompleteFilter.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompleteFilter.java new file mode 100644 index 00000000..a9b9b356 --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompleteFilter.java @@ -0,0 +1,8 @@ +package com.mapzen.places.api; + +/** + * Filter for customizing autocomplete results from {@link GeoDataApi}. + */ +public class AutocompleteFilter { + +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePrediction.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePrediction.java new file mode 100644 index 00000000..8f26edbc --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePrediction.java @@ -0,0 +1,71 @@ +package com.mapzen.places.api; + +import android.support.annotation.Nullable; +import android.text.style.CharacterStyle; + +import java.util.List; + +/** + * Represents a place returned from {@link GeoDataApi#getAutocompletePredictions( + * com.mapzen.android.lost.api.LostApiClient, String, LatLngBounds, AutocompleteFilter)}. + */ +public class AutocompletePrediction { + + private final String placeId; + private final String primaryText; + + /** + * Constructs a new prediction given an id an primary text. + * @param id + * @param text + */ + public AutocompletePrediction(String id, String text) { + placeId = id; + primaryText = text; + } + + /** + * Not implemented yet. + * @param characterStyle + * @return + */ + public CharSequence getFullText(@Nullable CharacterStyle characterStyle) { + throw new RuntimeException("Not implemented yet"); + } + + /** + * Returns the prediction's primary text. + * @param characterStyle + * @return + */ + public CharSequence getPrimaryText(@Nullable CharacterStyle characterStyle) { + return primaryText; + } + + /** + * Not implemented yet. + * @param characterStyle + * @return + */ + public CharSequence getSecondaryText(@Nullable CharacterStyle characterStyle) { + throw new RuntimeException("Not implemented yet"); + } + + /** + * Return's the prediction's id. + * @return + */ + @Nullable + public String getPlaceId() { + return placeId; + } + + /** + * Not implemented yet. + * @return + */ + @Nullable + public List getPlaceTypes() { + throw new RuntimeException("Not implemented yet"); + } +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePredictionBuffer.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePredictionBuffer.java new file mode 100644 index 00000000..cda1c32f --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/AutocompletePredictionBuffer.java @@ -0,0 +1,43 @@ +package com.mapzen.places.api; + +import com.mapzen.android.lost.api.Result; +import com.mapzen.android.lost.api.Status; + +import java.util.List; + +/** + * Represents a list of autocomplete results. + */ +public class AutocompletePredictionBuffer implements Result, DataBuffer { + + private final Status status; + private final List predictions; + + /** + * Constructs a new buffer given a status and list of autocomplete results. + * @param status + * @param predictions + */ + public AutocompletePredictionBuffer(Status status, List predictions) { + this.status = status; + this.predictions = predictions; + } + + @Override public Status getStatus() { + return status; + } + + @Override public int getCount() { + if (predictions == null) { + return 0; + } + return predictions.size(); + } + + @Override public AutocompletePrediction get(int index) { + if (predictions == null || index < 0 || index > predictions.size() - 1) { + return null; + } + return predictions.get(index); + } +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/DataBuffer.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/DataBuffer.java new file mode 100644 index 00000000..f3f80c75 --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/DataBuffer.java @@ -0,0 +1,20 @@ +package com.mapzen.places.api; + +/** + * Generic interface for representing data contained in a buffer. + * @param + */ +public interface DataBuffer { + /** + * Returns the number of objects in the buffer. + * @return + */ + int getCount(); + + /** + * Returns the object at a given index. + * @param index + * @return + */ + T get(int index); +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/GeoDataApi.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/GeoDataApi.java new file mode 100644 index 00000000..1639ecf8 --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/GeoDataApi.java @@ -0,0 +1,20 @@ +package com.mapzen.places.api; + +import com.mapzen.android.lost.api.LostApiClient; +import com.mapzen.android.lost.api.PendingResult; + +/** + * Main entry point for the Mapzen Places Geo Data API. + */ +public interface GeoDataApi { + /** + * Returns an object which can be used to retrieve autocomplete results. + * @param client + * @param query + * @param bounds + * @param filter + * @return + */ + PendingResult getAutocompletePredictions(LostApiClient client, + String query, LatLngBounds bounds, AutocompleteFilter filter); +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLng.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLng.java new file mode 100644 index 00000000..990bc288 --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLng.java @@ -0,0 +1,46 @@ +package com.mapzen.places.api; + +/** + * Represents a pair of coordinates as degrees. + */ +public class LatLng { + + private static final double LAT_MIN = -90; + private static final double LAT_MAX = 90; + private static final double LNG_MIN = -180; + private static final double LNG_MAX = 180; + private static final double ALL_LNGS = 360; + + private final double latitude; + private final double longitude; + + /** + * Constructs a new object given a latitude and longitude in degrees. + * @param lat + * @param lng + */ + public LatLng(double lat, double lng) { + if (LNG_MIN <= lng && lng < LNG_MAX) { + this.longitude = lng; + } else { + this.longitude = ((lng - LNG_MAX) % ALL_LNGS + ALL_LNGS) % ALL_LNGS - LNG_MAX; + } + this.latitude = Math.max(LAT_MIN, Math.min(LAT_MAX, lat)); + } + + /** + * Latitude, in degrees. This value is in the range [-90, 90]. + * @return + */ + public double getLatitude() { + return latitude; + } + + /** + * Longitude, in degrees. This value is in the range [-180, 180]. + * @return + */ + public double getLongitude() { + return longitude; + } +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLngBounds.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLngBounds.java new file mode 100644 index 00000000..18355cd5 --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/LatLngBounds.java @@ -0,0 +1,142 @@ +package com.mapzen.places.api; + +/** + * Represents a rectangular area. + */ +public class LatLngBounds { + + private final LatLng northeast; + private final LatLng southwest; + + /** + * Constructs a new object given southwest and northwest points. + * @param southwest + * @param northeast + */ + public LatLngBounds(LatLng southwest, LatLng northeast) { + this.southwest = southwest; + this.northeast = northeast; + } + + public LatLng getSouthwest() { + return southwest; + } + + public LatLng getNortheast() { + return northeast; + } + + /** + * Determines whether the given point is contained within the lat/lng's bounds. + * @param point + * @return + */ + public boolean contains(LatLng point) { + return this.includesLat(point.getLatitude()) && this.includesLng(point.getLongitude()); + } + + /** + * Returns a new object which includes the given point. + * @param point + * @return + */ + public LatLngBounds including(LatLng point) { + double swLat = Math.min(this.southwest.getLatitude(), point.getLatitude()); + double neLat = Math.max(this.northeast.getLatitude(), point.getLatitude()); + double neLng = this.northeast.getLongitude(); + double swLng = this.southwest.getLongitude(); + double ptLng = point.getLongitude(); + if (!this.includesLng(ptLng)) { + if (swLngMod(swLng, ptLng) < neLngMod(neLng, ptLng)) { + swLng = ptLng; + } else { + neLng = ptLng; + } + } + + return new LatLngBounds(new LatLng(swLat, swLng), new LatLng(neLat, neLng)); + } + + /** + * Returns the center of the lat/lng bounds. + * @return + */ + public LatLng getCenter() { + double midLat = (this.southwest.getLatitude() + this.northeast.getLatitude()) / 2.0D; + double neLng = this.northeast.getLongitude(); + double swLng = this.southwest.getLongitude(); + double midLng; + if (swLng <= neLng) { + midLng = (neLng + swLng) / 2.0D; + } else { + midLng = (neLng + 360.0D + swLng) / 2.0D; + } + + return new LatLng(midLat, midLng); + } + + private static double swLngMod(double swLng, double ptLng) { + return (swLng - ptLng + 360.0D) % 360.0D; + } + + private static double neLngMod(double neLng, double ptLng) { + return (ptLng - neLng + 360.0D) % 360.0D; + } + + private boolean includesLat(double lat) { + return this.southwest.getLatitude() <= lat && lat <= this.northeast.getLatitude(); + } + + private boolean includesLng(double lng) { + return this.southwest.getLongitude() <= this.northeast.getLongitude() ? + this.southwest.getLongitude() <= lng && lng <= this.northeast.getLongitude() : + this.southwest.getLongitude() <= lng || lng <= this.northeast.getLongitude(); + } + + /** + * Builder class for {@link LatLngBounds}. + */ + public static class Builder { + + private double northeastLat = 0; + private double northeastLng = 0; + private double southwestLat = 0; + private double southwestLng = 0; + + /** + * Includes this point for building of the bounds. The bounds will be extended in a minimum way + * to include this point. + * @param point + * @return builder object + */ + public Builder include(LatLng point) { + if (northeastLat == 0) { + northeastLat = point.getLatitude(); + northeastLng = point.getLongitude(); + southwestLat = point.getLatitude(); + southwestLng = point.getLongitude(); + } + if (point.getLatitude() > northeastLat) { + northeastLat = point.getLatitude(); + } else if (point.getLatitude() < southwestLat) { + southwestLat = point.getLatitude(); + } + if (point.getLongitude() > northeastLng) { + northeastLng = point.getLongitude(); + } else if (point.getLongitude() < southwestLng) { + southwestLng = point.getLongitude(); + } + return this; + } + + /** + * Constructs a new {@link LatLngBounds} from current boundaries. + * @return + */ + public LatLngBounds build() { + LatLng sw = new LatLng(southwestLat, southwestLng); + LatLng ne = new LatLng(northeastLat, northeastLng); + return new LatLngBounds(sw, ne); + } + } +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/Places.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/Places.java new file mode 100644 index 00000000..41cb2fed --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/Places.java @@ -0,0 +1,10 @@ +package com.mapzen.places.api; + +import com.mapzen.places.api.internal.GeoDataApiImpl; + +/** + * Main entry point for Mapzen Places API. + */ +public class Places { + public static final GeoDataApi GeoDataApi = new GeoDataApiImpl(); +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/AutocompletePendingResult.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/AutocompletePendingResult.java new file mode 100644 index 00000000..b7c1b66d --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/AutocompletePendingResult.java @@ -0,0 +1,101 @@ +package com.mapzen.places.api.internal; + +import com.mapzen.android.lost.api.PendingResult; +import com.mapzen.android.lost.api.ResultCallback; +import com.mapzen.android.lost.api.Status; +import com.mapzen.pelias.Pelias; +import com.mapzen.pelias.gson.Feature; +import com.mapzen.pelias.gson.Result; +import com.mapzen.places.api.AutocompleteFilter; +import com.mapzen.places.api.AutocompletePrediction; +import com.mapzen.places.api.AutocompletePredictionBuffer; +import com.mapzen.places.api.LatLng; +import com.mapzen.places.api.LatLngBounds; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import retrofit.Callback; +import retrofit.RetrofitError; +import retrofit.client.Response; + +/** + * Object returned by {@link GeoDataApiImpl#getAutocompletePredictions( + * com.mapzen.android.lost.api.LostApiClient, String, LatLngBounds, AutocompleteFilter)}. + */ +public class AutocompletePendingResult extends PendingResult { + + private final Pelias pelias; + private final String query; + private final LatLngBounds bounds; + private final AutocompleteFilter filter; + + /** + * Constructs a new object given a pelias instance, a query, a lat/lng bounds, and an autocomplete + * filter. + * @param pelias + * @param query + * @param bounds + * @param filter + */ + public AutocompletePendingResult(Pelias pelias, String query, LatLngBounds bounds, + AutocompleteFilter filter) { + this.pelias = pelias; + this.query = query; + this.bounds = bounds; + this.filter = filter; + } + + @NonNull @Override public AutocompletePredictionBuffer await() { + throw new RuntimeException("Not implemented yet"); + } + + @NonNull @Override + public AutocompletePredictionBuffer await(long time, @NonNull TimeUnit timeUnit) { + throw new RuntimeException("Not implemented yet"); + } + + @Override public void cancel() { + throw new RuntimeException("Not implemented yet"); + } + + @Override public boolean isCanceled() { + throw new RuntimeException("Not implemented yet"); + } + + @Override public void setResultCallback( + @NonNull final ResultCallback callback) { + LatLng center = bounds.getCenter(); + pelias.suggest(query, center.getLatitude(), center.getLongitude(), new Callback() { + @Override public void success(Result result, Response response) { + Status status = new Status(Status.SUCCESS); + + final ArrayList predictions = new ArrayList<>(); + final List features = result.getFeatures(); + for (Feature feature : features) { + AutocompletePrediction prediction = new AutocompletePrediction(feature.properties.gid, + feature.properties.name); + predictions.add(prediction); + } + + AutocompletePredictionBuffer buffer = new AutocompletePredictionBuffer(status, predictions); + callback.onResult(buffer); + } + + @Override public void failure(RetrofitError error) { + Status status = new Status(Status.INTERNAL_ERROR); + AutocompletePredictionBuffer buffer = new AutocompletePredictionBuffer(status, null); + callback.onResult(buffer); + } + }); + } + + @Override public void setResultCallback( + @NonNull ResultCallback callback, long time, + @NonNull TimeUnit timeUnit) { + throw new RuntimeException("Not implemented yet"); + } +} diff --git a/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/GeoDataApiImpl.java b/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/GeoDataApiImpl.java new file mode 100644 index 00000000..52ea24ba --- /dev/null +++ b/mapzen-places-api/src/main/java/com/mapzen/places/api/internal/GeoDataApiImpl.java @@ -0,0 +1,22 @@ +package com.mapzen.places.api.internal; + +import com.mapzen.android.lost.api.LostApiClient; +import com.mapzen.android.lost.api.PendingResult; +import com.mapzen.pelias.Pelias; +import com.mapzen.places.api.AutocompleteFilter; +import com.mapzen.places.api.AutocompletePredictionBuffer; +import com.mapzen.places.api.GeoDataApi; +import com.mapzen.places.api.LatLngBounds; + +/** + * {@link GeoDataApi} implementation for {@link com.mapzen.places.api.Places}. + */ +public class GeoDataApiImpl implements GeoDataApi { + + private Pelias pelias = new Pelias(); + + @Override public PendingResult getAutocompletePredictions( + LostApiClient client, String query, LatLngBounds bounds, AutocompleteFilter filter) { + return new AutocompletePendingResult(pelias, query, bounds, filter); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionBufferTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionBufferTest.java new file mode 100644 index 00000000..1f41694f --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionBufferTest.java @@ -0,0 +1,59 @@ +package com.mapzen.places.api; + +import com.mapzen.android.lost.api.Status; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AutocompletePredictionBufferTest { + + AutocompletePredictionBuffer buffer; + Status status; + List predictions; + + @Before public void setup() { + status = new Status(Status.SUCCESS); + predictions = new ArrayList<>(); + predictions.add(new AutocompletePrediction("test", "test")); + buffer = new AutocompletePredictionBuffer(status, predictions); + } + + @Test public void getStatus_shouldReturnStatus() { + Status s = buffer.getStatus(); + assertThat(s).isEqualTo(status); + } + + @Test public void getCount_shouldReturnCorrectCount() { + int count = buffer.getCount(); + assertThat(count).isEqualTo(1); + } + + @Test public void getCount_shouldReturnCorrectCount_nullPredictions() { + buffer = new AutocompletePredictionBuffer(status, null); + int count = buffer.getCount(); + assertThat(count).isEqualTo(0); + } + + @Test public void get_shouldReturnCorrectPrediction() { + AutocompletePrediction prediction = buffer.get(0); + assertThat(prediction).isEqualTo(predictions.get(0)); + } + + @Test public void get_shouldReturnCorrectPrediction_nullPredictions() { + buffer = new AutocompletePredictionBuffer(status, null); + AutocompletePrediction prediction = buffer.get(0); + assertThat(prediction).isNull(); + } + + @Test public void get_shouldReturnCorrectPrediction_badIndex() { + AutocompletePrediction prediction = buffer.get(-1); + assertThat(prediction).isNull(); + prediction = buffer.get(100); + assertThat(prediction).isNull(); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionTest.java new file mode 100644 index 00000000..dd2f2eb8 --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/AutocompletePredictionTest.java @@ -0,0 +1,42 @@ +package com.mapzen.places.api; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AutocompletePredictionTest { + + AutocompletePrediction prediction; + String id = "abc123"; + String primaryText = "Roberta's Pizza"; + + @Before public void setup() { + prediction = new AutocompletePrediction(id, primaryText); + } + + @Test(expected = RuntimeException.class) + public void getFullText_shouldThrowException() { + prediction.getFullText(null); + } + + @Test public void getPrimaryText_shouldReturnCorrectText() { + CharSequence text = prediction.getPrimaryText(null); + assertThat(text.toString()).isEqualTo(primaryText); + } + + @Test(expected = RuntimeException.class) + public void getSecondaryText_shouldThrowException() { + prediction.getSecondaryText(null); + } + + @Test public void getPlaceId_shouldReturnCorrectId() { + String placeId = prediction.getPlaceId(); + assertThat(placeId).isEqualTo(id); + } + + @Test(expected = RuntimeException.class) + public void getPlaceTypes_shouldThrowException() { + prediction.getPlaceTypes(); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngBoundsTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngBoundsTest.java new file mode 100644 index 00000000..a6f2416a --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngBoundsTest.java @@ -0,0 +1,91 @@ +package com.mapzen.places.api; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LatLngBoundsTest { + + LatLng southWestPoint = new LatLng(-50, -90); + LatLng northEastPoint = new LatLng(40, 80); + LatLngBounds bounds = new LatLngBounds(southWestPoint, northEastPoint); + + @Test public void shouldSetNorthEastPoint() { + assertThat(bounds.getNortheast()).isEqualTo(northEastPoint); + } + + @Test public void shouldSetSouthWestPoint() { + assertThat(bounds.getSouthwest()).isEqualTo(southWestPoint); + } + + @Test public void include_shouldSetNorthEastPoint() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + assertThat(bounds.getNortheast().getLatitude()).isEqualTo(40); + assertThat(bounds.getNortheast().getLongitude()).isEqualTo(50); + } + + @Test public void include_shouldSetSouthWestPoint() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + assertThat(bounds.getSouthwest().getLatitude()).isEqualTo(-40); + assertThat(bounds.getSouthwest().getLongitude()).isEqualTo(-50); + } + + @Test public void contains_shouldReturnTrue() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + LatLng point = new LatLng(20, 20); + assertThat(bounds.contains(point)).isTrue(); + } + + @Test public void contains_shouldReturnFalse() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + LatLng point = new LatLng(20, 120); + assertThat(bounds.contains(point)).isFalse(); + } + + @Test public void including_shouldReturnCorrectBounds() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + LatLng point = new LatLng(30, 100); + LatLngBounds containing = bounds.including(point); + assertThat(containing.getNortheast().getLatitude()).isEqualTo(40); + assertThat(containing.getNortheast().getLongitude()).isEqualTo(100); + assertThat(containing.getSouthwest().getLatitude()).isEqualTo(-40); + assertThat(containing.getSouthwest().getLongitude()).isEqualTo(-50); + } + + @Test public void getCenter_shouldReturnCorrectCenter() { + LatLng pointA = new LatLng(40, 50); + LatLng pointB = new LatLng(-40, -50); + LatLngBounds bounds = new LatLngBounds.Builder() + .include(pointA) + .include(pointB) + .build(); + LatLng center = bounds.getCenter(); + assertThat(center.getLatitude()).isEqualTo(0); + assertThat(center.getLongitude()).isEqualTo(0); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngTest.java new file mode 100644 index 00000000..40400033 --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/LatLngTest.java @@ -0,0 +1,38 @@ +package com.mapzen.places.api; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LatLngTest { + + LatLng latLng = new LatLng(40, 70); + + @Test public void shouldSetLat() { + assertThat(latLng.getLatitude()).isEqualTo(40); + } + + @Test public void shouldSetLng() { + assertThat(latLng.getLongitude()).isEqualTo(70); + } + + @Test public void shouldSetMinLat() { + LatLng minLat = new LatLng(-100, 70); + assertThat(minLat.getLatitude()).isEqualTo(-90); + } + + @Test public void shouldSetMaxLat() { + LatLng maxLat = new LatLng(100, 70); + assertThat(maxLat.getLatitude()).isEqualTo(90); + } + + @Test public void shouldSetLng_160() { + LatLng minLng = new LatLng(40, -200); + assertThat(minLng.getLongitude()).isEqualTo(160); + } + + @Test public void shouldSetLng_neg160() { + LatLng maxLng = new LatLng(40, 200); + assertThat(maxLng.getLongitude()).isEqualTo(-160); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/AutocompletePendingResultTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/AutocompletePendingResultTest.java new file mode 100644 index 00000000..93ab31f0 --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/AutocompletePendingResultTest.java @@ -0,0 +1,120 @@ +package com.mapzen.places.api.internal; + +import com.mapzen.android.lost.api.ResultCallback; +import com.mapzen.android.lost.api.Status; +import com.mapzen.pelias.Pelias; +import com.mapzen.pelias.gson.Result; +import com.mapzen.places.api.AutocompletePredictionBuffer; +import com.mapzen.places.api.LatLng; +import com.mapzen.places.api.LatLngBounds; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import android.support.annotation.NonNull; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import retrofit.Callback; + +public class AutocompletePendingResultTest { + + AutocompletePendingResult result; + Pelias pelias; + String query; + LatLngBounds bounds; + + @Before public void setup() { + pelias = spy(new Pelias()); + query = "test"; + bounds = new LatLngBounds(new LatLng(-40, -40), new LatLng(40, 40)); + result = new AutocompletePendingResult(pelias, query, bounds, null); + } + + @Test(expected = RuntimeException.class) + public void await_shouldThrowException() { + result.await(); + } + + @Test(expected = RuntimeException.class) + public void awaitTimeout_shouldThrowException() { + result.await(1, TimeUnit.SECONDS); + } + + @Test(expected = RuntimeException.class) + public void cancel_shouldThrowException() { + result.cancel(); + } + + @Test(expected = RuntimeException.class) + public void isCanceled_shouldThrowException() { + result.isCanceled(); + } + + @Test public void setResultCallback_shouldInvokePeliasSuggest() { + TestResultCallback callback = new TestResultCallback(); + result.setResultCallback(callback); + LatLng center = bounds.getCenter(); + verify(pelias).suggest(eq(query), eq(center.getLatitude()), eq(center.getLongitude()), + any(Callback.class)); + } + + @Test public void setResultCallback_shouldReturnSuccessStatus() { + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + Callback callback = (Callback) args[3]; + final Result result = new Result(); + callback.success(result, null); + return null; + } + }).when(pelias).suggest(anyString(), anyDouble(), anyDouble(), any(Callback.class)); + + TestResultCallback callback = new TestResultCallback(); + result.setResultCallback(callback); + assertThat(callback.getBuffer().getStatus().getStatusCode()).isEqualTo(Status.SUCCESS); + } + + @Test public void setResultCallback_shouldReturnFailureStatus() { + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + Callback callback = (Callback) args[3]; + callback.failure(null); + return null; + } + }).when(pelias).suggest(anyString(), anyDouble(), anyDouble(), any(Callback.class)); + + TestResultCallback callback = new TestResultCallback(); + result.setResultCallback(callback); + assertThat(callback.getBuffer().getStatus().getStatusCode()).isEqualTo(Status.INTERNAL_ERROR); + } + + @Test(expected = RuntimeException.class) + public void setResultCallbackTimeout_shouldThrowException() { + result.setResultCallback(null, 1, TimeUnit.SECONDS); + } + + class TestResultCallback implements ResultCallback { + + private AutocompletePredictionBuffer buffer; + + @Override public void onResult(@NonNull AutocompletePredictionBuffer result) { + buffer = result; + } + + public AutocompletePredictionBuffer getBuffer() { + return buffer; + } + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/GeoDataApiImplTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/GeoDataApiImplTest.java new file mode 100644 index 00000000..6760c5a7 --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/GeoDataApiImplTest.java @@ -0,0 +1,19 @@ +package com.mapzen.places.api.internal; + +import com.mapzen.android.lost.api.PendingResult; +import com.mapzen.places.api.AutocompletePredictionBuffer; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GeoDataApiImplTest { + + GeoDataApiImpl geoDataApi = new GeoDataApiImpl(); + + @Test public void getAutocompletePredictions_shouldReturnAutocompletePendingResult() { + PendingResult result = + geoDataApi.getAutocompletePredictions(null, null, null, null); + assertThat(result).isInstanceOf(AutocompletePendingResult.class); + } +} diff --git a/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/PlacesTest.java b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/PlacesTest.java new file mode 100644 index 00000000..9b15f353 --- /dev/null +++ b/mapzen-places-api/src/test/java/com/mapzen/places/api/internal/PlacesTest.java @@ -0,0 +1,18 @@ +package com.mapzen.places.api.internal; + +import com.mapzen.places.api.Places; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PlacesTest { + + @Test public void geoDataApiShouldNotBeNull() { + assertThat(Places.GeoDataApi).isNotNull(); + } + + @Test public void geoDataApiShouldBeOfCorrectClass() { + assertThat(Places.GeoDataApi).isInstanceOf(GeoDataApiImpl.class); + } +} diff --git a/samples/mapzen-places-api-sample/src/main/AndroidManifest.xml b/samples/mapzen-places-api-sample/src/main/AndroidManifest.xml index c435371b..b41dd713 100644 --- a/samples/mapzen-places-api-sample/src/main/AndroidManifest.xml +++ b/samples/mapzen-places-api-sample/src/main/AndroidManifest.xml @@ -2,13 +2,15 @@ + + - + diff --git a/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/GeoDataApiActivity.java b/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/GeoDataApiActivity.java new file mode 100644 index 00000000..e13817b3 --- /dev/null +++ b/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/GeoDataApiActivity.java @@ -0,0 +1,112 @@ +package com.mapzen.places.api.sample; + +import com.mapzen.android.lost.api.LostApiClient; +import com.mapzen.android.lost.api.PendingResult; +import com.mapzen.android.lost.api.ResultCallback; +import com.mapzen.places.api.AutocompletePrediction; +import com.mapzen.places.api.AutocompletePredictionBuffer; +import com.mapzen.places.api.LatLng; +import com.mapzen.places.api.LatLngBounds; +import com.mapzen.places.api.Places; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +/** + * Demonstrates use of the {@link com.mapzen.places.GeoDataApi} + */ +public class GeoDataApiActivity extends AppCompatActivity { + + LostApiClient client; + + ListView listView; + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_geodata); + listView = (ListView) findViewById(R.id.list_view); + + connectClient(); + } + + private void connectClient() { + client = new LostApiClient.Builder(this).addConnectionCallbacks( + new LostApiClient.ConnectionCallbacks() { + + @Override public void onConnected() { + queryAutocompleteApi(); + } + + @Override public void onConnectionSuspended() { + + } + }).build(); + client.connect(); + } + + private void queryAutocompleteApi() { + String query = "pizza"; + LatLng pointA = new LatLng(40.020451, -105.274679); + LatLng pointB = new LatLng(40.012004, -105.289957); + LatLngBounds bounds = new LatLngBounds.Builder().include(pointA).include(pointB).build(); + PendingResult result = + Places.GeoDataApi.getAutocompletePredictions(client, query, bounds, null); + result.setResultCallback(new ResultCallback() { + @Override public void onResult(@NonNull AutocompletePredictionBuffer result) { + displayAutocompleteItems(result); + } + }); + } + + private void displayAutocompleteItems(final AutocompletePredictionBuffer buffer) { + listView.setAdapter(new BaseAdapter() { + @Override public int getCount() { + return buffer.getCount(); + } + + @Override public Object getItem(int i) { + return buffer.get(i); + } + + @Override public long getItemId(int i) { + return i; + } + + @Override public View getView(int position, View convertView, ViewGroup parent) { + View view; + ViewHolder holder; + if (convertView == null) { + view = LayoutInflater.from(GeoDataApiActivity.this) + .inflate(R.layout.list_item, parent, false); + holder = new ViewHolder(view); + view.setTag(holder); + } else { + view = convertView; + holder = (ViewHolder) view.getTag(); + } + AutocompletePrediction prediction = buffer.get(position); + holder.title.setText(prediction.getPlaceId()); + holder.description.setText(prediction.getPrimaryText(null)); + return view; + } + }); + } + + private class ViewHolder { + TextView title; + TextView description; + + ViewHolder(View view) { + title = (TextView) view.findViewById(R.id.title); + description = (TextView) view.findViewById(R.id.description); + } + } +} diff --git a/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/MainActivity.java b/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/MainActivity.java deleted file mode 100644 index 15bef9a8..00000000 --- a/samples/mapzen-places-api-sample/src/main/java/com/mapzen/places/api/sample/MainActivity.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.mapzen.places.api.sample; - -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/samples/mapzen-places-api-sample/src/main/res/layout/activity_geodata.xml b/samples/mapzen-places-api-sample/src/main/res/layout/activity_geodata.xml new file mode 100644 index 00000000..7bbecd7c --- /dev/null +++ b/samples/mapzen-places-api-sample/src/main/res/layout/activity_geodata.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/samples/mapzen-places-api-sample/src/main/res/layout/activity_main.xml b/samples/mapzen-places-api-sample/src/main/res/layout/activity_main.xml deleted file mode 100644 index 57bea024..00000000 --- a/samples/mapzen-places-api-sample/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/samples/mapzen-places-api-sample/src/main/res/layout/list_item.xml b/samples/mapzen-places-api-sample/src/main/res/layout/list_item.xml new file mode 100644 index 00000000..b4e0639a --- /dev/null +++ b/samples/mapzen-places-api-sample/src/main/res/layout/list_item.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/samples/mapzen-places-api-sample/src/main/res/values/strings.xml b/samples/mapzen-places-api-sample/src/main/res/values/strings.xml index 274f15b6..46d224b4 100644 --- a/samples/mapzen-places-api-sample/src/main/res/values/strings.xml +++ b/samples/mapzen-places-api-sample/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ - mapzen-places-api-sample + Mapzen Places API + GeoDataApi + Places.GeoDataApi example usage diff --git a/settings.gradle b/settings.gradle index 38d48d9b..43dec25e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,4 @@ include ':samples_mapzen-places-api-sample' project(':samples_mapzen-android-sdk-sample').projectDir = new File('samples/mapzen-android-sdk-sample') project(':samples_mapzen-places-api-sample').projectDir = - new File('samples/mapzen-places-api-sample') \ No newline at end of file + new File('samples/mapzen-places-api-sample')