๊พธ์ค€ํ•จ์ด ์ง„๋ฆฌ๋‹ค!!

์–ด์ œ๋ณด๋‹ค ๋ฐœ์ „ํ•œ ์˜ค๋Š˜์ด ๋˜๊ณ ํ”ˆ ๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์˜ ๋ธ”๋กœ๊ทธ

Android/Kotlin

[Kotlin][Android] ์•ˆ๋“œ๋กœ์ด๋“œ ์นด์นด์˜ค๋งต API ์‚ฌ์šฉํ•˜๊ธฐ - POI์ •๋ณด ํ™œ์šฉํ•˜์—ฌ ๋งต์— PIN ์ƒ์„ฑํ•˜๊ธฐ

๋ށ์š” 2022. 11. 28. 13:54

์•ˆ๋…•ํ•˜์„ธ์š” ๐Ÿ‘‹

์˜ค๋Š˜์€ ์ €๋ฒˆ ํฌ์ŠคํŠธ์— ์ด์–ด์„œ 

์นด์นด์˜ค ๋งต 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๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์นด์นด์˜ค๋งต ๊ฒ€์ƒ‰ํ•˜๊ธฐ URL

๋˜ํ•œ ํ˜„์žฌ ํฌ์ŠคํŒ…์—์„œ ์‚ฌ์šฉํ•  ์ง€๋„๋Š” ์นด์นด์˜ค ๋งต(๋‹ค์Œ ์ง€๋„)์œผ๋กœ ํ•ด๋‹น ์ง€๋„๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ 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 ๊ณต์‹๋ฌธ์„œ์— ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…๋˜์–ด ์žˆ์œผ๋‹ˆ ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๐Ÿค”

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹คํ–‰ํ™”๋ฉด๊ณผ ํ•จ๊ป˜ ์˜ค๋Š˜์˜ ํฌ์ŠคํŒ…์„ ๋งˆ์น˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์˜ค๋Š˜๋„ ์ฆ์ฝ”ํ•˜์„ธ์š” :)