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; } } } |
'프로그래밍언어 > Android Programming' 카테고리의 다른 글
개발자 포럼에 뜬 안드로이드 3.0 허니콤, 어떨까? (0) | 2011.03.18 |
---|---|
안드로이드 공연정보어플 'PartyUHI' 마켓등록되었습니다! (0) | 2011.03.18 |
안드로이드 서브 메뉴 만들기! (0) | 2011.03.03 |
안드로이드 에뮬레이터에서 맵뷰 구동, 휴대폰에서 되지 않을 때 'keystore'문제 (0) | 2011.03.03 |
안드로이드 인텐트 (액티비티) 이동 (0) | 2011.03.03 |