Welcome! Everything is fine.

[Android/study] 네이버 지도② 다중 마커 띄우기, DB에서 위도와 경도 가져와 마커 띄우기 본문

Android

[Android/study] 네이버 지도② 다중 마커 띄우기, DB에서 위도와 경도 가져와 마커 띄우기

개발곰발 2022. 8. 3.
728x90

네이버 지도를 사용하면서 기억하고 싶은 부분을 정리하고자 한다. 아래의 이전 포스팅과 이어지는 내용이다.

 

[Android] 네이버 지도 사용하기, 현재 위치 표시, 마커 띄우기

📌 네이버 지도 사용하기 클라이언트 ID 발급 네이버 지도 SDK를 사용하려면 네이버 클라우드 플랫폼에서 애플리케이션을 등록하고 클라이언트 ID를 발급받아야 한다. NAVER CLOUD PLATFORM cloud computin

3uomlkh.tistory.com

📌 다중 마커 띄우기

마커를 2개 이상 띄우는 가장 단순한 방법은 마커 객체를 2개 만들어서 각각의 좌표와 지도 객체를 지정하는 것이다. 아래와 같은 코드로 마커를 각각 다른 곳에 띄울 수 있다.

Marker marker = new Marker();
marker.setPosition(new LatLng(37.5670135, 126.9783740));
marker.setMap(naverMap);

Marker marker2 = new Marker();
marker2.setPosition(new LatLng(37.563119, 126.969418));
marker2.setMap(naverMap);

마커가 2개가찍힌 모습

그러나 만약 마커를 100개를 찍고자 한다면, 위의 작업을 100번을 반복해야하니 힘들고 비효율적일 것이다. 찍고 싶은 좌표들을 배열로 만들고, 마커 객체도 배열로 만들어 

📌 Retrofit2를 이용하여 DB에서 위도, 경도 가져오기

retrofit2는 HTTP통신을 간편하게 만들어주는 라이브러리로, JSON이나 XML 데이터를 모델 객체로 변환한다. 따라서 JSON, XML을 파싱하는 라이브러리와 파싱 라이브러리에 맞는 converter 역시 필요하다. build.gradle(:app) 파일의 dependencies 안에 아래 코드를 붙여넣어 준다.

implementation 'com.squareup.retrofit2:retrofit:2.0.0'
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

본격적으로 시작하기 전, 먼저 식당의 위도와 경도가 포함된 JSON 파일 데이터를 찾아 DB에 넣는다. 데이터는 서울 열린데이터광장에서 서울시 지정·인증업소 현황 데이터를 사용하였다.

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

닷홈의 무료 호스팅을 이용하여 다음과 같이 DB에 데이터를 넣었고, php로 통신하였다.

이렇게 JSON 형식으로 나오는 데이터이다. 데이터의 구조를 정확하게 파악해야 데이터를 가져올 때 헷갈리지 않는다.

필요한 인터페이스와 클래스는 다음과 같다.

retrofit2를 이용하여 통신하는 방법은 아래 블로그에서 도움을 많이 받았다.

 

Retrofit2로 데이터 받아 와서 RecyclerView 만들기

서버에서 Json형태로 된 데이터를 받아와서 RecyclerView를 만들어보았었는데, 3일간 진짜 밥잠똥이거만 했다고 해도 과언이 아닐정도로 끙끙거리며 만들었다. 2년 내내 안드로이드를 얕게 붙잡고

purple-wood-lights.tistory.com

NaverMapRequest.java

여기서 NaverMapRequest는 retrofit2 객체를 만들기 위한 클래스이다. BASE_URL에는 기본 주소를 적는다. 만약 전체 주소가http://myhosting.dothome.co.kr/restaurant.php 라면, http://myhosting.dothome.co.kr/ 는 기본 주소이고 restaurant.php 는 상세 주소가 된다.

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class NaverMapRequest {
    // Base URL
    public static String BASE_URL = "http://myhosting.dothome.co.kr/";

    private static Retrofit retrofit;
    public static Retrofit getClient(){

        if(retrofit == null){
            retrofit = new Retrofit.Builder() // retrofit 객체 생성
                    .baseUrl(BASE_URL) // BASE_URL로 통신
                    .addConverterFactory(GsonConverterFactory.create()) // gson-converter로 데이터 parsing
                    .build();
        }
        return retrofit;
    }
}

NaverMapItem.java

NaverMapItem.java와 NaverMapData.java는 데이터를 받아오는 클래스이다. 여기서 데이터 구조를 잘 파악해야하는데, JSON 형태의 데이터를 다시 한 번 보면 {"A" : [{"가":"a", "나":"b", "다":"c"}, {"가":"a", "나":"b", "다":"c"}...]} 와 같은 구조로 되어있다. 겉에서부터 안으로  중괄호  → 대괄호 → 중괄호 순으로 들어간다.

NaverMapItem.java는 겉에 있는 중괄호의 데이터 클래스로, 노란색 형광펜으로 밑줄 친 "MAPSTOREINFO"를 가리킨다. 또한  public List<NaverMapData> MAPSTOREINFO; 에서 NaverMapData가 아래의 NaverMapData.java를 가리킨다. 즉 "MAPSTOREINFO"에 리스트 형태로 "serialNum, storeName, storeCategory, storeLat, storeLnt, storePhonenum, storeAddr, storeMenu"가 들어있는 것이다.

import java.util.List;

public class NaverMapItem {
    @SerializedName("MAPSTOREINFO")
    public List<NaverMapData> MAPSTOREINFO;
}

NaverMapData.java

여기서는 데이터를 받아올 클래스를 설정한다. 위쪽 코드에 있는 public List<NaverMapData> MAPSTOREINFO; 에서 <>안에 있는 NaverMapData가 아래 클래스를 말한다. 앞서 설명한대로 "MAPSTOREINFO"에 리스트 형태로 쭉 들어간다. 여기서 @SerializedName("serialNum") 처럼 괄호 안에 들어가 있는 텍스트는 php 파일과 일치해야 정상적으로 통신이 되므로 주의해야한다.

import com.google.gson.annotations.SerializedName;

public class NaverMapData {
    @SerializedName("serialNum")
    private int serialNum;
    @SerializedName("storeName") // php 파일과 이름이 같아야 함.
    private String storeName;
    @SerializedName("storeCategory")
    private String storeCategory;
    @SerializedName("storeLat")
    private double storeLat;
    @SerializedName("storeLnt")
    private double storeLnt;
    @SerializedName("storePhonenum")
    private String storePhonenum;
    @SerializedName("storeAddr")
    private String storeAddr;
    @SerializedName("storeMenu")
    private String storeMenu;

    public int getSerialNum() {
        return serialNum;
    }

    public String getStoreName() {
        return storeName;
    }

    public String getStoreCategory() {
        return storeCategory;
    }

    public double getStoreLat() {
        return storeLat;
    }

    public double getStoreLnt() {
        return storeLnt;
    }

    public String getStorePhonenum() {
        return storePhonenum;
    }

    public String getStoreAddr() {
        return storeAddr;
    }

    public String getStoreMenu() {
        return storeMenu;
    }
}

NaverMapApiInterface.java

항상 헷갈리는 인터페이스..인터페이스란 클래스가 반드시 구현해야할 행동을 지정하는데 사용되는 일종의 추상클래스이다. 사실 바로 이해되지는 않아서 더 검색해보며 알아가는 것이 좋을 것 같다. 아래 블로그 글을 보고 조금 더 잘 이해할 수 있었다.

 

안드로이드로 배우는 자바의 인터페이스 (콜백, 리스너, 옵저버) | 찰스의 안드로이드

Before diving into interface 초보 자바 개발자 분들의 단골로 하는 질문이 있습니다. "리스너(콜백)가 뭐에요?" 또는 "인터페이스가 뭔지 모르겠어요!!" 자바의 인터페이스에 대한 개념없이 리스너를 이

www.charlezz.com

여기서는 @GET, @POST, @PUT, @DELETE, @HEAD 총 5개의 Anotation중 @GET을 사용하였다. 괄호 안에 BASE_URL을 뺀 나머지 상세 주소인 VeganMapStoreFi.php 를 적어주면 된다. 그러면 서버에 통신 메서드를 가지고 GET 요청을 한 것이다. Call<NaverMapItem> getMapData(); 로 데이터를 NaverMapItem 형태로 담기로 정한다.이제 액티비티에서 getMapData() 메서드를 이용하여 데이터를 가져다가 써야한다.

 

import retrofit2.Call;
import retrofit2.http.GET;

public interface NaverMapApiInterface {
    @GET("VeganMapStoreFi.php")
    Call<NaverMapItem> getMapData();
}

MapActivity.java

이제 MapActivity.java로 가서 데이터를 받아온다. 먼저 데이터를 담을 NaverMapItem과 List<NaverMapData>의 객체를 생성한다.

private NaverMapItem naverMapList;
private List<NaverMapData> naverMapInfo;

onMapReady에 다음과 같은 내용을 작성한다.DB에 있는 식당 주소를 get(1)로 1번째 행의 주소를 불러와(getStroeAddr) 토스트로 띄우는 코드이다.

        @Override
        public void onMapReady (@NonNull NaverMap naverMap){
            Log.d(TAG, "onMapReady");
			
            // 클라이언트 객체 생성
            NaverMapApiInterface naverMapApiInterface = NaverMapRequest.getClient().create(NaverMapApiInterface.class);
            // 응답을 받을 콜백 구현
            Call<NaverMapItem> call = naverMapApiInterface.getMapData();
            // 클라이언트 객체가 제공하는 enqueue로 통신에 대한 요청, 응답 처리 방법 명시
            call.enqueue(new Callback<NaverMapItem>() {
                             @Override
                             public void onResponse(Call<NaverMapItem> call, Response<NaverMapItem> response) { // 통신 성공시
                                 naverMapList = response.body(); // naverMapList에 요청에 대한 응답 결과 저장
                                 naverMapInfo = naverMapList.MAPSTOREINFO;
                                 
                                 // 토스트로 첫번째 행의 식당주소(StoreAddr) 띄우기
                                 Toast.makeText(MapActivity.this, naverMapInfo.get(1).getStoreAddr(), Toast.LENGTH_SHORT).show();

                             }

                             @Override
                             public void onFailure(Call<NaverMapItem> call, Throwable t) { // 통신 실패시

                             }
                         });

            mNaverMap = naverMap;
            mNaverMap.setLocationSource(mLocationSource);

            ActivityCompat.requestPermissions(this, PERMISSION, PERMISSION_REQUEST_CODE);
        }

DB 1번째 행 정보
DB 1번째 행 식당 주소가 출력된 모습

우리는 마커를 찍는 것이 목표니까 onReasponse내의 부분만 바꿔보았다. DB에서 무사히 위도와 경도를 가져와 마커를 생성하긴 했지만, 이 DB에 있는 800여개의 위도와 경도를 한꺼번에 가져올 수 있는 방법이 필요하다. 이 과정을 get 괄호 안의 숫자만 바꿔가며 800번 반복하면 모두 가져올 수 있겠지만, 그것은 너무 비효율적이다.

 @Override
 public void onResponse(Call<NaverMapItem> call, Response<NaverMapItem> response) {
     naverMapList = response.body();
     naverMapInfo = naverMapList.MAPSTOREINFO;
     
     // 마커 객체 생성
     Marker marker = new Marker();
     
     // DB의 첫번째 행 식당의 위도, 경도 값 가져와 변수에 넣기
     double lat = naverMapInfo.get(0).getStoreLat();
     double lnt = naverMapInfo.get(0).getStoreLnt();
     
     // 가져온 위도, 경도 값으로 position 세팅
     marker.setPosition(new LatLng(lat, lnt));
     marker.setMap(naverMap);

 }

DB 0번쨰 행 정보
DB 0번째 행 식당 위도, 경도에 따라 마커가 띄워진 모습

반복문을 통해 해결할 수 있었다. 단, Marker 배열을 naverMapInfo.size() 만큼 만들고 변수 i도 그만큼 돌려가며 계속 새로운 마커를 만들 수 있도록 한다. 제대로 마커가 뜨지않는다면 마커를 만들고, position과 map 속성을 설정하는 것, get(i)로 차례대로 위도와 경도를 변수에 할당하는 것을 제대로 했는지 점검한다. 또한 그 아래에 마커마다 setOnClickListener()를 만들어서 마커가 눌릴 때 일어나는 이벤트를 처리하도록 했다. 지금은 간단하게 토스트만을 띄웠지만, 추후에 해당 가게의 이미지와 상세 정보가 뜨도록 할 예정이다.

 @Override
 public void onResponse(Call<NaverMapItem> call, Response<NaverMapItem> response) {
     naverMapList = response.body();
     naverMapInfo = naverMapList.MAPSTOREINFO;
     
     // 마커 여러개 찍기
     for(int i=0; i < naverMapInfo.size(); i++){
         Marker[] markers = new Marker[naverMapInfo.size()];

         markers[i] = new Marker();
         lat = naverMapInfo.get(i).getStoreLat();
         lnt = naverMapInfo.get(i).getStoreLnt();
         markers[i].setPosition(new LatLng(lat, lnt));
         markers[i].setMap(naverMap);

         int finalI = i;
         markers[i].setOnClickListener(new Overlay.OnClickListener() {
             @Override
             public boolean onClick(@NonNull Overlay overlay)
             {
                 Toast.makeText(getApplication(), "마커" + finalI + "클릭", Toast.LENGTH_SHORT).show();
                 return false;
             }
         });

     }

 }

DB에 있는 식당 800여개를 마커로 모두 띄운 모습
마커를 눌렀을 때 일어나는 클릭이벤트