[Android / Kotlin] 네이버 지도 API 사용하기 (2) 마커 표시/좌표를 주소로 변환

Subeen·2023년 3월 22일
0

Android

목록 보기
15/71
post-thumbnail

이전 포스팅에서는 네이버 지도 API를 사용하여 화면에 지도를 표시하고 현재 위치를 표시하는 예제를 만들었다.
이어서 네이버 지도를 클릭했을 때 마커가 표시되는 것과 동시에 해당 주소를 Toast 메시지로 나타내고자 한다.
네이버 지도의 OnMapClick() 이벤트를 통해 좌표 값을 얻을 수 있으며 Geocoder를 사용하여 좌표 값을 주소로 변환할 수 있다.

📍 네이버 지도 클릭 이벤트

setOnMapClickListener() 메서드로 OnMapClickListener를 지정하면 지도에 대한 클릭 이벤트를 받을 수 있다. 지도가 클릭되면 onMapClick() 콜백 메서드가 호출되며 파라미터로 클릭된 지점의 좌표가 전달된다.

  • 지도를 클릭/롱클릭 했을 때 클릭/롱클릭한 지점의 좌표를 토스트 메시지로 띄우기
    override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap

        // 지도가 클릭 되면 onMapClick() 콜백 메서드가 호출 되며, 파라미터로 클릭된 지점의 화면 좌표와 지도 좌표가 전달 된다.
        naverMap.setOnMapClickListener { point, coord ->
            Toast.makeText(
                this, "${coord.latitude}, ${coord.longitude}",
                Toast.LENGTH_SHORT
            ).show()
        }

        // 지도가 롱 클릭 되면 onMapLongClick() 콜백 메서드가 호출 되며, 파라미터로 클릭된 지점의 화면 좌표와 지도 좌표가 전달 된다.
        naverMap.setOnMapLongClickListener { point, coord ->
            Toast.makeText(
                this, "${coord.latitude}, ${coord.longitude}",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

📍 마커 추가

마커는 지도상의 한 지점을 나타내기 위한 오버레이로 지도에서 가장 널리 사용되는 요소이다.
객체를 생성하고 position 속성에 좌표를 지정한 후 map 속성에 지도 객체를 지정하면 마커가 나타난다.

👀 결과 동영상

📌 마커 추가

val marker = Marker()
marker.position = LatLng(37.5670135, 126.9783740)
marker.map = naverMap

📌 마커 삭제

marker.map = null
  • 지도를 클릭했을 때 클릭 된 지점의 좌표에 마커 추가하기
	private val marker = Marker()

    override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap
        
        naverMap.setOnMapClickListener { point, coord ->
            Toast.makeText(
                marker.position = LatLng(coord.latitude, coord.longitude)
                marker.map = naverMap
            ).show()
        }
    }

📍 좌표를 주소로 변환

안드로이드에서는 위도와 경도를 이용해서 주소를 구하는 경우나 역으로 주소를 이용해서 위도와 경도를 구하는 경우 GeoCoding이 사용된다.

👀 결과 동영상

📌 Geocoder
GeoCoding(지오코딩) : 주소를 가지고 위도와 경도를 구한다.
Reverse-GeoCoding(역지오코딩) : 위도와 경도를 가지고 주소를 구한다.

  • 위도, 경도를 주소로 반환 할 때의 반환 값
    • getAddressLine(0) : 전체 주소
    • adminArea : 시, 도
    • locality, subLocality : 구
    • thoroughfare : 동
    • subThoroughfare : 번지
    • featureName : 세부주소
    • postalCode : 우편번호
  • 권한 요청
    • AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  • 지도를 클릭했을 때 클릭 된 지점의 좌표에 대한 주소 구하기
	private val marker = Marker()
	override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap

        // 지도가 클릭 되면 onMapClick() 콜백 메서드가 호출 되며, 파라미터로 클릭된 지점의 화면 좌표와 지도 좌표가 전달 된다.
        naverMap.setOnMapClickListener { point, coord ->
            marker(coord.latitude, coord.longitude)
        }
    }

	// 클릭 된 지점의 좌표에 마커를 추가하는 함수
    private fun marker(latitude: Double, longitude: Double) {
        marker.position = LatLng(latitude, longitude)
        marker.map = naverMap

        getAddress(latitude, longitude)
    }

	// 클릭 된 지점의 좌표에 대한 주소를 구하는 함수
    private fun getAddress(latitude: Double, longitude: Double) {
    	// Geocoder 선언
        val geocoder = Geocoder(applicationContext, Locale.KOREAN)

		// 안드로이드 API 레벨이 33 이상인 경우
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            geocoder.getFromLocation(
                latitude, longitude, 1
            ) { address ->
                if (address.size != 0) {
                	// 반환 값에서 전체 주소만 사용한다.
                    // getAddressLine(0)
                    toast(address[0].getAddressLine(0))
                }
            }
        } else { // API 레벨이 33 미만인 경우 
            val addresses = geocoder.getFromLocation(latitude, longitude, 1)
            if (addresses != null) {
                toast(addresses[0].getAddressLine(0))
            }
        }
    }

    private fun toast(text: String) {
        runOnUiThread {
            Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT).show()
        }
    }

📍 전체 코드

  • activity_map_view.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".naver_map.MapViewActivity">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/map_fragment"
            android:name="com.naver.maps.map.MapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • MapViewActivity.kt
class MapViewActivity : AppCompatActivity(), OnMapReadyCallback {
    private val LOCATION_PERMISSION_REQUEST_CODE = 5000

    private val PERMISSIONS = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    private lateinit var binding: ActivityMapViewBinding
    private lateinit var naverMap: NaverMap
    private lateinit var locationSource: FusedLocationSource
    private val TAG = MapViewActivity::class.simpleName
    private val marker = Marker()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_map_view)

        if (!hasPermission()) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, LOCATION_PERMISSION_REQUEST_CODE)
        } else {
            initMapView()
        }
    }

    private fun initMapView() {
        val fm = supportFragmentManager
        val mapFragment = fm.findFragmentById(R.id.map_fragment) as MapFragment?
            ?: MapFragment.newInstance().also {
                fm.beginTransaction().add(R.id.map_fragment, it).commit()
            }

        mapFragment.getMapAsync(this)
        locationSource = FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE)
    }

    private fun hasPermission(): Boolean {
        for (permission in PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission)
                != PackageManager.PERMISSION_GRANTED
            ) {
                return false
            }
        }
        return true
    }


    override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap
        naverMap.locationSource = locationSource
        naverMap.uiSettings.isLocationButtonEnabled = true
        naverMap.locationTrackingMode = LocationTrackingMode.Follow

        naverMap.setOnMapClickListener { point, coord ->
            marker(coord.latitude, coord.longitude)
        }
    }

    private fun marker(latitude: Double, longitude: Double) {
        marker.position = LatLng(latitude, longitude)
        marker.map = naverMap

        getAddress(latitude, longitude)
    }


    private fun getAddress(latitude: Double, longitude: Double) {
        val geocoder = Geocoder(applicationContext, Locale.KOREAN)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            geocoder.getFromLocation(
                latitude, longitude, 1
            ) { address ->
                if (address.size != 0) {
                    toast(address[0].getAddressLine(0))
                }
            }
        } else {
            val addresses = geocoder.getFromLocation(latitude, longitude, 1)
            if (addresses != null) {
                toast(addresses[0].getAddressLine(0))
            }
        }
    }

    private fun toast(text: String) {
        runOnUiThread {
            Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT).show()
        }
    }

}

클릭 이벤트
마커
GeoCoder

profile
개발 공부 기록 🌱

0개의 댓글