์๋ ํ์ธ์ ๐
์ค๋์ ์ ๋ฒ ํฌ์คํธ์ ์ด์ด์
์นด์นด์ค ๋งต API๋ฅผ ํ์ฉํ์ฌ ์ง๋์ PIN์ ์ฐ๋ ๋ฒ์ ํฌ์คํ ํด๋ณด๋ ค ํฉ๋๋ค!
์นด์นด์ค ๋งต API์ ๊ธฐ๋ณธ ์ค์ ๋ฐ API๋ฅผ ํ์ฉํ์ฌ ์ง๋๋ฅผ ๋์ฐ๋ ๋ฐฉ๋ฒ์
์ด์ ํฌ์คํ ์ ์ฐธ๊ณ ํด์ฃผ์ธ์ ๐
[Kotlin][Android] ์๋๋ก์ด๋ ์นด์นด์ค๋งต API ์ฌ์ฉํ๊ธฐ - ์นด์นด์ค๋งต ํ๋ฉด์ ๋์ฐ๊ธฐ
์๋ ํ์ธ์ ์ค๋์ ์นด์นด์ค ๋งต API๋ฅผ ํ์ฉํ๋ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ๋ํด ํฌ์คํ ํด๋ณด๋ ค ํฉ๋๋ค. ์์น์ ๋ณด๋ฅผ ์ด์ฉํ ์ ์๋ API๋ ๋ง์ง๋ง ์ ๋ ๊ฐ์ธ์ ์ผ๋ก ์นด์นด์ค ๋งต์ด ๋ฌธ์๊ฐ ์ ์ ๋ฆฌ๋์ด์๋ค๊ณ ์๊ฐ
devyo-111commit.tistory.com

์นด์นด์ค ๋งต API์์๋ ํค์๋์ ๋ฐ๋ฅธ ์์น ์ ๋ณด๋ฅผ ์ ๋ฌํด์ฃผ๋ API๊ฐ ์กด์ฌํฉ๋๋ค.
์์ธํ ๋ฌธ์๋
https://apis.map.kakao.com/android/guide/#urlscheme_search
์ ๋งํฌ๋ฅผ ํตํด ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ค๋ ํฌ์คํ ์์ ์ฌ์ฉํ API๋ ์๋์ ๊ฐ์ต๋๋ค.
๋ํ ํ์ฌ ํฌ์คํ ์์ ์ฌ์ฉํ ์ง๋๋ ์นด์นด์ค ๋งต(๋ค์ ์ง๋)์ผ๋ก ํด๋น ์ง๋๋ ๋ด๋ถ์ ์ผ๋ก WCONGNAMULํ์์ผ๋ก
์ขํ๋ฅผ ํ์ํ๋ค๊ณ ํฉ๋๋ค. ์ API๋ฅผ ํตํด ์ขํ๋ฅผ ์ ๋ฌ๋ฐ์ ์ WGS84 ํ์์ ์ขํ๊ฐ์ ์ ๋ฌ๋ฐ์ผ๋ฏ๋ก
์ ์ขํ๊ฐ์ ๋ณํํด์ค API๋ ํจ๊ป ์ฌ์ฉํ๋ ค ํฉ๋๋ค.
์์ธํ ๋ฌธ์๋ ์๋ ๋งํฌ๋ฅผ ํตํด ํ์ธ ๊ฐ๋ฅํฉ๋๋ค ๐คฉ
Kakao Developers
์นด์นด์ค API๋ฅผ ํ์ฉํ์ฌ ๋ค์ํ ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํด๋ณด์ธ์. ์นด์นด์ค ๋ก๊ทธ์ธ, ๋ฉ์์ง ๋ณด๋ด๊ธฐ, ์น๊ตฌ API, ์ธ๊ณต์ง๋ฅ API ๋ฑ์ ์ ๊ณตํฉ๋๋ค.
developers.kakao.com
๊ทธ๋ผ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์ด๋ป๊ฒ ์ง์ผํ๋์ง ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค!
1. REST API์ ํธ์ถํ๊ธฐ ์ํ ๋ชจ๋ธ ์์ฑํ๊ธฐ
์์น ๊ฒ์ ๊ฒฐ๊ณผ JSON ๋ฐ ๊ทธ์ ๋ฐ๋ฅธ Model class
{
"documents": [
{
"address_name": "์์ธ ์ค๊ตฌ ํํ๋ก1๊ฐ 31",
"category_group_code": "PO3",
"category_group_name": "๊ณต๊ณต๊ธฐ๊ด",
"category_name": "์ฌํ,๊ณต๊ณต๊ธฐ๊ด > ์ง๋ฐฉํ์ ๊ธฐ๊ด > ์์ฒญ > ํน๋ณ์์ฒญ",
"distance": "",
"id": "8430129",
"phone": "02-120",
"place_name": "์์ธํน๋ณ์์ฒญ",
"place_url": "http://place.map.kakao.com/8430129",
"road_address_name": "์์ธ ์ค๊ตฌ ์ธ์ข
๋๋ก 110",
"x": "126.978652258823",
"y": "37.56682420267543"
}, ... // ์๋ต
],
"meta": {
"is_end": false,
"pageable_count": 45,
"same_name": {
"keyword": "์์ธ์์ฒญ",
"region": [],
"selected_region": ""
},
"total_count": 342
}
}
data class KakaoPoiModel(
val documents : MutableList<Documents> ,
val meta : Meta
)
data class Documents(
@SerializedName("address_name")
val addressName : String ,
@SerializedName("category_group_code")
val categoryGroupCode : String ,
@SerializedName("category_group_name")
val categoryGroupName : String ,
@SerializedName("category_name")
val categoryName : String ,
val distance : String ,
val id : String ,
val phone : String ,
@SerializedName("place_name")
val placeName : String ,
@SerializedName("place_url")
val placeURL : String ,
@SerializedName("road_address_name")
val roadAddressName : String ,
@SerializedName("x")
var longitude : String? ,
@SerializedName("y")
var latitude : String?
)
data class Meta(
@SerializedName("is_end")
val isEnd : Boolean ,
@SerializedName("pageable_count")
val pageableCount : Int ,
@SerializedName("same_name")
val sameName : SameName ,
@SerializedName("total_count")
val totalCount : Int
)
์ขํ๊ณ ๋ณํ ๊ฒฐ๊ณผ JSON ๋ฐ ๊ทธ์ ๋ฐ๋ฅธ Model Class
{
"meta": {
"total_count": 1
},
"documents": [
{
"x": 922606.0,
"y": 1197404.0
}
]
}
data class WcongModel(
val meta : WcongMeta ,
val documents : MutableList<WcongDocuments>
)
data class WcongMeta(
@SerializedName("total_count")
val totalCount : String
)
data class WcongDocuments(
@SerializedName("x")
val longitude: String ,
@SerializedName("y")
val latitude: String
)
REST Interface
import com.lee.beachcongetion.common.*
import com.lee.beachcongetion.data.retrofit.model.kakao.KakaoPoiModel
import com.lee.beachcongetion.data.retrofit.model.kakao.WcongModel
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
private const val KAKAO_BASE_URL = "https://dapi.kakao.com/"
private const val KAKAO_POI_URL = "v2/local/search/keyword.json"
private const val KAKAO_CONVERT_WCONG = "v2/local/geo/transcoord.json?&output_coord=WCONGNAMUL"
private const val AUTHORIZATION = "Authorization"
interface KakaoPoiService {
@GET(KAKAO_POI_URL) // ๊ฒ์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๋ Rest ํจ์
suspend fun getPOIList(
@Header(AUTHORIZATION) key : String ,
@Query("query") keyword : String ,
) : Response<KakaoPoiModel>
@GET(KAKAO_CONVERT_WCONG) // ์ขํ๊ณ๋ฅผ ๋ณ๊ฒฝํ๋ Rest ํจ์
suspend fun getWcongPoint(
@Header(AUTHORIZATION) key : String ,
@Query("x") longitude : String ,
@Query("y") latitude : String
) : Response<WcongModel>
}
class KakaoPoiInstance {
companion object{
private lateinit var instance : KakaoPoiService
fun getInstance() : KakaoPoiService {
if(!::instance.isInitialized){
val retrofit = Retrofit.Builder()
.baseUrl(KAKAO_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
instance = retrofit.create(KakaoPoiService::class.java)
}
return instance
}
}
}
2. Acitivy์์ ์ฌ์ฉํ๊ธฐ
package com.lee.beachcongetion.ui
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.lee.beachcongetion.R
import com.lee.beachcongetion.data.retrofit.KakaoPoiInstance
import com.lee.beachcongetion.databinding.ActivityTestBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.daum.mf.map.api.MapPOIItem
import net.daum.mf.map.api.MapPoint
import net.daum.mf.map.api.MapView
class TestActivity : AppCompatActivity(){
private lateinit var binding : ActivityTestBinding
private lateinit var mMap : MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
mMap = MapView(this@TestActivity)
binding.mapView.addView(mMap)// Map ํ์
addListeners()
}
/**
๋ฆฌ์ค๋ ๋ฑ๋ก ํจ์
**/
private fun addListeners() {
with(binding){
searchButton.setOnClickListener {
mMap.removeAllPOIItems() // ์ด์ ์ POI Item์ด ์กด์ฌํ๋ค๋ฉด ๋ชจ๋ ์ง์ด๋ค.
if(searchEditText.text.isNotEmpty()){ // ๊ฒ์์ฐฝ์ด ๋น์ด์์ง ์๋ค๋ฉด ํจ์ ์คํ
CoroutineScope(Dispatchers.IO).launch { // ํจ์๊ฐ suspend์ด๊ธฐ ๋๋ฌธ์ coroutine์ ํตํด ์ฒ๋ฆฌ
val response = KakaoPoiInstance.getInstance().getPOIList(getString(R.string.kakao_api_key) , searchEditText.text.toString())
if(response.isSuccessful){ // ํค์๋๋ฅผ ํตํ POI์ ๋ณด ๊ฒ์ ์ฑ๊ณต์
val poiList = response.body()?.documents
if(poiList!!.isEmpty()){ // ์ฑ๊ณตํ์ผ๋ ํด๋นํ๋ ์์น์ ๋ณด๊ฐ ํ๋๋ ์์๋์ ๋ํ ์์ธ ์ฒ๋ฆฌ
CoroutineScope(Dispatchers.Main).launch {
Toast.makeText(this@TestActivity , "ํด๋นํ๋ ์์น๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค!" , Toast.LENGTH_SHORT).show()
}
} else { // ๋ฆฌ์คํธ ๋ฐ๊ธฐ ์ฑ๊ณต
val mapPoiItems = ArrayList<MapPOIItem>() // Marker๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ArrayList
poiList.forEach { // ํค์๋์ ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ๋ฆฌ์คํธ์ ์ ๋ฌ๋ฐ์ ๋ชจ๋ ๋ง์ปค๋ก ํ์ํ๊ธฐ ์ํ ๋ถ๋ถ
val marker = MapPOIItem()
val wcongResponse = KakaoPoiInstance.getInstance().getWcongPoint(getString(R.string.kakao_api_key) ,it.longitude!! , it.latitude!!)
if(wcongResponse.isSuccessful){ // WCONG์ขํ๊ณ๋ก ๋ณํ ์ฑ๊ณต์
val wcongModel = wcongResponse.body()?.documents
/**
์ ๋ฌ๋ฐ์ ์ขํ๊ณ๋ WSG84์ขํ๊ณ๋ก ๋ณํ๋ ์ขํ ๊ฐ์ผ๋ก data model์ ๊ฐ์ ๋ณ๊ฒฝํด์ค
๋ณํ๋ ์ขํ๊ณ ๊ฐ์ Listํ์์ผ๋ก ์ ๋ฌ๋์ง๋ง ๊ฐ์ ํ๋๋ฐ์ ์กด์ฌํ์ง ์์
**/
it.longitude = wcongModel?.get(0)?.longitude
it.latitude = wcongModel?.get(0)?.latitude
with(marker){ // marker (POI Item)์ ์ฑ์ ํด์ฃผ๋ ๋ถ๋ถ
itemName = it.placeName
tag = 0
mapPoint = MapPoint.mapPointWithWCONGCoord(it.longitude?.toDouble()!! , it.latitude?.toDouble()!!)
markerType = MapPOIItem.MarkerType.BluePin
selectedMarkerType = MapPOIItem.MarkerType.RedPin
}
mapPoiItems.add(marker) // ์ค์ ๋ marker๋ฅผ List์ ์ ์ฅ
} else { // ์ขํ ๋ณ๊ฒฝ์ ์คํจํ์๋์ ๋ํ ์์ธ์ฒ๋ฆฌ
Toast.makeText(this@TestActivity , "์ ์์ ์ผ๋ก ์ขํ๋ฅผ ๋ณ๊ฒฝํ์ง ๋ชปํ์ต๋๋ค!" , Toast.LENGTH_SHORT).show()
return@launch
}
}
CoroutineScope(Dispatchers.Main).launch {
// ๋ชจ๋ ๊ณผ์ ์ด ์ฑ๊ณต์ coroutine์ ํตํด map์ ๋ฐฐ์ด์ ์ถ๊ฐ๋ marker๋ฅผ ์ถ๊ฐํด์ค๋ค.
mapPoiItems.forEach {
mMap.addPOIItem(it)
}
mMap.setZoomLevel(17 , true) // marker์ถ๊ฐ ํ ์ง๋ Zoom
}
}
} else { // ํค์๋ ๊ฒ์์ ๋ฐ๋ฅธ ์์น์ ๋ณด๋ฅผ ์ ์์ ์ผ๋ก ๋ถ๋ฌ์ค์ง ๋ชปํ์๋
Toast.makeText(this@TestActivity , "์๋ฒ์์ ์ ์์ ์ผ๋ก ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์ต๋๋ค!" , Toast.LENGTH_SHORT).show()
}
}
}
}
}
}
}
์ฝ๋๋ ์์ ๊ฐ์ผ๋ฉฐ ๋์์ ํค์๋ ๊ฒ์ ์์ ํด๋นํ๋ ๋ชจ๋ pin์ ์ง๋์ ํ์ํ๋ ์ํ ์ฝ๋์ ๋๋ค.
ํค์๋ ๊ฒ์์ผ๋ก ๋ฐ์์ค๋ ์ ๋ณด๋ ํด๋น ํค์๋์ ์ ํ๋์ ๋ฐ๋ผ ๋ฐฐ์ด๋ก ์ ๋ฌ๋ฐ์ต๋๋ค.
(์ฆ , ๊ฐ์ฅ ์ฒซ ๋ฒ์งธ ๋ฐฐ์ด ๊ฐ์ด ๊ฐ์ฅ ๊ฐ๊น๊ณ ์ ํํ ์ ๋ณด)
์ ์ํ ์ฝ๋์์ ์ฌ์ฉํ map๊ด๋ จ ํจ์ ์ธ์ ์ถ๊ฐ์ ์ธ ํจ์๋
์นด์นด์ค ๋งต api ๊ณต์๋ฌธ์์ ์์ธํ๊ฒ ์ค๋ช ๋์ด ์์ผ๋ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค. ๐ค
๋ง์ง๋ง์ผ๋ก ์คํํ๋ฉด๊ณผ ํจ๊ป ์ค๋์ ํฌ์คํ ์ ๋ง์น๊ฒ ์ต๋๋ค.
์ค๋๋ ์ฆ์ฝํ์ธ์ :)