Google Map Api 활용

Park Jae Hong·2022년 11월 15일
0

Google Map Api 사용 방법은 많은 블로그에 나와 있어서 생략 !
Google Map Api Key 발급 & 적용

Device 위치 정보 Google Map에 띄우기

1. Device 위치 정보 권한 설정

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    ...
     // 대략적인 위치 정보 권한 (3km 이내)
	 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     // 정확한 위치 정보 권한 (50m 이내)
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     ...
</manifest>

manifest 에서 위 2개의 코드를 추가해 준다.

2. build.gradle (app) 추가

dependencies {
	...
	implementation 'com.google.android.gms:play-services-location:21.0.1'
    ...
 }

3. 권한 확인 코드

private val permissionRequest = 99
private var permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

override fun onCreate(savedInstanceState: Bundle?) {
	// 권한이 없다면 권한 요청을 한다.
  	if (!isPermitted()) {
        ActivityCompat.requestPermissions(this, permissions, permissionRequest)
    }
}

private fun isPermitted(): Boolean {
    for (perm in permissions) {
        if (ContextCompat.checkSelfPermission(this, perm)
            != PackageManager.PERMISSION_GRANTED
        ) {
            return false
        }
    }
    return true
}

4. Gps 받아오기

override fun onMapReady(googleMap: GoogleMap) {
    mMap = googleMap
    mMap.setMinZoomPreference(5.0F) // 지도의 초대 크기
    mMap.setMaxZoomPreference(20.0F) // 지도의 최소 크기

    fusedLocationProviderClient =
        LocationServices.getFusedLocationProviderClient(this) //gps 자동으로 받아오기
    setUpdateLocationListener() // 현제 위치 업데이트
    
}

@SuppressLint("MissingPermission")
fun setUpdateLocationListener() {
    val locationRequest =
        LocationRequest.Builder(
        PRIORITY_HIGH_ACCURACY, //높은 정확도
        1000 //1초에 한번씩 GPS 요청
        ).build()
     
     //location 요청 함수 호출 (locationRequest, locationCallback)
     locationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            locationResult.locations.withIndex().forEach {
                setLastLocation(it.value)
            }
        }
    } 

    //location 요청 함수 호출 (locationRequest, locationCallback)
    fusedLocationProviderClient.requestLocationUpdates(
        locationRequest,
        locationCallback,
        Looper.myLooper()
    )
}

fun setLastLocation(location: Location) {
	val myLocation = LatLng(location.latitude, location.longitude)
        
    val markerOptions =
        MarkerOptions()
            .position(myLocation)
            .title("현재 위치")
    
    mMap.addMarker(markerOptions)
    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myLocation, 17.0F)) // 현재 위치, 지도 크기
}

Google Api Marker Custom

1. Marker Icon Image 삽입

  • 아래와 같은 방법으로 Marker의 이미지를 변경할 수 있다.
 val markerOptions =
    MarkerOptions()
        .position(myLocation)
        .title("현재 위치")
        .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_baseline_location_on_24))
  • 단, Vector 이미지를 사용할 경우 아래위 같은 Exception을 뱉는다.
    com.google.maps.api.android.lib6.common.apiexception.b: Failed to decode image. The provided image must be a Bitmap.

  • 아래와 같은 코드로 Vector Image를 BitmapDescriptor 로 변환해서 사용

 // 벡터 이미지 변환
private fun bitmapDescriptorFromVector(context: Context, vectorResId: Int): BitmapDescriptor? {
    return ContextCompat.getDrawable(context, vectorResId)?.run {
        setBounds(0, 0, intrinsicWidth, intrinsicHeight)
        val bitmap =
            Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
        draw(Canvas(bitmap))
        BitmapDescriptorFactory.fromBitmap(bitmap)
    }
}
  • 수정된 코드
val markerOptions =
    MarkerOptions()
        .position(myLocation)
        .title("현재 위치")
        .icon(bitmapDescriptorFromVector(this, R.drawable.ic_baseline_location_on_24))

Marker의 Click Event 정리

  • Marker 를 클릭 했을 경우 마커의 색 변경
  • 다른 Marker 를 클릭 했을 경우 이전 Marker를 선택 해제하고 클릭된 Marker를 선택 (색 변경)
  • Marker 이외에 다른 Click이 일어날 경우 Marker 선택 해제

1. Base Marker와 선택된 Marker 정의 & 현재 선택된 Marker 정보

private var markerState: Marker? = null
private var baseMarker: BitmapDescriptor? = null
private var selectMarker: BitmapDescriptor? = null

override fun onCreate(savedInstanceState: Bundle?) {
	 ...
 	 baseMarker = bitmapDescriptorFromVector(this, R.drawable.ic_baseline_location_on_24)
 	 selectMarker = bitmapDescriptorFromVector(this, R.drawable.ic_baseline_location_click)
     ...
     
     setMarkerClickListener()
}

2. Marker ClickListener 정의

  • Marker 를 클릭 했을 경우 마커의 색 변경
  • 다른 Marker 를 클릭 했을 경우 이전 Marker를 선택 해제하고 클릭된 Marker를 선택 (색 변경)
 private fun setMarkerClickListener() {
    mMap.setOnMarkerClickListener { marker -> 
        if (markerState != null && markerState != marker) {
            clearMarkerClick(checkNotNull(markerState))
            marker.setIcon(selectMarker)
            markerState = marker
        } else {
            marker.setIcon(selectMarker)
            markerState = marker
        }
        // false : 마커 클릭 이벤트의 기본 동작 수행 (클릭시 카메라 이동, title 띄우기 등)
        false
    }
}
private fun clearMarkerClick(marker: Marker) {
    marker.setIcon(baseMarker)
}

3. Map ClickListener 정의

  • Marker 이외에 다른 Click이 일어날 경우 Marker 선택 해제
private fun setMapClickListener() {
    mMap.setOnMapClickListener {
       if (markerState != null) {
            markerState?.setIcon(baseMarker)
            markerState = null
        }
    }
}

추가적으로 알아봐야할 사항

  • 권한 요청을 할 때 RequestCode의 의미
    : 특정한 의미는 없다. 다만 같은 요청을 했을 때 같으면 안된다.
  • 생성된 Marker는 얕은 복사가 되는지
    : 얕은 복사가 이루어 지고, 그 해당 Marker의 주소값이 복사 된다.
  • 선택을 해제하기 위해서 Map ClickListener로 받아서 진행하는데, Marker 클릭 여부를 직접 감지 할수 없는지
    (Marker 의 정보를 Obverser 하는 방식 ?)

전체 코드

package boostcamp.search.googlemapexample

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.location.Location
import android.os.Bundle
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import boostcamp.search.googlemapexample.databinding.ActivityMapsBinding
import com.google.android.gms.location.*
import com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
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.model.*

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private val permissionRequest = 99
    private lateinit var mMap: GoogleMap
    private lateinit var binding: ActivityMapsBinding
    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    lateinit var locationCallback: LocationCallback
    private var permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
    private var markerState: Marker? = null
    private var baseMarker: BitmapDescriptor? = null
    private var selectMarker: BitmapDescriptor? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMapsBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        if (!isPermitted()) {
            ActivityCompat.requestPermissions(this, permissions, permissionRequest)
        }
        baseMarker = bitmapDescriptorFromVector(this, R.drawable.ic_baseline_location_on_24)
        selectMarker = bitmapDescriptorFromVector(this, R.drawable.ic_baseline_location_click)

    }

    private fun isPermitted(): Boolean {
        for (perm in permissions) {
            if (ContextCompat.checkSelfPermission(this, perm)
                != PackageManager.PERMISSION_GRANTED
            ) {
                return false
            }
        }
        return true
    }

    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        mMap.setMinZoomPreference(5.0F)
        mMap.setMaxZoomPreference(20.0F)

        fusedLocationProviderClient =
            LocationServices.getFusedLocationProviderClient(this) //gps 자동으로 받아오기
        setUpdateLocationListener()
        setMarkerClickListener()
        setMapClickListener()

    }

    @SuppressLint("MissingPermission")
    fun setUpdateLocationListener() {
        val locationRequest =
            LocationRequest.Builder(PRIORITY_HIGH_ACCURACY, 50000).build()

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult.locations.withIndex().forEach {
                    setLastLocation(it.value)
                }
            }
        }

        //location 요청 함수 호출 (locationRequest, locationCallback)
        fusedLocationProviderClient.requestLocationUpdates(
            locationRequest,
            locationCallback,
            Looper.myLooper()
        )
    }

    fun setLastLocation(location: Location) {
        val myLocation = LatLng(location.latitude, location.longitude)
        val myLocation2 = LatLng(location.latitude + 0.001, location.longitude + 0.001)
        val markerOptions =
            MarkerOptions()
                .position(myLocation)
                .title("현재 위치")
                .icon(baseMarker)

        val markerOptions2 =
            MarkerOptions()
                .position(myLocation2)
                .title("현재 위치")
                .icon(baseMarker)

        mMap.addMarker(markerOptions)
        mMap.addMarker(markerOptions2)
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myLocation, 17.0F))

    }

    private fun setMarkerClickListener() {
        mMap.setOnMarkerClickListener { marker ->
            if (markerState != null && markerState != marker) {
                clearMarkerClick(checkNotNull(markerState))
                marker.setIcon(selectMarker)
                markerState = marker
            } else {
                marker.setIcon(selectMarker)
                markerState = marker
            }
            // 마커 클릭 이벤트의 기본 동작 수행 (클릭시 카메라 이동, title 띄우기 등)
            false
        }
    }

    private fun setMapClickListener() {
        mMap.setOnMapClickListener {
            if (markerState != null) {
                markerState?.setIcon(baseMarker)
                markerState = null
            }
        }
    }

    private fun clearMarkerClick(marker: Marker) {
        marker.setIcon(baseMarker)
    }

    // 벡터 이미지 변환
    private fun bitmapDescriptorFromVector(context: Context, vectorResId: Int): BitmapDescriptor? {
        return ContextCompat.getDrawable(context, vectorResId)?.run {
            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
            val bitmap =
                Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
            draw(Canvas(bitmap))
            BitmapDescriptorFactory.fromBitmap(bitmap)
        }
    }
}
profile
The people who are crazy enough to think they can change the world are the ones who do. -Steve Jobs-

0개의 댓글