구글 Maps API in Kotlin

고진호·2023년 7월 19일

초기 설정

1.console.cloud.google.com 에서 새 프로젝트를 생성하고, Google Maps API를 활성화

2.API키 보호에 있는 패키지 이름->Android 앱의 실제 패키지 이름

SHA-1 인증키 디지털지문 -> Android 앱 하단의 Terminal에 gradlew signingReport
입력 후 Ctrl+Enter 입력->SHA1 태그 확인

3.API키 생성하고 Android앱의 AndroidManifest.xml 파일에 추가

	 <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="발급받은 API 키" />

4.앱 수준의 build.gradle에 라이브러리 추가

implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'

5.sdk tools에서 google play services 패키지 설치

6.activity_main.xml 파일에 fragment 추가

<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/map_fragment"
        android:name="com.google.android.gms.maps.SupportMapFragment"/>

7.MainActivity에 구글 맵 띄우는 코드 작성

MapsInitializer.initialize(this,MapsInitializer.Renderer.LATEST,null)
						   context,렌더러의 버전 설정,콜백함수

현재 내 위치 표시

1.FusedLocationProviderClient

https://developers.google.com/maps/documentation/android-sdk/current-place-tutorial?hl=ko#kotlin_3

Android앱의 AndroidManifest.xml 파일에 추가(위치정보 권한)

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

build.gradle에 추가

implementation 'com.android.volley:volley:1.1.1'
implementation 'com.google.android.libraries.places:places:2.4.0'
placesClient와 fusedLocationProviderClient 객체 선언후 getMapAsync를 이용해 비동기적으로 맵 로드
Places.initialize(applicationContext,getString(R.string.maps_api_key))
        placesClient=Places.createClient(this)

        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map_fragment) as SupportMapFragment?
        mapFragment?.getMapAsync(this)

placesClient->Google Places API와 통신하는데 사용
fusedLocationProviderClient->위치 정보를 가져오는데 사용

권한을 허가받을 getLocationPermission() 메서드 구현

    fun getLocationPermission() {
        if (ContextCompat.checkSelfPermission(
                this.applicationContext,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            locationPermission = true
        } else {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                1
            )
        }
    }

onMapReady 메서드에서 사용할 getDeviceLocation() 구현

     fun getDeviceLocation(){
        try{
            if(locationPermission){
                val locationResult=fusedLocationProviderClient.lastLocation
                locationResult.addOnCompleteListener(this){
                //it:Task<Location!>을 받는다
                    if(it.isSuccessful){
                        lastknownLocation=it.result
                        //Task가 성공적이면 lastknownLocation에 그 결과를 넣는다.
                        if(lastknownLocation!=null){
                           	 map?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(lastknownLocation!!.latitude,lastknownLocation!!.longitude),DEFAULT_ZOOM))
                        }else{
                            map?.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultLatLng,DEFAULT_ZOOM))
                            map?.uiSettings?.isMyLocationButtonEnabled=false
                        }
                        //lastknownLocation이 null값이 아니면 그 경,위도 값으로 카메라를 옮긴다.
                        //lastknownLocation null값이면 정해놓은 defaultLatLng값으로 카메라를 옮긴다. DEFAULT_ZOOM->15.0F(15배 줌)
                    }

                }
            }
        }catch (e: SecurityException) {
            Log.e("Exception : %s", e.message, e)
        }
    }

onMapReady 메서드에서 사용할 updateLocationUI() 구현

fun updateLocationUI(){
        if(map==null){
            return
        }
        try{
            if(locationPermission){
                map?.isMyLocationEnabled=true
                map?.uiSettings?.isMyLocationButtonEnabled=true
                map?.uiSettings?.isZoomControlsEnabled = true
                //1.내 위치를 지도상에 표시
               	//2.지도에 위치버튼 활성화
                //3.줌컨트롤 활성화(확대,축소)
            }else{
                map?.isBuildingsEnabled=false
                map?.uiSettings?.isMyLocationButtonEnabled=false
                map?.uiSettings?.isZoomControlsEnabled = true
                //1.건물 지도상에 표시x 
                //2.위치버튼 비활성화
                lastknownLocation=null
                getLocationPermission()
               	//마지막 위치정보 null로 설정하고 권한 요청
             
            }
        }catch (e: SecurityException){
            Log.e("Exception : %s",e.message,e)
        }
    }

onRequestPermissionsResult 메서드 오버라이딩

//권한을 거부하거나 허용하면 호출되는 메서드
override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        locationPermission=false
        when(requestCode){
            1->{
                if(grantResults.isNotEmpty() && grantResults[0]==PackageManager.PERMISSION_GRANTED)
                {
                    locationPermission=true
                    getDeviceLocation()
                }
            }
            else ->super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
            updateLocationUI()
    }

onMapReady 메서드 오버라이딩

  override fun onMapReady(map: GoogleMap) {
        this.map=map
        map.setMapType(GoogleMap.MAP_TYPE_HYBRID) //구글맵의 타입 지정
        updateLocationUI()
        getDeviceLocation()
    }

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

2.LocationManager

리스너 등록->현재 위치 측정

       val suppoertMapFragment=supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
        //구글 지도 사용 준비가 완료되면 반응하는 리스너를 등록한다.
        suppoertMapFragment.getMapAsync {
            //구글맵 객체를 변수에 담아준다
            mainGoogleMap=it
            //지도의 옵션을 설정한다.
            it.uiSettings.isZoomControlsEnabled=true
            //현재 위치를 표시한다
            it.isMyLocationEnabled=true

            //위치 정보를 관리하는 객체를 가지고 온다.
            val locationManager=getSystemService(LOCATION_SERVICE) as LocationManager

            ///권한 확인
            val a1=ActivityCompat.checkSelfPermission(this@MainActivity,Manifest.permission.ACCESS_FINE_LOCATION)
            val a2=ActivityCompat.checkSelfPermission(this@MainActivity,Manifest.permission.ACCESS_COARSE_LOCATION)
            if(a1==PackageManager.PERMISSION_GRANTED && a2==PackageManager.PERMISSION_GRANTED)
            {
                //현재 저장되어 있는 위치 정보값을 가지고 온다.
                val location1=locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
                val location2 = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)


                //현재 위치를 표시한다.
                // 현재 위치를 표시한다.
                if(location1 != null){
                    setMyLocation(location1)
                } else if(location2 != null){
                    setMyLocation(location2)
                }
                //현재 위치를 측정하여 지도를 갱신한다
                getMyLocation()
            }

        }

setMyLocation() 구현

fun setMyLocation(location: Location){
        //위치 측정을 중단한다.
        if(myLocationListener!=null){
            val locationManager=getSystemService(LOCATION_SERVICE) as LocationManager
            locationManager.removeUpdates(myLocationListener!!)
            myLocationListener=null
        }
        
        val latLng=LatLng(location.latitude,location.longitude)
		//옮길 지도의 경,위도 객체 생성
        
        val cameraUpdate=CameraUpdateFactory.newLatLngZoom(latLng,20f)
  
        mainGoogleMap.animateCamera(cameraUpdate)
        //20배율로 지도를 찍고 있는 카메라 이동
        
        //마커가 여러개 찍히지 않기 위해 제거
        if(myMarker!=null){
            myMarker?.remove()
            myMarker=null
        }
        //현재 위치에 마커를 표시한다.
        val markerOptions=MarkerOptions()
        markerOptions.position(latLng)

        //마커 이미지를 변경한다
//        val markerBitmap=BitmapDescriptorFactory.fromResource(android.R.drawable.ic_menu_mylocation)
//        markerOptions.icon(markerBitmap)
        //기존 마커를 제거한다

       mainGoogleMap.addMarker(markerOptions)

    }

setMyLocation() 구현

    fun getMyLocation(){
        val a1 = ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)
        val a2 = ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
        if(a1 == PackageManager.PERMISSION_GRANTED && a2 == PackageManager.PERMISSION_GRANTED){
//권한 확인 후 두가지 권한이 모두 확인되면, 위치 측정 리스너 등록
            val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager

            // 위치 측정 리스너
            myLocationListener = object : LocationListener {
                override fun onLocationChanged(p0: Location) {
                    setMyLocation(p0)
                    //onLocationChanged에서 위치가 바뀌면 바뀐 위치로 현재 위치를 바꿔준다.
                }
            }

            // 위치 측정 요청
            if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) == true){
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0, 0f,myLocationListener!!)
                //requestLocationUpdates(위치 업데이트 제공자,업데이트 사이의 시간,업데이트 사이의 거리(float),콜백메서드를 정의하는 리스너(여기서는 MyLocationListener))
            }       
        }
    }

두 방법의 차이점

1번은 FusedLocationProviderClient를 사용하여 위치정보를 활성화하고
fusedLocationProviderClient.lastLocation를 통해 마지막으로 알려진 위치를 얻어서 카메라를 이동시킨다.
*구글이 위치정보를 계산하고 앱은 그냥 결과를 사용하는 시스템인 것 같음

2번은 LocationManager를 통해 위치 측정 리스너를 통해 현재 위치를 측정하고 지도에 표시한다
*정보 제공자를 직접 지정해서 정보를 가져오는 방식

FusedLocationProviderClient는 Google Play 서비스의 위치 API의 일부입니다. 이 API는 기기의 GPS,Wi-Fi,휴대폰 기지국 등 다양한 위치 정보 소스를 합쳐서 사용자에게 제공한다.

LocationManager는 Android 프레임워크의 일부로서,위치 정보를 직접 관리하는 역할을 한다. LocationManager는 GPS 프로바이더,네트워크 프로바이더 등 특정 위치 정보 제공자를 사용하여 위치를 가져온다.

GPS,Wi-Fi,휴대폰 기지국 등 다양한 위치 정보 소스를 사용하는 반면에
GPS_PROVIDER/NETWORK_PROVIDER 등 특정 정보 제공자를 사용하여 위치를 가져오기 떄문에
FusedLocationProviderClient가 LocationManager에 비해 상대적으로 더 정확한 위치정보를 제공한다.

FusedLocationProviderClient가 여러가지 소스중에 가장 효율적인것을 선택하기 때문에 배터리소모가 적은 반면에 LocationManager를 사용할 때는 GPS 또는 네트워크를 계속 활성화해야 하는 경우가 있고, 이런 경우에는 배터리 소모가 상대적으로 크다.

FusedLocationProviderClient는 Goole play service의 일부이기 때문에 일부 기기나 일정 지역에서 앱의 사용이 제한된다.
GPS 하드웨어나 네트워크 연결에 의존적이라 GPS하드웨어나 네트워크 연결이 필수적이다.

profile
Kotlin,Java 안드로이드 앱 개발자 지망생

1개의 댓글

comment-user-thumbnail
2023년 7월 19일

뛰어난 글이네요, 감사합니다.

답글 달기