이번 포스팅은 Google Map을 활용한 위치기반 서비스 앱 개발이다.
이전 포스팅에서 확인하였듯이 LBS 관련 서비스를 제공하기 위해선 Google Play 서비스 활용이 필수적이다.
[현재의 위치값 가져오기 복습]
Fused Location을 이용해서 위치정보를 받아오는 정보는 아래 3가지 방식을 통해서 받아온다.
- GPS
- 통신사 NW
- Wifi NW
위의 세가지 방식 중 GPS가 가장 정확하지만 배터리 소모가 많고 음영지역이 많다는 단점이 있다. 통신사 NW의 경우 상대적으로 음영지역이 적은 반면에 오차율이 1Km ~ 1.5Km로 너무 넓다는 단점이 있다. Wifi 네트워크를 이용하는 방식은 아직 우리나라에서는 법적으로 제한이 걸려있기 때문에 사용할 수 없다.
Fused Location API를 사용하기 전에 설정을 하면 위의 세가지 방법 중 가장 적절한 방법으로 현재의 위치 값을 가져오게 된다.
[Google Map을 활용]
Web 프로그래밍을할 때 Google Map을 이용하는방법은 정말 간단하다.
<script src=~~>를 통해 Google Map API를 임포트하고 지도를 표시 할 Layout을 설정하고 함수 하나만 콜 하면 될 일이었다.
그렇게 간단하던 Google Map이 안드로이드에서는 상대적으로 엄청 복잡한 과정을 거친다.
Manifest 설정, Fragment를 이용한 화면단 소스코딩 게다가 Android Developer Console에 프로젝트를 등록하여 Authentication Key 값을 생성 및 얻어와야지 비로소 사용할 수 있게된다.
[구현하기]
일단 manifest 파일에 permission 세팅
<permission
android:name="com.example.ch5_lbs.permission.MAPS_RECEIVE"
android:protectionLevel="signature"/>
<uses-permission
android:name="com.example.ch5_lbs.permission.MAPS_RECEIVE"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Google Map 사용을 하기 위해서 키값 입력
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyDQQICJSD3Rx3m6b0AFXiHJOjEeGWM3Sqw"/>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Google Map을 표현하기 위해서 Layout.xml에 Fragment를 추가
<fragment
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:name="com.google.android.gms.maps.SupportMapFragment"/>
MainActivity.java
package com.example.ch5_lbs;
import android.location.Location;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.FusedLocationProviderApi;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
/** * OnMapReadyCallback: 지도가 준비된 시점을 잡기위한 callback * 이 시점에 map을 이용하지 않으면 시간차의 문제로 nullpoint등의 에러 발생 * */
public class MainActivity extends AppCompatActivity
implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, OnMapReadyCallback {
TextView providerTextView;
ImageView onOffImageView;
TextView timeTextView;
TextView locationTextView;
TextView accuracyTextView;
GoogleApiClient googleApiClient;
FusedLocationProviderApi fusedLocationProviderApi;
Location currentlocation;
GoogleMap map; // fragment에 지도 뿌리는 view
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
providerTextView = (TextView) findViewById(R.id.txt_location_provider);
onOffImageView = (ImageView) findViewById(R.id.img_location_on_off);
timeTextView = (TextView) findViewById(R.id.gps_time);
locationTextView = (TextView) findViewById(R.id.gps_location);
accuracyTextView = (TextView) findViewById(R.id.gps_accuracy);
/** * Fused API는 실제 google play service라이브러리에서 제공되는 기능으로 * google의 다양한 service를 활용하기 위한 라이브러리이다. * 그중 locationservice쪽을 이용하겠다고 선언 */ googleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
fusedLocationProviderApi = LocationServices.FusedLocationApi;
}
private void toast(String msg){
Toast t=Toast.makeText(this, msg, Toast.LENGTH_SHORT);
t.show();
}
private String getDateTime(long time) {
if (time == 0)
return "";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return formatter.format(new java.util.Date(time));
}
private String convertDouble(double input) {
DecimalFormat format = new DecimalFormat(".######");
return format.format(input);
}
/** * 위치정보 획득후 호출 * 매개변수의 정보대로 다양한 정보 추출하고 화면 출력 역할 */ private void updateInfo(Location location){
onOffImageView.setImageResource(R.drawable.on);
timeTextView.setText(getDateTime(location.getTime()));
locationTextView.setText("LAT:" + convertDouble(location.getLatitude())
+" LNG:" + convertDouble(location.getLongitude()));
accuracyTextView.setText(location.getAccuracy() + " meters");
}
/** * 위치값 획득후에 호출되어 지도제어 */ private void showMap(Location location){
// 지도에서의 위치는 LatLng로 표현 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
// 지도 옵션 CameraPosition position = new CameraPosition.Builder().target(latLng).zoom(16f).build();
// 지도의 center 위치 이동 map.moveCamera(CameraUpdateFactory.newCameraPosition(position));
// market 올리기 map.clear(); // 이전 marker지우고 안지우면 중복으로 나옴 map.addMarker(new MarkerOptions().position(latLng).title("Location")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE)));
}
@Override protected void onResume() {
super.onResume();
googleApiClient.connect(); // 겨리과는 callback함수 호출로 if(map==null){
((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map_view)).getMapAsync(this);
}
}
@Override
protected void onPause() {
super.onPause();
if(googleApiClient.isConnected()){
googleApiClient.disconnect();
}
}
@Override
public void onConnected(@Nullable Bundle bundle) {
// 위치값 획득 Location location = fusedLocationProviderApi.getLastLocation(googleApiClient);
if(location != null){
currentlocation = location;
updateInfo(location);
showMap(location);
}
}
@Override
public void onConnectionSuspended(int i) {
toast("onConnectionSuspended");
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
toast("onConnectionFailed");
}
@Override
public void onMapReady(GoogleMap googleMap) {
map = googleMap;
UiSettings settings = map.getUiSettings();
settings.setZoomControlsEnabled(true);
map.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener(){
@Override
public void onInfoWindowClick(Marker marker) {
toast("info window click...");
}
});
}
}