PlacePickerというものがある
https://developers.google.com/places/android-api/placepicker?hl=ja
PlacePicker には、地理的住所やローカル ビジネスに一致するプレイスを含むインタラクティブなマップや近隣のプレイスの一覧を表示する UI ダイアログが用意されています。ユーザーがプレイスを選択すると、アプリでそのプレイスの詳細を取得できます。
ということで,intentで専用のダイアログに飛ばして,その選択結果(1件のみ)を forActivityResult()
で受け取って処理したりするのだが,わざわざ検索で別の画面に飛ばして選択させて帰ってこさせるのがなんか回りくどいと思ったのと,一度に複数件アプリ側のMap上にスポットのピンを立てたい!ということで,直接WebAPIの方を叩けば周辺nメートルのスポットを検索できたりするので,今回試してみることに
まずはじめに
兎にも角にも.Google Maps API v2 for AndroidとGoogle Places API Web ServiceをGoogle Developer Consoleで有効にしましょう.Google Maps APIにはAndroidキーが,Google Places APIには(恐らく直接RESTでAPIを叩くため)ブラウザキーが必要です.
有効化ができたら.app側のbuild.gradleのdependenciesに次の記述を追加して,okhttpとretrofit,google play servicesを導入します.
dependencies {
compile "com.squareup.okhttp:okhttp:2.4.0"
compile "com.squareup.okhttp:okhttp-urlconnection:2.4.0"
compile "com.squareup.retrofit:retrofit:2.0.0-beta2"
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.google.android.gms:play-services:8.1.0'
}
その後,AndroidManifest.xmlにパーミッションを追加します
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
また,Google Maps APIを使うためのmeta-dataをapplicationタグ内に追加したりします
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
Responseの受け皿となるPOJOクラスを用意する
Google Places API Web Serviceのレスポンス例は公式サイトで確認できます.
developers.google.com
ということで,これを受け取るためのPOJOクラスを作成します.今回は,後述しますがGsonConverterを使うので,各クラスのメンバには"m"はつけず,レスポンスのJSONの属性名に合わせます.例えば,nameであれば,mNameとするのではなく,nameのままで記述します.
まず,一番深いところにあるLocation.java.
public class Location {
private static final String TAG = Location.class.getSimpleName();
private final Location self = this;
private double lat;
private double lng;
public Location(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
}
続いて,その外側にあるGeometory.java
public class Geometry {
private static final String TAG = Geometry.class.getSimpleName();
private final Geometry self = this;
private Location location;
public Geometry(Location location) {
this.location = location;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
}
更に外側にあるResult.java
public class Result {
private static final String TAG = Result.class.getSimpleName();
private final Result self = this;
private Geometry geometry;
private String icon;
private String id;
private String name;
private String place_id;
private String rating;
private String reference;
private String[] types;
private String vicinity;
public Result(Geometry geometry, String icon, String id, String name, String place_id, String rating, String reference, String[] types, String vicinity) {
this.geometry = geometry;
this.icon = icon;
this.id = id;
this.name = name;
this.place_id = place_id;
this.rating = rating;
this.reference = reference;
this.types = types;
this.vicinity = vicinity;
}
public Geometry getGeometry() {
return geometry;
}
public void setGeometry(Geometry geometry) {
this.geometry = geometry;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPlace_id() {
return place_id;
}
public void setPlace_id(String place_id) {
this.place_id = place_id;
}
public String getRating() {
return rating;
}
public void setRating(String rating) {
this.rating = rating;
}
public String getReference() {
return reference;
}
public void setReference(String reference) {
this.reference = reference;
}
public String[] getTypes() {
return types;
}
public void setTypes(String[] types) {
this.types = types;
}
public String getVicinity() {
return vicinity;
}
public void setVicinity(String vicinity) {
this.vicinity = vicinity;
}
}
最後に,一番外側のResponse.java.
public class Response {
private static final String TAG = Response.class.getSimpleName();
private final Response self = this;
private List<Result> results;
public Response(List<Result> results) {
this.results = results;
}
public List<Result> getResults() {
return results;
}
public void setResults(List<Result> results) {
this.results = results;
}
}
このように記述することで,ネストしているJSONレスポンスでもGsonConverterでうまいことパースしてくれるようになります.
RetrofitでAPIを叩く仕組みを作る
まず,使うAPIを記述したServiceのInterfaceを記述します.
URL Sample:
https://maps.googleapis.com/maps/api/place/search/json
?types=cafe
&location=37.787930,-122.4074990
&radius=5000
&sensor=false
&key=YOUR_API_KEY
public interface PlaceApiService {
@Headers("Accept-Language: ja")
@GET("/maps/api/place/search/json")
Call<Response> requestPlaces(@Query("types") String types,
@Query("location") String location,
@Query("radius") String radius,
@Query("sensor") String sensor,
@Query("key") String key);
}
そして,このServiceを利用してPlaces APIにアクセスするためのクラスを用意します.
public class PlacesApiHelper {
private static final String TAG = PlacesApiHelper.class.getSimpleName();
private final PlacesApiHelper self = this;
private Context mContext;
public PlacesApiHelper(Context context) {
mContext = context;
}
public void requestPlaces(String types, LatLng latLng, int radius, Callback<Response> callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(mContext.getString(R.string.places_api_url))
.addConverterFactory(GsonConverterFactory.create())
.build();
PlaceApiService service = retrofit.create(PlaceApiService.class);
Call<Response> call = service.requestPlaces(types,
String.valueOf(latLng.latitude) + "," + String.valueOf(latLng.longitude),
String.valueOf(radius),
"false",
mContext.getString(R.string.google_maps_key_browser));
call.enqueue(callback);
}
}
R.string.places_api_url
にはPlaces APIのURL(https://maps.googleapis.com)を,R.string.google_maps_key_browser
にはDeveloper Consoleで取得したブラウザキーを記述しておきましょう.
ActivityにGoogle Mapを表示させ,ボタンをクリックしたらスポット検索させるようにする.
次のようなLayoutを用意します. SupportMapFragment
を利用します.EditTextがありますが今回は使わなかったりします.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/edit_search_word"
android:layout_toStartOf="@+id/btn_search"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Search"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:id="@+id/btn_search"/>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/edit_search_word"
android:layout_marginTop="5dp"
android:name="com.google.android.gms.maps.SupportMapFragment"/>
</RelativeLayout>
そしてMainActivityを記述します.今回は始点の座標は決め打ちです.
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final String TAG = PasteActivity.class.getSimpleName();
private final PasteActivity self = this;
EditText mSearchEdit;
Button mSearchButton;
PlacesApiHelper mHelper;
private GoogleMap mGoogleMap;
private SupportMapFragment mMapFragment;
private LatLng mCurrentLatLng;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSearchEdit = (EditText) findViewById(R.id.edit_search_word);
mSearchButton = (Button) findViewById(R.id.btn_search);
mSearchButton.setOnClickListener(mOnSearchButtonClickListener);
mHelper = new PlacesApiHelper(this);
mMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mMapFragment.getMapAsync(this);
}
@Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
}
private View.OnClickListener mOnSearchButtonClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentLatLng = new LatLng(34.694487, 135.19517);
mHelper.requestPlaces("food", mCurrentLatLng, 5000, mResultCallback);
}
};
private Callback<Response> mResultCallback = new Callback<Response>() {
@Override
public void onResponse(retrofit.Response<Response> response, Retrofit retrofit) {
mGoogleMap.clear();
List<Result> results = response.body().getResults();
for(Result r : results) {
Location location = r.getGeometry().getLocation();
LatLng latLng = new LatLng(location.getLat(), location.getLng());
String name = r.getName();
mGoogleMap.addMarker(new MarkerOptions().position(latLng).title(name));
}
mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mCurrentLatLng, 15));
}
@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
};
}
と,ここまでやれば地図に周辺の検索数だけピンを打てるようになります.