본문 바로가기

프로그래밍언어/Android Programming

안드로이드 위치기반 서비스 API


11위치 기반

서비스 API

- 안드로이드 SDK가 제공하는 위치 기반 서비스 API를 활용하는 방법?

- 특정한 하드웨어 장치의 제공자를 이용해서 단말기의 현재 위치를 알아내는 방법?

- 수치로 된 위치 좌표를 사용자에게 친숙한 지명으로 변환하는 방법?

- 변환한 지명을 수치로 된 위치 좌표로 바꾸는 방법?

- 여러 종류의 지도 연계 방법?

- 지도를 다루는데 도움되는 편의 수단?

 

 

GPS 활용

- 안드로이드 SDK에는 내장 GPS 장치에 기초한 위치 정보 제공자가 포함되어 있음

- 기기에 GPS 장치가 없는 경우를 위한 대안적인 위치 정보 제공자들 존재

 

 

 

Ø 위치 알아내기

1. LOCATION_SERVICE getSystemService()를 호출해서 LocationManager 인스턴스를 얻는다.

- 특별한 권한이 필요하지 않음

 . . .

 

2. 응용프로그램에 필요한 위치 정보의 수준에 따라 적절한 권한을 요청하는 요소를 AndroidManifest.xml 파일에 추가한다.

- 세밀한(fine) 위치 정보, 성긴(coarse) 위치 정보 얻기

 

3. getBestProvider() 메서드나 getAllProviders() 메서드를 이용해서 위치 정보 제공자를 선택한다.

 

- 원하는 제공자에 대한 조건들을 Criteria 객체에 설정해서 그 조건들에 가장 근접한 제공자를 선택하는 예

: Criteria 클래스는 사용자 요구에 적합한 위치공급자의 기준을 명시

: setAccuracy() 메서드에 NO_REQUIREMENT, ACCURAVY_COARSE, ACCURACY_FINE 지정 가능

: setPowerRequirement() 메서드는 위치 정보 제공자의 전원 사용 수준 지정

 (NO_REQUIREMENT, POWER_HIGH, POWER_LOW 지정 가능)

: Criteria는 제공자가 사용자에게 비용을 물리는지의 여부라던가 고도 정보 포함 여부 같은

기타 세부 조건들을 설정하는 메서드

 

- getBestProvider()가 선택한 제공자로부터 위치 정보를 받기 위해서는 LocationListener 인터페이스를 구현해야 함

: LocationListener에서 구현해야 할 메서드 4가지

  ☞ 둘은 제공자의 활성화/비활성화 사건을 알줌

  ☞ 제공자의 상태를 알려줌

onLocationChanged(): 위치 정보를 응용프로그램에 제공하는 메서드

   : onLocationChanged()는 선택된 제공자가 공급한 최근 위치 정보를 담은 Location객체 받음

  

 

위치의 지오코딩

- geocoding: 사람에게 익숙한 주소나 서술적인 지명을 지리적 수치 좌표로 변환하는 것

 

Geocoder?

- 특별한 권한이 필요 없음

- Geocoder 객체를 이용해 주어진 Location 객체의 수치 좌표들을 주소나 지명으로 변환하는 예

    : getFromLoation() 가 돌려준 위치 중 하나의 위치를 서술하는 주소가 여러 개 일 수 있음

    : 일반적으로 목록의 첫 주소가 가장 상세한 주소

   

    : Address 객체에서 주소 정보를 뽑아 내는 방법

     1. getFeatureName(), getLocality(): 국가 이름 같은 일반적인 정보 몇 가지 뽑아 낼 때

     2. 주소 줄(address line)들을 얻는 것

         : getMaxAddressLineIndex()가 돌려준 값만큼 루프를 돌리면서 getAddressLine()

메서드를 호출해서 각 주소 줄을 얻음

         : 위치의 주소를 사용자에게 표시하고자 할 때 흔히 쓰임

        : 위치를 방향 지시에 사용할 때나 도로의 주소를 알아야 할 때

        : But, 완전한 주소를 얻지 못할 수 있다

 

위치를 지도로 표시하기

- 안드로이드 SDK에서 주어진 위치를 Google 지도로 표시하는 방법은 두 가지

1. 위치를 담은 Uri 객체를 이용해서 내장 Google 지도 응용프로그램을 띄우는 것

2. MapView 위젯을 이용하여 응용프로그램 안에서 지도를 표시하는 것

 

Ø 외부 지도 응용프로그램을 이용한 지도 표시

- 안드로이드 내장 지도 응용 프로그램을 띄워서 알아낸 위치의 지도를 표시 할 수 있음

 : 지정된 위도와 경도가 유효하다면 내장 지도 응용프로그램이 해당 위치의 지도를 표시

 : 지도 응용프로그램이 요구하는 형태의 URI 문자열을 만들고 이 문자열을 이용해 Uri 객체를

만든 후 그것을 이용해 새 Intent 객체를 생성

 : startActivity()를 호출해서 그 Intent를 실행

 : 지정된 위, 경도가 유요하다면 내장 지도 응용프로그램이 해당 위치의 지도를 표시

 

Ø MapView 위젯을 이용한 지도 표시와 상호작용

- 지도와 응용프로그램 자체에 통합

 

사용자가 지명을 입력하면 그 화면에서 즉시 해당 지도를 표시하도록 해보자 !!!

 

- 우선, MapView 위젯을 하나 화면에 추가

: 태그 이름이 완전히 한정된 이름

: apikey라는 특성이 필수

 

- 다음으로, MapView Google 지도를 사용할 수 있도록 AndroidManifest.xml 파일에 두 가지 요소를 추가

    .  .  .

   : 첫 요소는 MapView 위젯의 실행에 필요한 라이브러리를 명시적으로 지정

   : 둘째 요소는 인터넷 접근(Google 지도 서비스)을 위한 권한

   : 이 두 가지 요소 중 하나라도 없으면 오류 발생

 

 

- 마지막으로, 응용프로그램이 MapView 위젯을 사용하려면 응용프로그램의 Activity 클래스가 보통의 Activity가 아니라 MapActivity를 확장해야 함

- MapView MapActivity 안에서만 사용할 수 있음(MapActivity 외부에서 접근하는 것은 오류)

- MapActivity isRouteDisplayed() 메서드를 구현해야 한다. 이 메서드는 지도에 경로(route)를 표시할 것이면 true, 아니면 false를 돌려주어야 함

  : 경로를 표시하지 않도록 하는 기본적인 구현

 

☞ 여기까지, 응용프로그램에서 MapView를 지도에서 표시하는 데 필요한 기본적인 조건

 

- MapController 객체를 이용해서 MapView를 제어하는 예

   : MapView 위젯을 얻어서 지도 표시 모드를 위성 모드로 설정

   : MapController 객체를 얻어서 지도의 확대, 축소 수준 설정

 

- 특정 위치를 지도에 표시하는 예

   : 이 코드 블록을 “Map” 버튼 클릭 처리 메서드에 적절히 추가

   : 주어진 위도와 경도 좌표로 GeoPoint 객체를 생성

   : 객체의 animateTo() 메서드를 호출해서 지도를 그 위치로 이동

   : GeoPoint 객체는 마이크로디그리(백만분의 1) 단위를 사용하므로 지오코딩으로 얻은

    좌표 값들에 1E6(1,000,000, 1백만)을 곱함

   : 지도의 중심을 설정하고 싶다면 setCenter() 메서드를 이용

 

 

Ø Google 지도 API 키 얻기

- 응용프로그램에서 MapView를 사용하려면 반드시 Google에서 API 키를 받아야 함

 

1. 디버그 인증서에 대한 MD5 지문을 생성한다.

2. 웹 브라우저에서 http://code.google.com/intl/ko-KR/android/maps-api-signup.html로 간다

3. 그 페이지의 Anroid Maps APIs Terms of Service를 읽고, 동의한다면 그 아래의 체크 상태를 체크한다.

4. 그 아래의 텍스트 입력 상자에, 단계 1에서 만든 지문을 넣는다.

5. Generate API Key 버튼을 클릭하면 생성된 키가 표시된다.

 

1단계

- MDF Fingerprint를 얻기 위해서는 keytool이라는 툴을 이용

 

- MD5 Fingerprint를 생성

- 안드로이드 어플리케이션 테스트에 사용되는 디버그용 keystore

 

2단계

3단계

- 권한 설정 및 라이브러리 사용 설정을 변경

- ACCESS_COARSE_LOCATION: 개략적인 위치 정보에 접근하는 것을 허용

- ACCESS_FINE_LOCATION: 자세한 위치 정보에 접근하는 것을 허용

 

4단계

- 레이아웃 xml 수정

 

 

 

 

 

 

 

 

 

 

 

※ 각종 에러들!!!

1.

 

 

 

 

 

2.

 

 

package com.androidbook.location;

 

import java.io.IOException;

import java.util.Iterator;

import java.util.List;

 

import android.content.Intent;

import android.location.Address;

import android.location.Geocoder;

import android.net.Uri;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.ImageView;

import android.widget.TextView;

 

import com.google.android.maps.GeoPoint;

import com.google.android.maps.MapActivity;

import com.google.android.maps.MapController;

import com.google.android.maps.MapView;

import com.google.android.maps.MapView.LayoutParams;

 

public class Mapping extends MapActivity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.mapping);

       

        final EditText name = (EditText) findViewById(R.id.placename);

        final Geocoder coder = new Geocoder(getApplicationContext());

        final TextView results = (TextView) findViewById(R.id.result);

        final Button mapLoc = (Button)findViewById(R.id.map_it);

       

        final MapView map = (MapView) findViewById(R.id.map);

        map.setSatellite(true);

        final MapController mapControl = map.getController();

        mapControl.setZoom(17);

 

        map.setBuiltInZoomControls(true);

       

        Button geocode = (Button) findViewById(R.id.geocode);

        geocode.setOnClickListener(new View.OnClickListener() {

 

            public void onClick(View v) {

                String placeName = name.getText().toString();

                try {

                    List<Address> geocodeResults = coder.getFromLocationName(placeName, 3);

                    Iterator<Address> locations = geocodeResults.iterator();

 

                    String locInfo = "Results:\n";

                    double lat = 0f;

                    double lon = 0f;

                    while (locations.hasNext()) {

                        Address loc = locations.next();

                        locInfo += String.format("Location: %f, %f", loc.getLatitude(), loc.getLongitude());

                        lat = loc.getLatitude();

                        lon = loc.getLongitude();

                    }

                    results.setText(locInfo);

                   

                    final String geoURI = String.format("geo:%f,%f", lat, lon  );

                   

                    mapLoc.setOnClickListener(new View.OnClickListener() {

                        public void onClick(View v) {

                            Uri geo = Uri.parse(geoURI);

                            Intent geoMap = new Intent(Intent.ACTION_VIEW, geo);

                            startActivity(geoMap);

                        }

                       

                    });

                    mapLoc.setVisibility(View.VISIBLE);

                   

                    GeoPoint newPoint = new GeoPoint((int)(lat * 1E6), (int)(lon*1E6));

                    mapControl.animateTo(newPoint);

                   

                    // add a view at this point

                    MapView.LayoutParams mapMarkerParams = new MapView.LayoutParams(

                            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,

                            newPoint, MapView.LayoutParams.TOP_LEFT );

                    ImageView mapMarker = new ImageView(getApplicationContext());

                    mapMarker.setImageResource(R.drawable.paw);

                    map.addView(mapMarker, mapMarkerParams);

                   

 

                } catch (IOException e) {

                    Log.e("Mapping", "Failed to get location info", e);

                }

 

            }

 

        });

       

        // check to see if we were launched with a browser intent... e.g. "geoname://loc/yellowstone"

        Intent launchIntent = getIntent();

        if (launchIntent != null) {

            String action = launchIntent.getAction();

            Uri data = launchIntent.getData();

            Log.v("Mapping", "Intent action = " + action);

            if (data != null ) {

                Log.v("Mapping", "Intent Uri = " + data.toString());

               

                name.setText(data.getLastPathSegment());

                geocode.performClick();

            }

        }

       

       

    }

 

    @Override

    protected boolean isRouteDisplayed() {

        //

        return false;

    }

 

    @Override

    protected boolean isLocationDisplayed() {

        // we don't display sensor based location on the map

        return false;

    }

   

   

}

 

 

Ø 지도 이동

- 지오코딩으로 얻은 결과가 사용자가 원했던 위치와 정확히 일치하지 않을 수도 있고 사용자가 지도로 그냥 여기저기를 둘러보고 싶을 수도 있음

- 지동 이동(panning)기능을 제공

 

- 해당 XML 레이아웃에서 clickable 특성을 true로 설정해서 클릭 반응을 활성화

 

 

Ø 지도 확대 • 축소

- 안드로이드 SDK 1.5에 새로 추가된 메서드 하나를 호출하면 확대, 축소 기능이 활성화

 

 

Ø 지도상의 위치 표식

- 지도의 특정 지점을 표시

 

첫 번째 방법

- 지명에서 얻은 위도와 경도에 해당하는 위치에 이미지 표시 위젯을 추가

: 주어진 위도와 경도를 GeoPoint 객체를 생성

: 한 지점을 지정하기 위해 MapView.LayoutParams 객체를 생성하고 이를 이용해 그 이미지

뷰 위젯을 지도상의 해당 위치에 추가

 

두 번째 방법

- 여러 개의 표식을 추가할 수 있게 하는 더 쉬운 방법은?

- ItemizedOverlay를 사용하는 것!!

private class HutsItemizedOverlay

     extends ItemizedOverlay<OverlayItem> {

 

     public HutsItemizedOverlay(Drawable defaultMarker) {}

 

     protected OverlayItem createItem(int i) {}

     public int size() {}

 

}

   : ItemizedOverlay 파생 클래스를 정의

- 생성자

: 생성자의 Drawable 매개변수는 지도 위에 나타날 표식을 위한 표시물 자원

: 표식은 직접 만들어서 넘겨줘야 함

: boundCenterBottom() 호출은 표식 아래에 그림자를 드리우는 효과

 

- populate() 메서드를 호출

- 위치 자료가 생기는 즉시 호출해 주어야 함

- createItem() 메서드를 size() 메서드의 반환값만큼 반복 호출

: createItem() 메서드 구현

: createItme() 메서드는 배열 중 주어진 색인에 해당하는 GeoPoint 객체를 인수로 해서 생성한 OverlayItem 객체를 돌려줌

: 표식의 제목이나 설명을 사용하지 않기 때문에 2, 3째 매개변수에는 null을 지정

 

- createItem() 메서드에 주어지는 최대 색인은 size() 메서드가 결정하고 size() 메서드는 그냥 위치 배열의 크기를 돌려줌

 

ItemizedOverlay<OverlayItem> 파생 클래스의 구현 끝

 

응용프로그램에서 이 클래스를 MapView에게 알려주어야 함

   : 응용프로그램 MapActivity onCreate() 메서드에서 해줌

: 표식을 위한 표시물 자원으로부터 Drawable 객체를 생성

: 그 객체가 ItemizedOverlay 객체의 일부로 정확히 그려지도록 setBounds()를 호출

: 다음으로, HutsItemizedOverlay 객체를 생성하고 그것을 MapView Overlay 목록에 추가

: MapView getOverlays() MapView에 존재하는 현재 Overlay 객체들의 목록을 돌려줌

: 앞에서 만든 HutsitemizedOverlay 객체를 인수로 해서 그 목록의 add() 메서드를 호출

함으로써, 각 표시지역마다 하나의 Overlay 객체가 MapView에 추가

: 마지막으로, 사용자가 지도를 확대하고 축소할 수 있는 컨트롤을 MapView에 추가

: ItemizedOverlay<OverlayItem> getCenter()는 기본적으로 첫 OverlayItem 항목의 위치를 돌려주는데 그것을 모든 OverlayItem 항목의 중심 위치를 돌려주도록 재정의하고 그것으로 MapView의 중심을 설정

 

package com.androidbook.location;

 

import java.util.List;

 

import android.graphics.Canvas;

import android.graphics.drawable.Drawable;

import android.os.Bundle;

 

import com.google.android.maps.*;

 

 

public class Huts extends MapActivity {

 

   

   

    @Override

    protected void onCreate(Bundle data) {

        super.onCreate(data);

        setContentView(R.layout.huts);

 

        Drawable marker = getResources().getDrawable(R.drawable.paw);

       

        // work around to issue

        // see http://groups.google.com/group/android-developers/msg/a7998c2c08bddc2a

        marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight());

       

        HutsItemizedOverlay huts = new HutsItemizedOverlay(marker);

       

        MapView map = (MapView)findViewById(R.id.map);

        map.setSatellite(true);

      

        List<Overlay> overlays = map.getOverlays();

        overlays.add(huts);

 

        map.setBuiltInZoomControls(true);

        

        final MapController mapControl = map.getController();

     

        mapControl.setCenter(huts.getCenter());

        mapControl.zoomToSpan(huts.getLatSpanE6(), huts.getLonSpanE6());

    

       

    }

 

    @Override

    protected boolean isRouteDisplayed() {

        // we do not show routes

        return false;

    }

   

    private class HutsItemizedOverlay extends ItemizedOverlay<OverlayItem> {

 

        public HutsItemizedOverlay(Drawable defaultMarker) {

            super(defaultMarker);

            

            // change the direction of the shadow so the bottom of the marker is the part "touching"

            boundCenterBottom(defaultMarker);

 

            //static data, so we call this right away

            populate();

        }

       

 

        @Override

        public GeoPoint getCenter() {

            Integer averageLat = 0;

            Integer averageLon = 0;

            for (GeoPoint point : hutPoints) {

                averageLat += point.getLatitudeE6();

                averageLon += point.getLongitudeE6();

            }

            averageLat /= hutPoints.length;

            averageLon /= hutPoints.length;

            return new GeoPoint(averageLat, averageLon);

        }

 

 

        @Override

        public void draw(Canvas canvas, MapView mapView, boolean shadow) {

            super.draw(canvas, mapView, false);

        }

 

       

        public GeoPoint hutPoints[] = new GeoPoint[] {

            // Lakes of the Clouds

            new GeoPoint(44258793, -71318940),

            // Zealand Falls

            new GeoPoint(44195798, -71494402),

            // Greanleaf

            new GeoPoint(44160372, -71660385),

            // Galehead

            new GeoPoint(44187866, -71568734),

            // Carter Notch

            new GeoPoint(44259224, -71195633),

            // Mizpah Spring

            new GeoPoint(44219362, -71369473),

            // Lonesome

            new GeoPoint(44138452, -71703064),

            // Madison Spring

            new GeoPoint(44327751, -71283283),

        };

 

        @Override

        protected OverlayItem createItem(int i) {

            // the "title" and "snippet" fields aren't used anywhere just yet... so

            // we've ignored them here

           

            OverlayItem item = new OverlayItem(hutPoints[i], null, null);

           

            return item;

        }

 

        @Override

        public int size() {

            return hutPoints.length;

        }

       

    }

 

}