반응형

이번 글은 네이버 맵 API를 사용하면서 내가 사용한 API들에 대해 정리해보려 한다.

안드로이드에서는 맵을 사용할때 주로 네이버, 카카오, 구글 맵을 사용하는데

나는 내가 평소에 자주 사용했던 네이버맵을 사용하기로 했다.

맵을 사용할때 참고했던 자료는 아래 링크에 있다.

 

Android 시작 가이드 - Mobile Dynamic Map (v3)

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

guide.ncloud-docs.com

 

 

Reverse Geocoding - Maps

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

guide.ncloud-docs.com

 

 

Directions 5 - Maps

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

guide.ncloud-docs.com

1. Mobile Dynamic Map

Mobile Dynamic Map은 말 그대로 NaverMap의 지도를 사용하는 기능이다.

 

Android 시작 가이드 - Mobile Dynamic Map (v3)

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

guide.ncloud-docs.com

위 링크에서 메니페스트 부분과 build.gradle 부분에 설정을 해주면 레이아웃에 MapView를 추가할 수 있다.

레이아웃에 맵뷰를 추가한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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=".MainActivity"
    android:orientation="vertical">
 
    <com.naver.maps.map.MapView
        android:id="@+id/mv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
 
 
</LinearLayout>
cs

딱히 설정해줄 것은 없고 사이즈만 지정해준 후 id만 부여해주면 된다.

xml에서 맵뷰를 만들었으면 java파일에서 맵에 대한 설정을 해주면 맵을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
    private MapView mapView;
    private static NaverMap naverMap;
    private LatLng myLatLng = new LatLng( 37.3399126.733);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        mapView = (MapView)findViewById(R.id.mv);
 
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(this);
 
    }
 
    @Override
    public void onMapReady(@NonNull NaverMap naverMap) {
        this.naverMap = naverMap;
 
        CameraPosition cameraPosition = new CameraPosition(myLatLng, 16);
        naverMap.setCameraPosition(cameraPosition);
    }
}
 
cs

먼저 OnMapReadyCallback을 implements한 후 onMapReady를 @Override해줘야 한다.

맵뷰의 아이디를 등록한 후 onCreate에 mapView.onCreate(), mapView.getMapAsync()를 호출한다.

mapView의 onCreate는 mapView를 생성하고, getMapAsync는 맵이 정상적으로 작동될 때 onMapReady()를 호출한다.

onMapReady()는 맵의 초기위치를 지정하거나 마커를 찍거나 만들어진 맵에서 여러가지 행위를 하기 위해 사용한다.

onMapReday()에서 CameraPosition을 호출하는데 맵의 초기위치를 지정하기 위해 사용한다.

CameraPosition의 첫번째 매개변수는 위경도값이 저장되는 LatLng이고, 두번째 변수는 맵의 줌 비율이다.

2. ReverseGeocoding

NaverMap에서 제공하는 Geocoding, ReverseGeocoding은 위경도 <-> 주소 변환을 제공하는 API이다.

Geocoding은 문자열로 이루어진 주소 -> 위경도

ReverseGeocoding은 위경도 -> 주소 기능을 제공한다.

내가 이번에 만든 어플은 맵에서 위치(마커)를 찍었을 때 해당 위치의 주소를 얻는 기능을 필요로 해서 ReverseGeocoding을 사용했다.

ReverseGeocoding의 요청 헤더와 응답 바디는 아래 링크에 정리되어있다.

 

gc - Reverse Geocoding

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

api.ncloud-docs.com

요청 헤더에서 orders 부분이 어떤 응답 바디를 얻을 것인지 결정하는 것인데, 

- legalcode: 좌표 to 법정동
- admcode: 좌표 to 행정동
- addr: 좌표 to 지번 주소
- roadaddr: 좌표 to 도로명 주소(새주소)

4가지가 있다.

내 경우에는 지번 주소와 도로명 주소가 필요해서 addr, roadaddr을 사용했다.

ReverseGeocoding을 사용한 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 
public void CallRetrofit(LatLng latlng){
        try {
            reverseGeocoderClient = ReverseGeocoderClient.getInstance();
            reverseGeocoderInterface = ReverseGeocoderClient.getReverseGeocoderInterface();
 
            mlatlng = latlng;
            reverseGeocoderInterface.getAddress(Double.toString(latlng.longitude) + "," + Double.toString(latlng.latitude),
                    "json",
                    "addr,roadaddr",
                    key_id,
                    key).enqueue(new Callback<Address>() {
                @Override
                public void onResponse(Call<Address> call, Response<Address> response) {
                    //need to set out of country
                    Address address = response.body();
                    if(address.getStatus().getCode()==0) {
                        maddress = address.getResults().get(0).getRegion().getArea1().getName() + " " +
                                address.getResults().get(0).getRegion().getArea2().getName() + " " +
                                address.getResults().get(0).getRegion().getArea3().getName();
 
                        if(address.getResults().size() == 1) {
                            maddress += " " + address.getResults().get(0).getLand().getNumber1();
                            if(address.getResults().get(0).getLand().getNumber2().length() != 0)
                                maddress += "-" + address.getResults().get(0).getLand().getNumber2();
                        }else{
                            maddress += " " + address.getResults().get(0).getLand().getNumber1();
                            if(address.getResults().get(0).getLand().getNumber2().length() != 0)
                                maddress += "-" + address.getResults().get(0).getLand().getNumber2();
                            maddress += "\n" + address.getResults().get(1).getLand().getAddition0().getValue();
                        }
 
                        tv_calendar_schedule_add_address.setText(maddress);
                    }
                    else{
                        tv_calendar_schedule_add_address.setText("위치 정보가 없습니다.");
                    }
 
                }
 
                @Override
                public void onFailure(Call<Address> call, Throwable t) {
 
                }
            });
        }catch (Exception e){
            tv_calendar_schedule_add_address.setText(e.getMessage());
        }
    }
 
cs

3.  Directions 5

Directions 5는 출발지와 목적지 사이의 경로, 예상 시간, 요금 등을 제공하는 네비게이션API이다.

NaverMap에서 제공하는 네비게이션 API는 Directions 5, Directions 15 두가지가 있는데, 5, 15 숫자는 지정할 수 있는 최대 경유지를 의미하는 것으로 알고 있다.

15개 까지 경유지를 지정할 필요를 못느껴서 Directions 5를 사용했다.

Directions 5의 요청 헤더와 응답 바디는 아래 링크에 설명되어 있다.

 

driving - Directions 5

쿠키 제공 동의 당사는 고객님의 브라우징 기반 정보를 바탕으로 관련 정보 및 광고 제공을 위하여 지식 기반 쿠키를 사용합니다.

api.ncloud-docs.com

Direction 5를 사용한 코드는 아래와 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 
public void CallRetrofit(){
        try{
            navigationClient = NavigationClient.getInstance();
            navigationInterface = NavigationClient.getNavigationInterface();
 
            navigationInterface.getNavigation(Double.toString(start.longitude)+","+Double.toString(start.latitude),
                    Double.toString(finish.longitude)+","+Double.toString(finish.latitude),
                    key_id,
                    key).enqueue(new Callback<Navigation>() {
                @Override
                public void onResponse(Call<Navigation> call, Response<Navigation> response) {
                    Navigation navigation = response.body();
 
                    int size = navigation.getRoute().getTraoptimal().get(0).getPath().size();
                    String str = "";
 
                    LatLng mlatlng = new LatLng((start.latitude+finish.latitude) / 2, (start.longitude + finish.longitude) / 2);
                    CameraPosition cameraPosition = new CameraPosition(mlatlng,10);
                    naverMap.setCameraPosition(cameraPosition);
 
 
                    list.clear();
                    for(int i = 0; i < size; i++){
                        list.add(new LatLng( navigation.getRoute().getTraoptimal().get(0).getPath().get(i).get(1), navigation.getRoute().getTraoptimal().get(0).getPath().get(i).get(0)));
                    }
                    path.setCoords(list);
                    path.setColor(0xff000fff);
                    path.setMap(naverMap);
 
                    et_lat.setText(Integer.toString(navigation.getRoute().getTraoptimal().get(0).getSummary().getDuration() / 1000+ "sec");
                    et_lng.setText(Integer.toString(navigation.getRoute().getTraoptimal().get(0).getSummary().getDistance()) + "m");
                }
 
                @Override
                public void onFailure(Call<Navigation> call, Throwable t) {
                    tv_test.setText(t.getMessage());
                }
            });
 
        }
        catch (Exception e){
            tv_test.setText(e.getMessage());
        }
    }
 
cs

요청 헤더로 출발지 위경도, 목적지 위경도, key_id, key를 입력하면 아래와 같은 형식의 json응답을 얻을 수 있다.

위 사진에서 보이는 Double 형태의 숫자들은 출발지와 목적지 사이의 경로들의 위경도값이다.

출발지와 위경도 사이의 경로들을 다 구하긴 했는데, 이 경로들을 어떻게 맵에 표시할까 고민하던 중에 navermap에서 제공하는 PathOverlay에 대해 알게 되었다.

3-1. PathOverlay

PathOverlay에 대해서는 아래 링크에 자세히 설명되어 있다.

 

PathOverlay (NAVER Map Android SDK 3.12.0)

setPatternImage @UiThread public void setPatternImage​(@Nullable OverlayImage pattern) 패턴 이미지를 지정합니다. 패턴 이미지의 크기가 경로선의 두께보다 클 경우 경로선의 두께에 맞게 축소됩니다. null일 경

navermaps.github.io

PathOverlay는 지도에 경로선을 나타내기 위한 NaverMap에서 제공하는 클래스로 

PathOverlay.setCoords()에 매개변수로 List<LatLng> list를 넣어주면 리스트에 저장되어 있는 LatLng값들을 경로선으로 이어줄 수 있다.

위의 Directions 5코드에서 PathOverlay는 아래 코드에서 사용되었다.

1
2
3
4
5
6
7
8
9
 
list.clear();
for(int i = 0; i < size; i++){
    list.add(new LatLng( navigation.getRoute().getTraoptimal().get(0).getPath().get(i).get(1), navigation.getRoute().getTraoptimal().get(0).getPath().get(i).get(0)));
}
path.setCoords(list);
path.setColor(0xff000fff);
path.setMap(naverMap);
 
cs

리스트 하나를 clear해준 후 

응답 바디에서의 경로의 갯수만큼 경로의 위경도 값을 리스트에 넣어준다.

그다음 setCoords를 통해 리스트를 경로선으로 그려주고,

경로선의 색상을 지정한다.

경로선의 색상을 지정하는 매개변수는 ARGB값을 넣어주면 된다.

그 다음 경로를 naverMap에 setMap 시켜주면 지도에 경로가 그려진다.

3-2. NaverMap.getContentBounds

경로를 다 그렸는데 또 다른 문제가 생겼다.

경로를 그리긴 했지만, 경로가 맵에 표시되지 않는 경우가 발생했다.

왜냐면 카메라포지션을 경로에 맞게 잡아주지 못했으니까.....

경로가 지도에 잘 표현되려면 최소한 출발지와 목적지가 둘다 맵 안에 표시되어야 한다.

그래서 고민하다가 지도의 최소 위경도와 최대 위경도 값을 알 수 있다면 출발지와 목적지를 둘다 지도안에 넣을 수 있겠다 생각이 들었고, NaverMap에서 제공하는 클래스와 메서드들을 찾아보다가 해당 기능을 찾을 수 있었다.

바로 NaverMap.getContentBounds였다.

NaverMap의 메서드들에 대한 정리는 아래 링크에 잘 되어있다.

 

NaverMap (NAVER Map Android SDK 3.12.0)

getContentPadding @NonNull public int[] getContentPadding() 지도의 콘텐츠 패딩을 배열로 반환합니다. 배열의 크기는 4이며, 각 원소는 순서대로 왼쪽, 위쪽, 오른쪽, 아래쪽 패딩을 나타냅니다. 기본값은 모

navermaps.github.io

getContentBounds는 LatLngBounds 형태로 결과값을 반환하는데 LatLngBounds의 메서드들은 아래 링크에 정리되어 있다.

 

LatLngBounds (NAVER Map Android SDK 3.12.0)

isEmpty public boolean isEmpty() 영역이 비어있는지 여부를 반환합니다. 최남단의 위도가 최북단의 위도보다 크거나 같을 경우, 최서단의 경도가 최동단의 경도보다 크거나 같을 경우, 영역이 유효하

navermaps.github.io

LatLngBounds에서 제공되는 getSouthWest(), getNorthEast()를 사용해서 남서, 북동 좌표를 얻고 최저 위경도, 최고 위경도 값을 얻을 수 있었다.

이 값들을 이용해서 출발지가 맵 안에 있고, 목적지가 맵 안에 있을 때까지 CameraPosition의 zoom을 줄이면서 경로가 포함된 맵을 얻을 수 있었다.

경로가 포함된 맵을 세팅하는 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
    private void cameraTest(){
        double zoom = 16;
        LatLng mlatlng = new LatLng((start.latitude+finish.latitude) / 2, (start.longitude + finish.longitude) / 2);
        CameraPosition cameraPosition = new CameraPosition(mlatlng,zoom);
        //naverMap.setCameraPosition(cameraPosition);
        //zoom 올려가면서 조절
 
        for(; zoom >= 1.0; zoom-=0.5){
            cameraPosition = new CameraPosition(mlatlng, zoom);
            naverMap.setCameraPosition(cameraPosition);
 
            minLat = naverMap.getContentBounds().getSouthWest().latitude;
            minLng = naverMap.getContentBounds().getSouthWest().longitude;
            maxLat = naverMap.getContentBounds().getNorthEast().latitude;
            maxLng = naverMap.getContentBounds().getNorthEast().longitude;
 
            if(start.latitude > minLat && start.latitude < maxLat &&
            start.longitude > minLng && start.longitude < maxLng &&
            finish.latitude > minLat && finish.latitude < maxLat &&
            finish.longitude > minLng && finish.longitude < maxLng){
                naverMap.setCameraPosition(cameraPosition);
                return;
            }
 
            zoom -= 1.0;
        }
    }
 
cs

CameraPositon의 LatLng 값은 출발지와 목적지의 중간값으로 지정했고, 

위에서 설명한 것처럼 zoom의 배율을 조금씩 줄여가면서 경로를 맵에 담을 수 있도록 하였다.

4. 앞으로의 목표

기능에 초점을 맞춰서 코드를 구현하다보니 전체적인 코드가 통일이 잘 안되고 지저분한 느낌이 있음을 느꼈다.

직관적이고 깔끔한 코드를 작성할 수 있도록 코드를 개선해야겠다 :)

반응형

+ Recent posts