Lomohome.com :: 괴발자 모근원

- 이번 글은 보기좋게 PDF로 첨부합니다.




- 개인적인용도로 요약한 글이라 글에서는 경어체를 사용하지 않습니다. 글 읽으시는데 참고부탁드립니다.

- Mac의 Pages 로 작성한 후 블로그에 포스팅하려니 서식이 다 깨졌네요.

   PDF 파일로 보는것이 보기 좋습니다.


저번엔 안드로이드용 위치기반 지점찾기 (LBS)를 구현하였고, 이번에 아이폰용 뱅킹어플을 만들면서 아이폰용도 지점찾기를 어플로 구현할 필요가 생겼다.


이번엔 계속 써와서 익숙한 Java 가 아니라 Objective C 여서 시작하기가 막막했다. 배우면서, 삽질하며 완성시킨거라 버그도 있을것이고 여러부분에서 미숙한 점이 있을테지만 마찬가지로 까먹지 않기 위하여 정리를 해둔다.


1. 프로젝트에 프레임웍 추가하기.

프로젝트의 프레임웍에서 마우스 오른쪽버튼 (또는 옵션클릭)을 하여 프레임웍을 추가해준다.

사용자 위치정보를 가져올 CoreLocation.framework 와 지도표시에 필요한 MapKit.framework 을 추가해준다.


추가가 된것을 확인하면 성공.










2. 뷰에서 사용할 마커(어노테이션) 준비하기.


지도 앱들을 보면 다음과 같은 핀이 있는데 이것이 안드로이드에서는 마커, iOS에서는 어노테이션이라고 불리우는 드랍핀이다. 


그냥 써도 되지만 지점찾기 앱에서는 각 마커마다 지점의 정보를 가지고 있기

때문에 MKAnnotation 을 구현하여 커스텀 어노테이션을 만들어 쓰기로 했다.



//  BranchMarker.h

// 마커(어노테이션) 쓰일 객체.


#import <Foundation/Foundation.h>

#import <MapKit/MKAnnotation.h>


@interface BranchMarker : NSObject <MKAnnotation>{

//요거 세개는 어노테이션에 필수로 구현해줘야 동작한다.

CLLocationCoordinate2D coordinate;

NSString *title;

NSString *subtitle;

// 아래는 추가로 필요해서 변수 준비.

NSString *bussBrNm; //영업점명

NSString *bussBrTelNo; //영업점 전화번호

NSString *bussBrAdr; //영업점주소 (찾아오시는길)

NSString *trscDrtm; //거래시간

NSString *bussBrAdr2; //영업점주소 (주소)

NSString *markerType; //마커 타입 (0:지점, 1:ATM)

}


@property (nonatomic,assign) CLLocationCoordinate2D coordinate;

@property (nonatomic,copy) NSString *title;

@property (nonatomic,copy) NSString *subtitle;


@property (nonatomic,retain) NSString *bussBrNm;

@property (nonatomic,retain) NSString *bussBrTelNo;

@property (nonatomic,retain) NSString *bussBrAdr;

@property (nonatomic,retain) NSString *trscDrtm;

@property (nonatomic,retain) NSString *bussBrAdr2;

@property (nonatomic,retain) NSString *markerType;


@end

헤더에서는 coordinate, title, subtitle 을 필수로 구현해줘야 MKAnnotation 이 멀쩡히 돌아간다.


//  BranchMarker.m

#import "BranchMarker.h"


@implementation BranchMarker

@synthesize coordinate, title, subtitle;

@synthesize bussBrNm,bussBrTelNo,bussBrAdr,trscDrtm,bussBrAdr2,markerType;


-(void) dealloc{

[title release];

[subtitle release];

[super dealloc];

}


@end

구현파일에서는 특별히 구현할것이 없고 synthesize 만 충실히 해주도록 한다.



3. 뷰컨트롤러 준비하기.

이제 실제 지도를 구현해본다. 이번 어플에서는 크게 다음과 같이 네개의 뷰가 겹쳐져 있다.

맨 아래에 지도를 표시하는 MKMapView 가 깔리고 그 위로 서브뷰로 아이콘 버튼들이 있는 툴바,

그리고 툴바위에 역 지오코딩 (위도, 경도를 가지고 주소를 추적해내는 기술) 한 스트링이 UILabel 로 뿌려지고, 마지막으로 그 위에 어플이 로딩상태일때 로딩을 표시할 스피너가 올려져있다.


//  BranchMapViewController.h

// 지점찾기 컨트롤러.


#import <UIKit/UIKit.h>

#import <MapKit/MapKit.h>

#import <CoreLocation/CoreLocation.h>


//위치관리자, 맵뷰, 그리고 리버스 지오코더 딜리게이트를 구현한다.

@interface BranchMapViewController : UIViewController <CLLocationManagerDelegate , MKMapViewDelegate, MKReverseGeocoderDelegate>{

NSString *searchType; //지점,ATM 검색 타입

MKMapView *mapView; //지도

//,경도를 가지고 해당위치의 주소를 가지고 오는 리버스지오코더

MKReverseGeocoder *reverseGeocoder

//위지관리자. GPS,wifi 등으로 현재 기기의 위치를 가져온다.

CLLocationManager *locationManager;

CLLocation *lastScannedLocation; //마지막으로 검색된 위치를 저장할 객체.

UIActivityIndicatorView * spinner; //화면의 로딩 스피너.

UILabel *geoLabel; //툴바에 리버스지오코더의 결과를 표시한다.

}


@property (retain, nonatomic) NSString *searchType;

@property (retain, nonatomic) MKMapView *mapView;

@property (nonatomic, retain) MKReverseGeocoder *reverseGeocoder;

@property (nonatomic, retain) CLLocationManager *locationManager;

@property (nonatomic, retain) CLLocation *lastScannedLocation;

@property (nonatomic, retain) UIActivityIndicatorView * spinner;

@property (nonatomic, retain) UILabel *geoLabel;


//뷰컨트롤러를 만들때 검색타입을 지정한다. BRANCH/ATM

- (id)initWithShowType:(NSString *)showType;  

//지점정보를 HTTP통신으로 가지고 온다.
- (void)getBranchDataWithLocation:(CLLocation *)location; 

@end


메인 구현파일이라 엄청길다.

//  BranchMapViewController.m

#import "BranchMapViewController.h"

#import <MapKit/MapKit.h>

#import <CoreLocation/CoreLocation.h>

#import "BranchMarker.h"

#import "BranchMapGetDataAction.h"


@implementation BranchMapViewController


@synthesize searchType;

@synthesize mapView,reverseGeocoder,geoLabel;

@synthesize locationManager;

@synthesize lastScannedLocation;

@synthesize spinner;


- (id)initWithShowType:(NSString *)showType {

if ((self = [super init])) {

        // Custom initialization

self.searchType = showType;

    }

NSLog(@"initWithShow %@",self.searchType);

    return self;

}


//이미지로 커스텀 뷰를 만들어준다.

//_normalImg : 버튼 이미지, _touchImg : 눌럿을때 바뀔 이미지, _width : 이미지버튼의 가로길이, _height : 이미지버튼의 세로길이 , _sel : 버튼눌렀을때 액션

-(UIButton*) createCustomImageButtonWithNormalImgNm:(NSString*)_normalImg

  andTouchImg:(NSString*)_touchImg andWidth:(float)_width

andHeight:(float)_height andSEL:(SEL)_sel{

// 버튼 배경에 사용할 이미지 준비.

    UIImage *normalImage = [UIImage imageNamed:_normalImg];

    UIImage *touchImage = [UIImage imageNamed:_touchImg];

    

    // 버튼 생성 

//x,y,width,height

    CGRect buttonRect = CGRectMake(0.0f, 0.0f, _width, _height); 

    UIButton *button = [[[UIButton alloc

initWithFrame:buttonRect] autorelease];

    // 버튼의 배경 이미지 설정

    [button setBackgroundImage:normalImage forState:UIControlStateNormal];

    [button setBackgroundImage:touchImage forState:UIControlStateHighlighted];

    

    // 버튼에 액션 설정

[button addTarget:self action:_sel

forControlEvents:UIControlEventTouchUpInside];


return button;

}


- (void)viewDidLoad {

    [super viewDidLoad];

//searchType 널탕이 들어오면 기본적으로 지점 검색으로 한다.

if (self.searchType == nil) self.searchType = @"BRANCH";

//위치 관리자를 초기화한다.

self.locationManager = [[[CLLocationManager alloc] init] autorelease];

//딜리게이트는 self 설정후 하단에서 딜리게이트 구현.

self.locationManager.delegate = self;

//측정방법은 가장 좋게.

self.locationManager.desiredAccuracy = kCLLocationAccuracyBest

//2000m 이상 위치가 변경되면 노티를 .

self.locationManager.distanceFilter = 2000.0f

    [self.locationManager startUpdatingLocation]; //현재위치 가져오기 시작~

//지도 뷰를 만든다.

//뷰의 크기만큼 지도를 채운다.

mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];

mapView.showsUserLocation = YES; // 위치 표시.

[mapView setMapType:MKMapTypeStandard]; //지도 형태는 기본.

[mapView setZoomEnabled:YES]; //줌가능

[mapView setScrollEnabled:YES]; //스크롤가능

mapView.delegate = self; //딜리게이트 설정 (anotation 메소드를 구현한다.)

MKCoordinateRegion region;

MKCoordinateSpan span; //보여줄 지도가 처리하는 넓이 정의.

span.latitudeDelta = 0.02; //숫자가 적으면 좁은영역 까지 보임.

span.longitudeDelta = 0.02;

CLLocationCoordinate2D location = mapView.userLocation.coordinate;

//위치정보를 못가져왔을때 기본으로 보여줄 위치.

location.latitude = 37.566275; //37.490481 이건 우리집

location.longitude = 126.981794; //126.857790

region.span = span; //크기 설정.

region.center = location; //위치 설정.

[mapView setRegion:region animated:TRUE]; //지도 뷰에 지역 설정.

[mapView regionThatFits:region]; //지도 화면에 맞게 크기 조정.

[self.view addSubview:mapView]; //서브 뷰로 지도를 추가함.

//하단에 버튼들 toolbar 추가

//현재 뷰의 크기를 가져와서 상단 바의 길이가 조정되면 하단 바가 잘리는것을 방지하기 위함.

float heightPos = self.view.bounds.size.height

UIToolbar *toolbar = [[UIToolbar alloc

  initWithFrame:CGRectMake(0.0, heightPos - 50.0f , 320.0, 50.0)]; toolbar.barStyle = UIBarStyleBlackTranslucent; //툴바스타일은 까만 투명색

// 영역 잡아주는 버튼아이템. 왼쪽에 빈 영역 두고, 오른쪽으로 버튼들을 배치하기위함.

UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc]

  initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace

  target:nil action:nil];

 

//이미지 커스텀 버튼.

UIBarButtonItem *hereBtn = [[UIBarButtonItem alloc]

   initWithCustomView:[self createCustomImageButtonWithNormalImgNm:@"here.png"

   andTouchImg:@"here_pressed.png" andWidth:40.0f andHeight:40.0f

   andSEL:@selector(setSearchTypeToHere)]]; //현위치

UIBarButtonItem *branchBtn = [[UIBarButtonItem alloc]

   initWithCustomView:[self createCustomImageButtonWithNormalImgNm:@"atm_btn.png" 

   andTouchImg:@"atm_btn_pressed.png" andWidth:40.0f andHeight:40.0f 

   andSEL:@selector(setSearchTypeToATM)]]; //ATM검색

UIBarButtonItem *atmBtn = [[UIBarButtonItem alloc]

   initWithCustomView:[self createCustomImageButtonWithNormalImgNm:@"hana_btn.png"

   andTouchImg:@"hana_btn_pressed.png" andWidth:40.0f andHeight:40.0f 

   andSEL:@selector(setSearchTypeToBranch)]]; //지점검색

//툴바 아이템 배치

toolbar.items = [NSArray

arrayWithObjects:flexibleSpace,hereBtn,atmBtn,branchBtn,nil];


//툴바를 뷰에 추가.

[self.view addSubview:toolbar];

//툴바에 쓰인 버튼들 릴리즈.

[flexibleSpace release];

[hereBtn release];

[branchBtn release];

[atmBtn release];

[toolbar release];

//화면스피너 셋팅. 로딩중을 표시하기 위함.

self.spinner = [[UIActivityIndicatorView alloc

initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

//화면중간에 위치하기위한 포인트.

[self.spinner setCenter:CGPointMake(320.0f/2.0, 480.0f/2.0)]; 

[self.view addSubview:spinner]; //스피너를 뷰에 추가하고 필요시에 start

//geoCoder 라벨 셋팅. '서울시 송파구 신천동' 따위를 툴바에 표시한다

geoLabel = [[UILabel alloc

initWithFrame:CGRectMake(5.0, heightPos - 45.0f, 160.0, 40.0)];

geoLabel.backgroundColor = [UIColor clearColor];

geoLabel.highlighted = YES;

geoLabel.highlightedTextColor = [UIColor whiteColor];

geoLabel.shadowColor = [UIColor blackColor];

geoLabel.textColor = [UIColor whiteColor];

geoLabel.textAlignment = UITextAlignmentLeft;

geoLabel.numberOfLines = 2; //두줄 표시 가능.

[self.view addSubview:geoLabel]; //뷰에 라벨 추가.

//초기 환영 메세지.

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"위치기반 지점찾기" message:@"위치정보를 가져오는데 기기,통신상태에 따라 시간이 걸릴수 있으며 일부 동작하지 않는 기기도 있습니다.\n\n하단의 아이콘을 이용하여 현재 지도가 표시하고 있는 지역을 중심으로 지점/ATM 검색하실 있습니다." delegate:nil cancelButtonTitle:nil

otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}


//검색 타입 ATM으로 셋팅.

-(void)setSearchTypeToATM{

//현재 지도가 위치하는곳을 중심으로.

CLLocation *customLocation = [[CLLocation alloc

initWithLatitude:mapView.centerCoordinate.latitude 

longitude:mapView.centerCoordinate.longitude];

self.searchType = @"ATM";

[self getBranchDataWithLocation:customLocation]; //HTTP 통신

[customLocation release];

}


//검색 타입 지점으로 셋팅.

-(void)setSearchTypeToBranch{

//현재 지도가 위치하는곳을 중심으로.

CLLocation *customLocation = [[CLLocation alloc

initWithLatitude:mapView.centerCoordinate.latitude 

longitude:mapView.centerCoordinate.longitude];

self.searchType = @"BRANCH";

[self getBranchDataWithLocation:customLocation]; //HTTP 통신

[customLocation release];

}


//현위치

-(void)setSearchTypeToHere{

[self.locationManager startUpdatingLocation];  //로케이션 메니저 다시 시작~

}


//문자열 치환 메소드. source : 원본, 찾을문자열, 바꿀문자열.

-(NSString*)replaceStrSource:(NSString*)sourceStr 

strFrom:(NSString*)_from strTo:(NSString*)_to{

NSMutableString *mstr = [NSMutableString stringWithString:sourceStr];

NSRange substr = [mstr rangeOfString: _from];

while (substr.location != NSNotFound) {

[mstr replaceCharactersInRange: substr withString:_to];

substr = [mstr rangeOfString: _from];

}

return mstr;

}



//지도 데이터를 HTTP통신을 통해 받아와서 표시해준다.

- (void)getBranchDataWithLocation:(CLLocation *)location{

NSLog(@"getBranchDataWithLatitude:%f andLongitude:%f",

location.coordinate.latitude,location.coordinate.longitude);

//화면에 로딩스피너 스타트.

[self.spinner startAnimating];

//HTTP통신에 ContentProvide server 규격을 맞추기 위해, 위도,경도에서 콤마(.) 제거해서 보내야한다.

NSString *lat = [self replaceStrSource:

[NSString stringWithFormat:@"%f",location.coordinate.latitude]

strFrom:@"." strTo:@""];

NSString *lng = [self replaceStrSource:

[NSString stringWithFormat:@"%f",location.coordinate.longitude]

strFrom:@"." strTo:@""];

NSString *range = @"3000"; //기본 3Km반경 지점을 검색해 오게 만든다.

NSString *sType = @"0";

//ATM = 1, 지점 = 0

if ([self.searchType isEqualToString:@"ATM"]) sType = @"1";

else sType = @"0";


//HTTP통신으로 지점정보 가져오는 액션 초기화.

BranchMapGetDataAction *getAction = [[BranchMapGetDataAction alloc

initWithSearchType:sType andReqLat:lat andReqLng:lng andReqRange:range];

//HTTP통신으로 지점정보를 가져온다.

NSMutableArray *branchMarkerAry = [getAction getData];

//마커를 새로 찍기전에 기존에 지도에 있던 마커(annotation) 전부 지운다.

NSMutableArray *toRemove = [NSMutableArray arrayWithCapacity:1];

for(id annotation in mapView.annotations){

if (annotation != mapView.userLocation){

[toRemove addObject:annotation];

}

}

NSLog(@"remove %d annotations.",[toRemove count]);

[mapView removeAnnotations:toRemove];

//받아온 마커(annotation) 맵에 찍어낸다.

NSLog(@"branch marker count : %d",[branchMarkerAry count]);

if([branchMarkerAry count] > 0){

for (BranchMarker* marker in branchMarkerAry){

if (marker != nil) [mapView addAnnotation:marker];

}

}


//reverseGeocoding 시작.

self.reverseGeocoder = [[[MKReverseGeocoder alloc

initWithCoordinate:location.coordinate] autorelease];

    reverseGeocoder.delegate = self;

    [reverseGeocoder start];

//화면의 로딩 스피너 없애기.

[self.spinner stopAnimating];


}


//메모리 부족을 받았을때.

- (void)didReceiveMemoryWarning {

    // Releases the view if it doesn't have a superview.

    [super didReceiveMemoryWarning];

    NSLog(@"branchmap memory warning.");

    // Release any cached data, images, etc that aren't in use.

}


// 내릴때.

- (void)viewDidUnload {

    

NSLog(@"branchmap viewDidUnload");

[self.locationManager stopUpdatingLocation];

self.locationManager = nil;

self.reverseGeocoder = nil;

self.mapView = nil;

self.searchType = nil;

self.lastScannedLocation = nil;

self.spinner = nil;

[super viewDidUnload];

}


//객체 내려갈때.

- (void)dealloc {

NSLog(@"branchmap dealloc");

//사용한 객체들 릴리즈.

[mapView release];

[reverseGeocoder release];

[locationManager release];

[searchType release];

[lastScannedLocation release];

[spinner release];

    [super dealloc];

}



#pragma mark MKMapViewDelegate


NSString *tempTelNo; //어노테이션의 더보기에서 전화걸기를 누를때 임시로 전화번호를 저장할 변수.


//맵의 어노테이션 (마커) 표시.

-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id<MKAnnotation>)annotation{

if (annotation==self.mapView.userLocation){

[mV.userLocation setTitle:@"현재 위치"]; //현재위치 마커에 표시할 타이틀.

return nil; //현재 위치 마커일경우 커스텀 마커를 사용하지 않는다.

}

//현재위치 마커가 아닐때에는 지점마커이다.

BranchMarker *mk = (BranchMarker *) annotation;

MKPinAnnotationView *dropPin = nil; //마커 준비

static NSString *reusePinID = @"branchPin"; //마커 객체를 재사용 하기위한 ID

//마커 초기화

dropPin = (MKPinAnnotationView *)[mapView 

dequeueReusableAnnotationViewWithIdentifier:reusePinID]; 

if ( dropPin == nil ) dropPin = [[[MKPinAnnotationView alloc]

initWithAnnotation:annotation reuseIdentifier:reusePinID] autorelease];

//핀이 떨어지는 애니메이션

dropPin.animatesDrop = YES;

//마커 오른쪽에 (>) 모양 버튼 초기화.

UIButton *infoBtn = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];

dropPin.userInteractionEnabled = TRUE;

dropPin.canShowCallout = YES;

dropPin.rightCalloutAccessoryView = infoBtn;

//마커 왼쪽에 표시할 지점,ATM 아이콘

NSString* markerImg = nil;

if ([mk.markerType isEqualToString:@"0"]){

markerImg = @"hana.png";

dropPin.pinColor = MKPinAnnotationColorGreen;

} else {

markerImg = @"atm.png";

dropPin.pinColor = MKPinAnnotationColorRed;

}

dropPin.leftCalloutAccessoryView = [[[UIImageView alloc

initWithImage:[UIImage imageNamed:markerImg]] autorelease];


//마커 리턴

return dropPin;

}



//어노테이션의 더보기

-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view

calloutAccessoryControlTapped:(UIControl *)control{

BranchMarker *mk = (BranchMarker *) view.annotation;

tempTelNo = nil;

//얼럿메세지 초기화

NSString *alertMessage = [mk.title stringByAppendingString:@"\n"]; 

if ([mk.bussBrAdr length] > 1) //주소

alertMessage = [[alertMessage stringByAppendingString:@"\n"]

stringByAppendingString:mk.bussBrAdr];

if ([mk.trscDrtm length] > 1) //ATM운영 시간

alertMessage = [[alertMessage stringByAppendingString:@"\nATM : "

stringByAppendingString:mk.trscDrtm]; 

NSString* telTitle = nil; //전화걸기 버튼 타이틀.

if ([mk.bussBrTelNo length] > 1){ //전화번호

alertMessage = [[alertMessage stringByAppendingString:@"\n대표전화 : "]

stringByAppendingString:mk.bussBrTelNo];


telTitle = @"전화걸기";

}

tempTelNo = mk.bussBrTelNo;

//얼럿뷰 표시

UIAlertView *confirmDiag = [[UIAlertView alloc] initWithTitle:nil

message:alertMessage delegate:self cancelButtonTitle:@"닫기" 

otherButtonTitles:telTitle, nil];


[confirmDiag show];

[confirmDiag release];

}


//어노테이션의 더보기 (얼럿뷰) 에서 버튼 클릭.

-(void)alertView:(UIAlertView *)alertView 

clickedButtonAtIndex:(NSInteger)buttonIndex{

if (buttonIndex == 1){

NSLog(@"전화걸기 : %@",tempTelNo);


if (tempTelNo != nil){

[[UIApplication sharedApplication

openURL:[NSURL URLWithString:[@"tel:" 

stringByAppendingString:tempTelNo]]];

}

} else if (buttonIndex == 0) {

NSLog(@"닫기");

}

}


#pragma mark LocationManager

//위치가 변경되었을때 호출.

-(void)locationManager:(CLLocationManager *)manager

didUpdateToLocation:(CLLocation *)newLocation 

fromLocation:(CLLocation *)oldLocation {


NSString *strInfo = [NSString 

stringWithFormat:@"didUpdateToLocation: latitude = %f, longitude = %f",

newLocation.coordinate.latitude, newLocation.coordinate.longitude];

NSLog(@"%@",strInfo);


MKCoordinateRegion region; //레젼설정

region = MKCoordinateRegionMakeWithDistance(newLocation.coordinate, 2000, 2000);

MKCoordinateRegion adjustedRegion = [mapView regionThatFits:region];

[mapView setRegion:adjustedRegion animated:YES];

//마지막으로 검색된 위치를 다른곳에서 활용하기 위하여 설정.

self.lastScannedLocation = newLocation; 


//한번 위치를 잡으면 로케이션 매니저 정지.

[self.locationManager stopUpdatingLocation];

[self getBranchDataWithLocation:self.lastScannedLocation]; //화면에 마커찍기

}


//위치를 못가져왔을때 에러 호출.


-(void)locationManager:(CLLocationManager *)manager

  didFailWithError:(NSError *)error{

NSLog(@"locationManager error!!!");

//위치를 못가져왔을땐 현재 지도에 표시된 지역기준으로 지점검색 들어간다~

[self setSearchTypeToBranch];

//에러 다이얼로그 표시.

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"위치기반 지점찾기" message:@"현재위치를 검색할수 없습니다.\n설정 > 일반 > 위치서비스 활성화 되어있는지 확인해주세요.\n\n위치정보를 가져올수 없어도 하단의 아이콘을 통하여 현재 지도의\n영업점/ATM 위치는 검색하실수\n있습니다." delegate:nil cancelButtonTitle:nil otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}


#pragma mark reverseGeocoder

//역지오코더 검색되었을때 UILabel 역지오코딩 내용 표시

-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder

didFindPlacemark:(MKPlacemark *)placemark{


    if (geoLabel != nil){

//혹시 몰라 한번 try 싸줌.

@try {

NSString *geoString = @"";

//locality 서울특별시 subLocality 송파구 thoroughfare 신천동

//지역에 따라 특정 파라메터에 값이 없을 있음. nil체크 하여 표시함.

if (placemark.locality != nil

geoString = [[geoString 

stringByAppendingString:placemark.locality

stringByAppendingString:@" "];

if (placemark.subLocality != nil)

geoString = [[geoString 

stringByAppendingString:placemark.subLocality]

stringByAppendingString:@"\n"];

if (placemark.thoroughfare != nil)

geoString = [geoString

stringByAppendingString:placemark.thoroughfare];

//아무 정보도 받아올수 없으면 나라이름이라도 표시.

if ([geoString length] < 1 && placemark.country != nil)

geoString = placemark.country;

geoLabel.text = geoString; //UILabel 표시

}

@catch (NSException * e) {

//오류 발생하면 UILabel 비워줌.

NSLog(@"reverse GeoCoding error : %@",e);

geoLabel.text = nil;

}

@finally {

}

}

}


//역지오코더 에러 발생시 그냥 로그.


-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder

didFailWithError:(NSError *)error{

    NSLog(@"MKReverseGeocoder has failed.");

}


@end



4. 데이터 받아오는 액션 준비하기.

 지점 데이터는 HTTP통신으로 받아오게 된다.

예를 들어 http://111.11.11.11:8888/getBranch.do?a=123&b=456 이런식으로 URL을 호출하게 되면 서버에서 리턴값이 스트링으로 “S;10;테스트지점;02-123-4567;서울시 구로구 개봉동;....”  이런식으로 세미콜론(;) 으로 구분된 문자로 내려오게 된다.

그러면 프로그램에서 해당 스트링을 잘라서 객체에 잘 집어넣으면 된다. 

이것은 컨덴트 서버와 규격을 맞추어 프로그래밍을 해야한다.

하나은행에서 쓰이는 지점정보 서버와의 통신은 대외비이므로 지도구현과 관계없는 부분은 생략하여 정리한다.

//  BranchMapGetDataAction.h

// HTTP 통신으로 컨덴츠 서버에서 데이터를 받아서 어노테이션에 셋팅하는 액션


#import <Foundation/Foundation.h>


@interface BranchMapGetDataAction : NSObject{

NSString *searchType; //검색조건

NSString *reqLat; //요청 위도

NSString *reqLng; //요청 경도

NSString *reqRange; //요청 범위 (메타 m 단위)

}


@property (nonatomic,retain) NSString *searchType;

@property (nonatomic,retain) NSString *reqLat;

@property (nonatomic,retain) NSString *reqLng;

@property (nonatomic,retain) NSString *reqRange;


- (id)initWithSearchType:(NSString *)_searchType andReqLat:(NSString *)

_reqLat andReqLng:(NSString *)_reqLng andReqRange:(NSString*)

_reqRange; //초기화 메소드

- (NSMutableArray*)getData; //데이터를 가져오는 메소드

- (NSString*)generateGeoCode:(NSString*)str; //서버의 응답 스트링 지오코드에 콤마 붙이는 메소드.


@end



//  BranchMapGetDataAction.m


#import "BranchMapGetDataAction.h"

#import "BranchMarker.h"

#import <MapKit/MapKit.h>


@implementation BranchMapGetDataAction

@synthesize searchType,reqLat,reqLng,reqRange;


//초기화 메소드.


(id)initWithSearchType:(NSString *)_searchType 

andReqLat:(NSString *)_reqLat andReqLng:(NSString *)_reqLng 

andReqRange:(NSString*)_reqRange {


if ((self = [super init])) {

        // Custom initialization

self.searchType = _searchType;

self.reqLat = _reqLat;

self.reqLng = _reqLng;

self.reqRange = _reqRange;

    }

    return self;

}


// 결과값 받아다가 어노테이션(마커) 배열로 리턴.

- (NSMutableArray *)getData{

//스테이더스 바에 로딩 표시. (데이터 가져오는 네트워크 상태 표시)

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

//요청타입이 널탕이면 기본적으로 지점검색으로 셋팅.

if (self.searchType == nil || [self.searchType isEqualToString:@""]){

self.searchType = @"0";

}

//요청 URL

NSString *urlString = @"http://1.1.1.1/a/b.jsp?distance=";

urlString = [[urlString stringByAppendingString:self.reqRange]

stringByAppendingString:@"&map_x="];

urlString = [[urlString stringByAppendingString:self.reqLng]

stringByAppendingString:@"&map_y="];

urlString = [[urlString stringByAppendingString:self.reqLat]

stringByAppendingString:@"&svc_type="];

urlString = [urlString stringByAppendingString:self.searchType];

NSURL *url = [NSURL URLWithString:urlString];

NSLog(@"url : %@", urlString);

//리퀘스트 객체.

NSMutableURLRequest *request = [[[NSMutableURLRequest alloc]

initWithURL:url] autorelease];

//레스폰스 객체,에러 객체 준비.

NSURLResponse *response = nil;

NSError *error = nil;

//데이터 받아오기.

NSData* receiveData = [NSURLConnection sendSynchronousRequest:request

returningResponse:&response error:&error];

//받아온 데이터 파싱.

NSString *str = [[NSString alloc] initWithData:receiveData 

encoding:0x80000000 + kCFStringEncodingDOSKorean];

str = [str stringByReplacingPercentEscapesUsingEncoding:

0x80000000 + kCFStringEncodingDOSKorean];


//NSLog(@"DATA GETTED!!! : %@",str);

//에러가 발생하였으면 에러표시.

if(error != nil) {

NSLog(@"%@", [error localizedDescription]);

UIAlertView *alert = [UIAlertView alloc];

[alert initWithTitle:@"에러" message:[NSString 

stringWithFormat:@"서버에 접속할 없습니다.\n%@",

[error localizedDescription]] delegate:self 

cancelButtonTitle:@"확인" otherButtonTitles:nil];

[alert show];

[alert release];

}


//마커배열 준비.

//받아온 스트링을 세미콜론으로 잘라서 배열로 넣어버린다.

NSArray *branchArray = [str componentsSeparatedByString:@";"]; 

NSMutableArray *returnAry = [[NSMutableArray alloc] init]; //리턴할 배열 준비.

NSLog(@"getted branch array size : %d",[branchArray count]);

@try {

//i=2 준것은 첫번째 배열엔 성공여부(S) 두번째 배열엔 받아온 지점 갯수 (int#) 이다

안쓰이므로 무시하고 세번째 배열원소부터 사용하도록한다.

for (int i=2; i<([branchArray count]-1); i+=7) { 

//마커 준비.

BranchMarker *marker = [[BranchMarker alloc] init];

// 셋팅.

marker.bussBrNm = [branchArray objectAtIndex:i];

marker.bussBrTelNo = [branchArray objectAtIndex:i+1];

marker.bussBrAdr = [branchArray objectAtIndex:i+3];

marker.bussBrAdr2 = [branchArray objectAtIndex:i+2];

marker.trscDrtm = [branchArray objectAtIndex:i+4];

//마커에 위도,경도 정보 셋팅.

MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };

region.center.latitude = [[self generateGeoCode:

[branchArray objectAtIndex:i+6]] floatValue];

region.center.longitude = [[self generateGeoCode:

[branchArray objectAtIndex:i+5]] floatValue];

region.span.longitudeDelta = 0.01f;

region.span.latitudeDelta = 0.01f;

marker.coordinate = region.center; //셋팅!

//찾아오시는길은 값이 있을때에만 셋팅.

if ([ marker.bussBrAdr length] > 1

marker.subtitle = marker.bussBrAdr;

marker.markerType = self.searchType; //마커 타입 (지점/ATM)

if ([self.searchType isEqualToString:@"0"]){

//지점이면 이름에다가 "지점" 이라는 글씨 추가로 셋팅.

marker.title = [marker.bussBrNm 

stringByAppendingString:@" 지점"];

} else {

marker.title = marker.bussBrNm;

}

//배열에 추가.

[returnAry addObject:marker];

//마커 릴리즈.

[marker release];

}

}

@catch (NSException * e) {

//가끔 컨덴츠 서버에서 오류가 데이터를 내리는 경우가 있다.에러,보정처리는 알아서~

.....삭제.....

}

@finally {

}




//검색결과가 없을때 오류 표시.

if ([returnAry count] == 0){

NSString *errorTitle = nil;

NSString *errorMsg = @"\n\n네트워크 오류일수 있으니 다른지역으로 이동, 또는 지도를 확대하여\n검색하시거나 잠시 다시 시도해주세요.";

if ([self.searchType isEqualToString:@"0"]){

errorTitle = @"영업점 검색오류";

errorMsg = [[NSString stringWithString:

@"해당 지역에 '영업점' 검색결과가\n없습니다."] stringByAppendingString:errorMsg];

} else {

errorTitle = @"ATM 검색오류";

errorMsg = [[NSString stringWithString:

@"해당 지역에 'ATM' 검색결과가\n없습니다."] stringByAppendingString:errorMsg];

}

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:errorTitle

message:errorMsg delegate:nil 

cancelButtonTitle:nil

otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}

//스테이더스바 로딩 끄기.

[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

//배열 리턴.

return returnAry;

}



// 위도 경도에 콤마 붙이기. ex(37123456 -> 37.123456)

-(NSString*)generateGeoCode:(NSString*)str {

if (str != nil && [str length] >= 8) {

int lastIdx = [str length];

int middIdx = lastIdx - 6;

NSString* s1 =[str substringWithRange:

NSMakeRange (0,middIdx)]; //콤마 앞의 스트링

NSString* s2 =[str substringWithRange:

NSMakeRange (middIdx,6)]; //콤마 뒤의 스트링

NSString *output = [[s1 stringByAppendingString:@"."]

stringByAppendingString:s2]; //콤마 붙여서 리턴

return output;

}

return nil;

}


@end



5. 실행 스크린샷.

최초 실행하면 나오는 안내문구.



현재위치를 표시하며 현재위치 기준으로 영업점들을 찾아서 주루룩 찍어낸다.



현재위치 마커엔 title 로 "현재위치" 라고 셋팅 해 두었다.



지점/ATM 마커를 누르면 간단한 정보가 나온다.



간단한 정보에서 오른쪽 버튼을 누르면 상세한 정보가 나온다.



선택 지점/ATM으로 전화걸기



현위치 말고도 지도를 옮겨 원하는 지역에서 검색을 할 수도 있다.



원하는 지역으로 옮겨가서 하단의 지점/ATM 아이콘을 누르기만 하면 된다.



마지막으로 우리동네도 한번.



6. 해결하지 못한 부분


- MKMapview 의 지도화면에 특정 지점을 눌러서 뭔가 액션을 해주고 싶어 MKMapview를 상속하여 커스텀 맵뷰를 구현했는데 터치이벤트에서 오류 작렬! 그리고 줌인 줌아웃시에 오류가 난다.. ㅠ


- iOS 4부터는 스테이터스 바에 위치서비스 사용 아이콘이 나오는데 분명 LocationManager 를 stop 시켜주었는데도 아이콘이 계속 떠있다. Tweeter 어플등을 보면 현위치를 가져오고 난 뒤에는 아이콘이 사라지는것 같은데..

solution : 

mapView.showsUserLocation = YES;

이 문제였다. 현위치가 필요하지 않을때 적당한 시점에서 NO 를 넣어주면 현재위치 가져오는것을 종료하고 스테이더스바에 위치서비스 아이콘이 없어지게 된다.


- 3G 네트워크 등. 인터넷 상태가 불안정할때 처리에 오래 걸리는 문제. 데이터를 받아오는 순간에 프로그램이 정지된것 처럼 보인다. iOS 도 스레드를 돌려서 백그라운드로 돌려야 하나? 이건 다음버전에서 고민.


- 이번 글은 보기좋게 PDF파일로 첨부합니다.




2010.11.17 모근원 (Geunwon,Mo)

mokorean@gmail.com

twitter : @mokorean

http://Lomohome.com



* 추가  TIP : Google 로고 옮기기.
현재 툴바로 구글로고를 가려지게 되어있는데 이게 앱스토어에 올라갈경우 리젝사유가 된다고 한다.
그래서 바로 구글링해서 구글로고 옮기는법을 찾았다.



먼저 MKMapView 에다가 메소드를 추가할것이니 카테고리로 구현하도록 한다.
나는 클래스를 추가하였다.

//

//  BranchMapMKMapView.h

//  BranchMap

//

//  Created by Geunwon,Mo on 10. 11. 18..

//  Copyright 2010 Lomohome.com. All rights reserved.

//


#import <MapKit/MapKit.h>


@interface MKMapView (Additions) 


- (UIImageView*)googleLogo;


@end


//

//  BranchMapMKMapView.m

//  BranchMap

//

//  Created by Geunwon,Mo on 10. 11. 18..

//  Copyright 2010 Lomohome.com. All rights reserved.

//


#import "BranchMapMKMapView.h"



@implementation MKMapView (Additions)


- (UIImageView*)googleLogo {

UIImageView *imgView = nil;

for (UIView *subview in self.subviews) {

if ([subview isMemberOfClass:[UIImageView class]]) {

imgView = (UIImageView*)subview;

break;

}

}

return imgView;

}


@end


카테고리 추가후 지도를 구현하는 ViewController  (여기서는 BranchMapViewController.m)에다가 카테고리로 추가한 메소드를 뷰가 나오기전에 실행해서 구글로고의 위치를 변경하도록 한다.

#import "BranchMapGetDataAction.h"


....생략.....



////////////// Custom MapView Category start ///////////////

float _toolBarPositionY = 0.0f; //<- 이놈은 viewDidLoad 안에서 값을 셋팅한다. 현재 화면 툴바의 Y좌표값.


- (void)viewDidAppear:(BOOL)animated {

NSLog(@"view did appear");

[self relocateGoogleLogo];

}


- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {

[self relocateGoogleLogo];

}


- (void)relocateGoogleLogo {

UIImageView *logo = [mapView googleLogo];

if (logo == nil)

return;

CGRect frame = logo.frame;

frame.origin.y = _toolBarPositionY - frame.size.height - frame.origin.x;

logo.frame = frame;

}

////////////// Custom MapView Category end ///////////////



Posted by 모근원
  1. 이전 댓글 더보기
  2. 아쿠아마린 2011.01.03 17:12

    감사합니다. ^^ 유익한 정보를 포스팅 해주셔서 ~ 잘 보고, 공부하고 갑니다. 참!!! pdf 파일 다운 받아 보았습니다. ^^ 감사합니다.
    이렇게 먼저 공부해서 정보를 공유한다는 것이 쉽지 않으셨을 텐데 ^^ 감사합니다. ~

  3. 비밀댓글입니다

  4. 좋은 자료 잘 보았습니다.
    맵뷰 처음사용하는데 큰 도움이 되었습니다.

  5. UIActivityIndicatorView spiner가 안보이는데 원래 그런건가요?
    아님 뭔가 다른 설정을 해야 하나요??
    웹페이지에서는 잘나오는데.. Mapview에서는 UIActivityIndicatorView 보이지 않는데 어떻게 된걸까요??

    • 음... 그런가요 저도 잘 모르는부분이라 검색을 좀 해봐야 겠네요 ㅠㅠ

    • 알자님.. 다시 보실지 모르겠지만 위의 프로그램에서는 문제가 있습니다.
      저도 이 글을 작성후에 안것인데 UIApplication 의 keyWindow 를 이용하시면 화면 최상위에 인디게이터를 띄우실수 있을겁니다.
      조만간 시간이 나면 다른글로 정리해서 올릴수 있으면 좋겠네요.

  6. 김태우 2011.05.01 13:45

    많은 도움이 되었습니다..
    그런데 막히는 부분이 한두가지가 아닌데..
    소스코드 를 요청해도 될런지요....부탁좀 드리겠습니다...
    아..저는 학생이며 상업적용도가 아닌 개인공부용으로 사용할 목적입니다.
    hananet0282@naver.com

    • 죄송합니다. 코드는 제공해드리지 않습니다.
      여러모로 부딫히며 코딩해보시는것이 공부에 더 도움에 되리리 믿습니다.

  7. 황호성 2011.05.10 13:53

    좋은 정보 감사합니다. 주석이 너무 잘되어있어서 이해하기 순조로웠습니다.

  8. 너무 정리 잘해놓으셨네요..
    잘봤습니다.

  9. 좋은 정보 감사합니다.하면서 궁금한게 생겼는데 ViewBase 기반으로 하면 처음으로 생기는 파일이 4개가 생기는데 프로젝트명AppDelegate.h,m 프로젝트명ViewController.h,m 이렇게 4개요 근데 위에 보면 프로젝트.h 여기에다가 작성하셨는데요 기본적인데다가 하면 @interface 부분도 좀 다르고 한데 좀 상세히 정보를 얻을수 있을까 하는데 괜찮나요?

    • 위의 내용은 전혀 프로젝트.h에 작성하지 않았습니다.
      실제로 저 위의 프로그램들은 큰 프로젝트의 일부분이구요.

      BranchMarker.h , .m <-이건 데이터를 담을 객체입니다.
      BranchMapViewController.h , .m <- 이건 실제 뷰컨트롤러입니다. 특별이 다른 뷰를 만들어 쓰지 않기때문에 뷰 컨트롤러에서 뷰의 설정까지도 다 합니다만...
      BranchMapGetDataAction.h ,.m <- 이건 데이터 통신을 위한 객체이구요.

      이렇게 만들어진 뷰를 처음프로젝트를 생성하셔서 보시려면 처음에 나오는 RootViewController (여기서 말씀하신건 프로젝트명ViewController가 되겠네요) 에 import 로 BranchMapViewController.h 를 하신담에 해당 뷰를 addSubview 아시면 될겁니다.

      제가 여기서 프로그램 개발의 AtoZ 까지 설명을 못드려서 보시는데 불편이 있으셨을거라 생각됩니다. 다른쪽의 예제도 참고해주면서 이 MKMapView 의 예제를 참고하시면 공부하시는데 도움이 될거라 생각됩니다.

      즐건 코딩되세요 :-)

  10. 너무 설명이 잘 되어 있어서, 만약 링크를 잃어버리면 아까울 것 같아서 따로 제 게시판에 복사했습니다. 물론 출처는 밝혔고요.. 만약 원하지 않으시면 삭제하겠습니다. ^^

  11. 위 설명 자세히 잘적어 두셨네요^^ 너무나도 좋은 참고 하였습니다.
    3G에서 느린이유는 아무래도 통신하여 데이터 표시하는 부분처리가 동기식으로 되어있어서 그런것 같네요.
    //데이터 받아오기.

    NSData* receiveData = [NSURLConnection sendSynchronousRequest:request

    returningResponse:&response error:&error]; <== 요부분.

  12. 윗분 말씀처럼 동기로 구현하셔서 그런겁니다. 비동기 방식으로 바꿔보세요~. 관련해서는 구글링~

  13. 김태우 2011.08.13 07:53

    안녕하세요.. 초보 개발자입니다..

    저는 좌표값이 있어서 http 쪽을 빼버렸습니다..

    - (NSMutableArray *)getData 메소드 에서 마커 리턴도 잘됩니다.

    하지만 현재 위치만 표시할뿐.. 좌표에 마커가 찍히지 않습니다..

    어디를 확인해봐야 되는것인지요..

    • 좌표계가 달라서 실제로 좌표가 엉뚱한데 찍혀있을수 있습니다.
      맵을 최대로 축소해보면 아마 다른위치에 찍혀있거나 할것 같아요.
      마커의 객채가 실제로 생성되었는지 확인해보시면 좋을듯 합니다.

  14. 소스코드중 BranchMapGetDataAction 에서 요청 하는 URL 주소가 NSString *urlString = @"http://1.1.1.1/a/b.jsp?distance=";
    urlString = [[urlString stringByAppendingString:self.reqRange]
    stringByAppendingString:@"&map_x="];
    urlString = [[urlString stringByAppendingString:self.reqLng]
    stringByAppendingString:@"&map_y="];
    urlString = [[urlString stringByAppendingString:self.reqLat]
    stringByAppendingString:@"&svc_type="];
    urlString = [urlString stringByAppendingString:self.searchType];
    NSURL *url = [NSURL URLWithString:urlString];

    맞나요..?
    이에대한 정보를 확인하고 싶습니다.

  15. 비밀댓글입니다

  16. 냐하송 2011.12.02 16:06

    좋은자료 감사합니다.

  17. paxdreamer 2012.04.03 13:24

    정말 좋은 포스팅 입니다. 지금 구현해 보려고 합니다. 정리까지 잘해주시니 감사하네요^^
    음..구글맵api에 대해 질문이 있는데...혹시 경로검색에 대해 구현해 보셨는지요??
    국내에서는 구현이 안되서 미국 도시를 기준으 만들어 보려고 하는데...어떻게 해야할지 고민입니다.
    구글링해도 정보가 별로 안보이고....만들어 보셨을꺼 같아서 질문 드립니다.

  18. 좋은 자료 감사합니다 ^^ 지도에서 사용되는 거의 모든기능을 보기 좋게 구현하셨네요~!

  19. 흠바바 2013.01.09 21:08

    모근원님^^강좌 글잘봤습니다^^
    제가 이번에 오프라인맵을 개발해야 하는데요..
    혹시 오프라인맵해보셨나요?

    openstreetmap을 이용해서 아이폰에서
    아이폰에서 인터넷이되지않아도 되게 하는거 입니다.

  20. MKReverseGeocoderDelegate 가 deprecated 되었더라구요 . 작업이 오래되셔서 관심가지실진 모르겠지만
    다른사람들에게 그에 관한 소식과 대처방안등을 서술해주시면 많이들 관심 가질것같네요. 잘보고 갑니다

    • 글이 올라간게 3년정도 되었고.. 그간 애플맵도 많은 변화가 있었으니 deprecated 된것이 많이 있겠네요 ^^
      감사합니다. 다른분들도 이 글은 그저 참고용으로만 사용하실거라 믿습니다.

  21. 큰 도움이 되었습니다.정말 감사합니다!!

원래 하나은행 스마트폰 뱅킹의 위치기반(LBS) 지점찾기는 WebView 에서 Google Map API 를 통하여 구현이 되어있었다.

아이폰에서는 이게 잘 돌아가는데... 안드로이드에서는 기계마다 되는것도 있고, 안되는것도 있고..

영 껄쩍지근 했다. (사실 이번에 출시한 갤럭시 S 에서 안돌아가는 이유가 가장 컸지..)


그래서 내친김에 WebView 에서 구현하지말고 MapView 로 구현해버리기로 했다.

이틀정도 작업한거라 고쳐야할 부분도 많고 (특히 Runnable 로 구현한 길게 누르기는...) 버그도 좀 있지만

일단 돌아가니, 이제까지 한것을 까먹지 않으려고 블로그에 정리를 해 둔다.


* OSX 의 Pages 를 이용하여 블로그 글을 정리했는데.. 웹으로 카피하니까, 이게 폰트 색 정의 해둔것이 죄다 깨졌다.

  감안해서 참고하시길.. 마지막에 PDF 로 첨부해둔다..



MapView 추가하기.


AndroidMenifest.xml 을 수정한다.


<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


윗줄부터 INTERNET 은 구글지도API 가 인터넷연결을 통하여 데이터를 받아오기때문에 추가해주어야하고

ACCESS_***_LOCATION 은 현재위치를 프로바이더(네트웍,GPS)를 통하여 받아오기 위해 추가해준다.


그 다음, <application> 태그 안쪽에 수정되어야 할 항목이다. 먼저,


<!-- 안드로이드 맵뷰를 사용하려면 라이브러리를 추가한다. -->

<uses-library android:name="com.google.android.maps" />


라이브러리를 사용함을 선언해준다. 그리고 액티비티 선언을 하나 추가해준다. 


<!-- 지점찾기 맵 -->

<activity android:name=".BranchMapActivity" android:screenOrientation="portrait">

<intent-filter>

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>


다음은 레이아웃을 그려줄 branchmap.xml 에서 쓰인 맵뷰 부분의 선언이다.


...

<com.google.android.maps.MapView

android:id="@+id/mapView"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:enabled="true"

android:clickable="true"

android:apiKey="0kiM******" /> <!-- API 키를 등록해야 동작한다. -->

...


위에서 쓰인 android:apiKey 는 각 개발머신에 따라 따로 받아서 적어넣어야한다.

API Key 를 넣지않으면 동작은 하지만 지도데이터를 받아오지 않는다.

여기서 따로 설명은 하지 않고, 다음의 링크를 따라가면 MD5 값을 가지고 구글 API 키를 받아오는법이 잘 설명이 되어있다.


http://www.mobileplace.co.kr/1070


참고로 나는 맥을 사용해서 개발을 진행하였기때문에 다음의 명령어로 MD5키를 받아왔다.


keytool -list -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android

받아온다음 Google Map API 사이트 (http://code.google.com/intl/ko-KR/android/maps-api-signup.html)에서 API를 받아와서 XML 에 넣어주면 된다.


이제 맵뷰를 표시하는 핵심 클래스인 BranchMapActivity.java 의 내용중 맵뷰에 관련한 부분을 정리해본다.

public class BranchMapActivity extends MapActivity {


맵을 표시하는 액티비티는 MapActivity 를 상속받아 구현한다.


다음은 전역변수로 사용되어진 변수 중, 지도의 표시에 관련한 변수들이다.


private MapView mapView; //맵뷰 객체 

private List<Overlay> listOfOverlays; //맵에 표시된 오버레이(레이어)들을 가지고 있는 리스트

private String bestProvider; //현재 위치값을 가져오기위한 프로바이더. (network, gps)


private LocationManager locM; //위치 매니저

private LocationListener locL; //위치 리스너

private Location currentLocation; //현재 위치

private MapController mapController; //맵을 줌시키거나, 이동시키는데 사용될 컨트롤러


private LocationItemizedOverlay overlayHere; //현재위치 마커가 표시되어질 오버레이

private LocationItemizedOverlay overlayBranch; //지점위치 마커들이 표시되어질 오버레이

private List<BranchInfoDTO> brList; //지점리스트


다음은 onCreate 메소드에서 맵뷰에 관련한 부분이다.


@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);


...


setContentView(R.layout.branchmap); //맵액티비티 xml을 풀어헤친다.


...


overlayHere = null;

overlayBranch = null; //각 오버레이 초기화


...


mapView = (MapView) findViewById(R.id.mapView); //맵뷰 객체를 가져온다.

mapView.setBuiltInZoomControls(true); //줌인,줌아웃 컨트롤을 표시한다.


mapController = mapView.getController(); //맵컨트롤러를 가져온다.

mapController.setZoom(17); //초기 확대는 17정도로..


//위치 매니저를 시스템으로부터 받아온다.

locM = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

//사용가능한 적절한 프로바이더를 받아온다.

//network (보통 3G망,Wifi AP 위치정보)또는 gps 둘중 하나로 설정된다.

bestProvider = locM.getBestProvider(new Criteria(), true);


//기기에 가지고 있는 마지막 위치정보로 현재위치를 초기 설정한다.

currentLocation = locM.getLastKnownLocation(bestProvider);

//위치 리스너 초기화

locL = new MyLocationListener();

//위치 매니저에 위치 리스너를 셋팅한다.

//위치 리스너에서 10000ms (10초) 마다 100미터 이상 이동이 발견되면 업데이트를 하려한다.

locM.requestLocationUpdates(bestProvider, 10000, 100, locL); 


//처음에 한번 맵뷰에 그려준다.

updateOverlay(currentLocation);

}


위에서 한번 언급된 MyLocationListener 는 액티비티 클래스안에 인너클래스로 구현한다.

리스너는 로케이션 매니저에 추가되어 GPS 나 네트워크로부터 위치정보 변경되는것을 감시하게 된다.


public class MyLocationListener implements LocationListener {


@Override

public void onLocationChanged(Location location) {

//위치 이동이 발견되었을때 호출될 메소드.

//위의 설정에서 10초마다 100미터 이상 이동이 발견되면 호출된다.

updateOverlay(location);

}


@Override

public void onProviderDisabled(String provider) {

Log.d(LOG_TAG, "GPS disabled : " + provider); 

}


@Override

public void onProviderEnabled(String provider) {

Log.d(LOG_TAG, "GPS Enabled : " + provider);

}


@Override

public void onStatusChanged(String provider, int status, Bundle extras) {

Log.d(LOG_TAG, "onStatusChanged : " + provider + " & status = "

+ status);

}


}


다음은 내가 구현한 지도그려주기 액티비티의 꽃이라 할수 있는 updateOveray 메소드이다.

요청을 받으면 Location 객체 (위치)를 기준으로 현재위치 마커를 찍고, 지점리스트를 HttpClient 를 통하여 통신해서 받아온후 지점들의 마커를 표시하게 된다.


protected void updateOverlay(Location location) {

//기존에 화면에 찍어둔 오버레이 (마커들)을 싹 지운다.

listOfOverlays = mapView.getOverlays(); //맵뷰에서 오버레이 리스트를 가져온다.

if (listOfOverlays.size() > 0) {

listOfOverlays.clear(); //오버레이가 있을때 싹 지워준다.

Log.d(LOG_TAG, "clear overlays : " + listOfOverlays.size());

} else {

Log.d(LOG_TAG, "empty overlays");

}


//Location 객체를 가지고 GeoPoint 객체를 얻어내는 메소드

GeoPoint geoPoint = getGeoPoint(location); 

//현재위치를 표시할 이미지

Drawable marker;


//실제 운영소스엔 분기하여 현재위치와 선택위치 이미지를 변경하게 되어있다.

marker = getResources().getDrawable(R.drawable.icon_here); 

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


//LocationItemizedOverlay 를 이용하여 현재위치 마커를 찍을 오버레이를 생성한다.

overlayHere = new LocationItemizedOverlay(marker);

//touch event 의 null pointer 버그를 방지하기 위해 마커를 찍고 바로 populate 시켜준다.

overlayHere.mPopulate();

//현재위치를 GeoCoder 를 이용하여 대략주소와 위,경도를 Toast 를 통하여 보여준다.

String geoString = showNowHere(location.getLatitude(), location.getLongitude() , true);


//현재위치 마커 정의

OverlayItem overlayItem = new OverlayItem(geoPoint, "here", geoString);

overlayHere.addOverlay(overlayItem); //현재위치 오버레이 리스트에 현재위치 마커를 넣는다.


// 지점정보를 HTTP통신을 통해 서버에서 받아와서 전역변수인 brList (지점리스트)에 넣는다.

// 성능을 고려하여 쓰레드로 구현이 되어있다.

// 고다음 지점리스트 오버레이에 넣고 화면에 찍어주는 메소드.

showBranchMarker(location.getLatitude(), location.getLongitude(),

this.searchType, SEARCH_RANGE);


// 맵뷰에서 터치이벤트를 받을 오버레이를 추가한다.

// 특정지점을 오래 눌렀을때 특정 지점 기준으로 재검색을 하기 위하여 터치이벤트를 받아와야한다.

mapView.getOverlays().add(new MapTouchDetectorOverlay());


// 마지막으로 생성된 오버레이레이어를 맵뷰에 추가한다.

mapView.getOverlays().add(overlayHere);

mapView.getController().animateTo(geoPoint); //현재위치로 화면을 이동한다.

mapView.postInvalidate(); //맵뷰를 다시 그려준다.

}


조금 복잡하고 지저분하게 구성되어있어 퍼포먼스는 조금 떨어진다. 개선의 여지가 있다.

시간나면 수정해보자...


다음은 updateOverlay 메소드에서 사용되었던 getGeoPoint 메소드 전문이다.


private GeoPoint getGeoPoint(Location location) {

if (location == null) {

return null;

}

Double lat = location.getLatitude() * 1E6;

Double lng = location.getLongitude() * 1E6;

return new GeoPoint(lat.intValue(), lng.intValue());

}


별것 없다. 주의해야할점은 GeoPoint 객체는 위도, 경도 표시에 1E6 을 곱해줘야한다는것이다.


그리고 마커를 생성하고 오버레이에 표시, 그리고 마커를 눌렀을때 이벤트를 발생시키는 클래스이다.

인너클래스로 구현하였다.


protected class LocationItemizedOverlay extends

ItemizedOverlay<OverlayItem> {

private List<OverlayItem> overlays;


public LocationItemizedOverlay(Drawable defaultMarker) { //오버레이 생성자

//마커 이미지의 가운데 아랫부분이 마커에서 표시하는 포인트가 되게 한다.

super(boundCenterBottom(defaultMarker)); 

overlays = new ArrayList<OverlayItem>();

}


@Override

protected OverlayItem createItem(int i) {

return overlays.get(i);

}


@Override

public int size() {

return overlays.size();

}


public void addOverlay(OverlayItem overlay) {

overlays.add(overlay);

//null pointer 버그때문에 오버레이 아이템 추가후 가능한 빨리 populate 해줘야한다.

populate(); 

}


@Override

protected boolean onTap(int index) {


//마커를 눌렀을때 발생시킬 이벤트 메소드이다.


if ("here".equals(overlays.get(index).getTitle())) {

//현재 위치일 경우 간단한 토스트 메세지를 보여준다.

Toast.makeText(getApplicationContext(),

overlays.get(index).getSnippet(), Toast.LENGTH_SHORT)

.show();

} else {

//지점선택일 경우 다이얼로그를 통하여 지점정보를 보여준다.

//‘전화걸기’ 버튼으로 지점으로 전화거는 기능도 추가되어있다.

//맵뷰에 관련한 소스가 아니어서 이곳에서는 표시 하지 않는다.

...

}


return true;

}


//외부에서 마커의 populate 를 해주기 위한 메소드.

public void mPopulate() {

populate();

}

}



지점 정보를 HTTP 통신을 통해 가져오는 메소드이다.

HTTP 통신시 랙현상을 없애기위해 쓰레드로 구현을 해봤다.

근데 스레드가 생각한대로 동작하진 않는것 같다. 잘못쓰고 있는것일까... -_-


private void showBranchMarker(Double lat, Double lng, String searchType,

String searchRange) {


GetMapDataThread excuteThread = new GetMapDataThread(getMapdataHandler,

lat, lng, searchType, searchRange);

excuteThread.start();

}



실제 HTTP통신을 하는 클래스를 호출하는 쓰레드이다.

HTTP 통신 부분은 지도표시와 상관이 없기때문에 여기서 소스를 게시하지는 않는다.

다만 기존에 HTTPConnection 으로 구현되어있던 HTTP 통신을 HTTPClient 로 변경하니까

퍼포먼스도 훨신 좋아지고 불필요한 커넥션을 줄일수 있었다.


private class GetMapDataThread extends Thread {


private Handler tHandler;


private Double lat, lng;

private String searchType;

private String searchRange;


public GetMapDataThread(Handler tHandler) {

this.tHandler = tHandler;

}


public GetMapDataThread(Handler tHandler, Double lat, Double lng,

String searhType, String searchRange) {

this(tHandler); //스레드 처리 완료후 지도에 가져온 지점정보를 가지고 마커를 찍어줄 핸들러

this.lat = lat; //위도

this.lng = lng; //경도

this.searchType = searhType; //검색조건 (0 : 지점, 1: ATM)

this.searchRange = searchRange; //검색범위 단위는 m(미터)이다.

}


@Override

public void run() { //스레드 실행~


Bundle bundle = new Bundle();


try {

//전역변수로 선언한 지점 리스트를 준비한다. BranchInfoDTO 는 도메인이다.

brList = new ArrayList<BranchInfoDTO>(); 

brList = gdA.getMapData(lat.toString(), lng.toString(),

searchType, searchRange);

//gdA 클래스는 HTTP 통신을 해서 지점정보를 가져오는 클래스이다.

//여기서는 설명하지 않았다. onCreate 에서 생성했다.


bundle.putBoolean("SUCCESS_KEY", true); //성공하면 번들에 성공메세지 셋팅


} catch (Exception e) {

...


bundle.putBoolean("SUCCESS_KEY", false); //실패하면 false 이다.

// ignore


} finally {

try {

Message msg = tHandler.obtainMessage();

msg.setData(bundle);

tHandler.sendMessage(msg); //핸들러에 메세지를 보낸다.


interrupt();


} catch (Exception e) {

// ignore

}

}


}

}



스레드에서 HTTP 통신을 통하여 가져온 지점정보를 가지고 지도에 지점 마커들을 찍어주고 오버레이에 추가하는 핸들러이다.


final Handler getMapdataHandler = new Handler() {

public void handleMessage(Message msg) {


if (msg.getData().getBoolean("SUCCESS_KEY")) {  // HTTP 통신이 성공적으로 이루어졌을때.


// draw branches

Drawable branchMarker;


int markerType = 0;


if ("0".equals(searchType)) { //검색조건에따라 마커이미지를 지점,ATM 중에 선택

markerType = R.drawable.icon_branch;

} else if ("1".equals(searchType)) {

markerType = R.drawable.icon_atm;

}


branchMarker = getResources().getDrawable(markerType);


branchMarker.setBounds(0, 0, branchMarker.getIntrinsicWidth(),

branchMarker.getIntrinsicHeight());


Double lat, lng;


//지점 마커들을 그려줄 오버레이를 준비한다.

overlayBranch = new LocationItemizedOverlay(branchMarker);

overlayBranch.mPopulate();


StringBuilder sb;

//반복문을 돌면서 마커들을 오버레이에 추가한다.

//나중에 마커를 눌렀을때 다이얼로그에 지점 정보를 보여주기위해 스니펫에 몇가지 정보를

//string 으로 전달한다.


for (BranchInfoDTO d : brList) {


lat = Double.parseDouble(d.getYCord()) * 1E6;

lng = Double.parseDouble(d.getXCord()) * 1E6;

GeoPoint branchGeoPoint = new GeoPoint(lat.intValue(),

lng.intValue());


sb = new StringBuilder();

sb.append(d.getBussBrNm()).append(";")

.append(d.getBussBrTelNo()).append(";")

.append(d.getBussBrAdr()).append(";")

.append(d.getTrscDrtm()).append(";")

.append(d.getBussBrAdr2());


// Create new overlay with marker at geoPoint

OverlayItem overlayItem = new OverlayItem(branchGeoPoint,

"branch", sb.toString());

overlayBranch.addOverlay(overlayItem);

}


}

//마커 찍은것이 없으면 오류 메세지를 토스트로 보여준다.

if (overlayBranch.size() < 1){

Toast.makeText(getApplicationContext(),

"검색결과가 없거나 통신장애 입니다.\n'메뉴'버튼을 눌러 조건을 변경하여 다시 검색해 주세요.",

Toast.LENGTH_LONG).show();

}


//지점 오버레이를 맵뷰 오버레이에 최종적으로 추가해준다.

if (overlayBranch != null) {

mapView.getOverlays().add(overlayBranch);

mapView.postInvalidate();

}


};

};


토스트 메세지로 현재 주소와 위도,경도를 잠시 표시해주는 메소드.


private String showNowHere(double lat, double lng , boolean showOption){

StringBuilder geoString = new StringBuilder();

try {

Geocoder goecoder = new Geocoder(getApplicationContext(),

Locale.getDefault());


Address adr = goecoder.getFromLocation(lat,

lng, 1).get(0);


if (adr.getLocality() != null) geoString.append(adr.getLocality()).append(" ");

if (adr.getThoroughfare() != null) geoString.append(adr.getThoroughfare());

if (!"".equals(geoString.toString())) geoString.append("\n\n");

} catch (Exception e) { }

geoString.append("위도 : ").append(lat).append(" ,경도 : ").append(lng);

if (showOption){

Toast.makeText(getApplicationContext(), geoString.toString(),

Toast.LENGTH_SHORT).show();

}

return geoString.toString();

}


캡춰 화면에서 ‘서울특별시 신천동’과 위,경도가 떠있는 토스트이다.

그런데 ‘송파구’ 를 어떻게 가져오는지 모르겠다 -_-;;



이 다음은 화면에서 터치 이벤트를 받아올 오버레이이다.

맵뷰에서 특정지점을 누르고 있으면 현재위치가 아닌 특정지점을 기준으로 지점정보를 검색해오려고 만든 오버레이인데 길게 누르는 이벤트를 받아오는 방식이 좀 어거지이다.

분명 이부분은 개선이 되어야 할것이다.


public class MapTouchDetectorOverlay extends Overlay implements

OnGestureListener {

private GestureDetector gestureDetector;


//onTouchEvent 의 ACTION_DOWN 등을 가지고 직접 처리 하지 않고

//제스처들을 쉽게 캐치할수있는 리스너이다.

private OnGestureListener onGestureListener


private static final long LOOOOONG_PRESS_MILLI_SEC = 1500; // 1.5초정도를 길게누름으로 인식한다.


// for touch timer

private Handler mHandler;

private long touchStartTime;

private long longPressTime;

private MotionEvent globalEvent;


//생성자

public MapTouchDetectorOverlay() {

gestureDetector = new GestureDetector(this);

init();

}


public MapTouchDetectorOverlay(OnGestureListener onGestureListener) {

this();

setOnGestureListener(onGestureListener);

init();

}


//생성자들이 호출할 초기화 함수

private void init() {

mHandler = new Handler();

globalEvent = null;

}


//길게누름을 감지할 스레드

private Runnable looongPressDetector = new Runnable() {

public void run() {

//화면을 누르고 있던 시간

long touchHoldTime = longPressTime - touchStartTime

if ((globalEvent != null)

&& (touchHoldTime > (LOOOOONG_PRESS_MILLI_SEC - 200))) { //조건중에 200ms 를 빼고 검사하는것은 기기마다 성능이 달라서 약간의 여유를 준것이다.

Log.d(LOG_TAG, "loooooong press detected!");

float x = globalEvent.getX();

float y = globalEvent.getY(); //화면에서 눌려있던 지점을 받아온다.


GeoPoint p = mapView.getProjection().fromPixels((int) x,

(int) y); //눌려있던 지점을 위도 경도로 바꿔준다.

Location selectedLocation = new Location(currentLocation);

selectedLocation.setLatitude((p.getLatitudeE6() / 1E6));

selectedLocation.setLongitude((p.getLongitudeE6() / 1E6));

currentLocation = selectedLocation;


locM.removeUpdates(locL); //현재위치 리스너를 잠시 없애버린다.

udateOverlay(currentLocation); //지점 재검색 및 마커 다시 표시

showNowHere((p.getLatitudeE6() / 1E6) , (p.getLongitudeE6() / 1E6) , true);

}

}

};


@Override

public boolean onTouchEvent(MotionEvent event, MapView mapView) {

if (gestureDetector.onTouchEvent(event)) {

return true;

}


onLongPress(event);

return false;

}


@Override

public boolean onDown(MotionEvent e) {

if (onGestureListener != null) {

return onGestureListener.onDown(e);

} else {

// start timer

touchStartTime = System.currentTimeMillis();

mHandler.postDelayed(looongPressDetector,

LOOOOONG_PRESS_MILLI_SEC);

//1.5초 있다가 길게누름을 체크해본다.

}


return false;

}


@Override

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

float velocityY) {

if (onGestureListener != null) {

return onGestureListener.onFling(e1, e2, velocityX, velocityY);

}

return false;

}


@Override

public void onLongPress(MotionEvent e) {

if (onGestureListener != null) {

onGestureListener.onLongPress(e);

}


//화면을 누르고 있으면 onLongPress 가 호출되는데 호출될때마다 체크할 시간을 변수에 넣는다.

//이부분이 퍼포먼스 하락에 영향을 줄 것 같다.

globalEvent = e;

longPressTime = System.currentTimeMillis();


}


@Override

public boolean onScroll(MotionEvent e1, MotionEvent e2,

float distanceX, float distanceY) {

if (onGestureListener != null) {

onGestureListener.onScroll(e1, e2, distanceX, distanceY);

}

return false;

}


@Override

public void onShowPress(MotionEvent e) {

if (onGestureListener != null) {

onGestureListener.onShowPress(e);

}

}


@Override

public boolean onSingleTapUp(MotionEvent e) {

if (onGestureListener != null) {

onGestureListener.onSingleTapUp(e);

}

return false;

}


public boolean isLongpressEnabled() {

return gestureDetector.isLongpressEnabled();

}


public void setIsLongpressEnabled(boolean isLongpressEnabled) {

gestureDetector.setIsLongpressEnabled(isLongpressEnabled);

}


public OnGestureListener getOnGestureListener() {

return onGestureListener;

}


public void setOnGestureListener(OnGestureListener onGestureListener) {

this.onGestureListener = onGestureListener;

}


}


완성된 지점찾기의 동작모습.

액티비티를 실행하게 되면 다음과 같이 작동한다.


실행하게 되면 인트로로 다이얼로그를 하나 띄워준다.



현재 위치가 표시되고 현위치 주변의 지점들을 마커로 표시해준다.



확대 축소 컨트롤은 기기에 마다 내장되어있는 디자인에 다르게 표시된다.



마커를 누르게 되면 간단한 지점 정보 다이얼로그가 뜬다.



메뉴 버튼을 누르면 지점, ATM 찾기를 선택할수 있고, 현위치 메뉴를 선택하면 화면을 다시 현위치로 옮겨준다.



화면을 줌아웃 시키고, ATM 찾기로 옵션을 변경시켜보았다.



화면의 특정지점을 누르고 있으면 그 지점을 기준으로 다시 검색을 해온다.



마지막으로 우리동네도 한번 검색해봤다.





* 추가. 2010-7-13

더블탭시 화면 확대와 화면 스크롤시 길게 누르기 취소를 하기 위하여 다음 부분을 추가.


public class MapTouchDetectorOverlay extends Overlay implements
            OnGestureListener , OnDoubleTapListener{


....


@Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
           
            // for Cancle detect loooong touch
            touchStartTime = System.currentTimeMillis() + 5000;
           
            if (onGestureListener != null) {
                onGestureListener.onScroll(e1, e2, distanceX, distanceY);
            }
            return false;
        }


.....


@Override
        public boolean onDoubleTap(MotionEvent e) {
            mapController.zoomIn();
            return false;
        }


.....


}



* 메일주소 등을 적으면서 소스를 달라고 하는 리플들을 보면 눈살이 많이 찌푸려집니다.

본문을 찬찬히 읽어보시고 궁금한점이나 보완해야할점, 토의하고 싶으신 점이 있다면 저도 즐겁게 리플을 달겠지요..

앞으로 소스를 달라고 하는류의 리플은 그냥 제 블로그에서 삭제하도록 하겠습니다.

모쪼록 양해 부탁드립니다.

Posted by 모근원
  1. 이전 댓글 더보기
  2. 안드로이드3일차 2011.08.01 16:15

    페이락님이야 말로 물에빠져건져주면 보따리 내놔!!! 할 분이시군요. 난 위에 소스보면서 오호~~음...아직은 모르겠군...이러면서 혹시...스스가?? 한 일인이지만 솔직히 도둑놈 심보여요~ Copy&Paste 하는 개발잔 오래 못갑니다. 여기 첨 오지만 개념 좀 없으신 분은 첨이라 댓글남깁니다. 공부하자고요~~

  3. 윗분들 모두 좋은 말씀들 감사드리며 새겨듣겠습니다.
    이제 위의 사항으로 더이상 리플은 자제 부탁드리겠습니다.
    앞으로는 조금 더 정보를 드릴 수 있는부분은 이번 일을 참고해서 더 드릴수 있도록 하겠습니다.
    들러주신분들 모두 감사드립니다.

  4. 개발자 연 2011.08.12 16:15

    현재 진행중인 프로젝트에서 이 소스와 동일한 기능을 구현해야 하는 액티비티가 있어 많은 참고가 됐습니다. 한가지 이해가 안가는 부분이 있는데, updateOverlay에서
    호출하는 showBranchMarker 메소드의 SEACH_RANGE 부분입니다. showBranchMarker에서 호출되는 다른 핸들러 메소드들을 뜯어봐도 저 검색범위 란 인자값은 계속 넘겨주기만 할뿐 어디서 어떻게 적용되는지를 모르겠네요....;;
    따로 클래스나 메소드를 만드셨는지요? 저 같은 경우는 Calc라는 두 지점간 거리 계산 메소드를 만들어서 마크 찍은 위치와 지점 마커들의 좌표를 인자로 넘겨 계산을 한 후 리턴되는 값을 상수와 비교하여 값이 맞을 경우에 지점 마커들을 찍어주는 식으로 구현하려고 했는데 어찌된 일인지 해당 액티비티 내에서 if문이나 for문이 전혀 안먹네요 ㅡ;;; SEARCH_RANGE를 어떻게 해결하셨는지 궁금합니다.

    • 답변이 늦었네요.
      지금 소스가 없어서 확인 불가 하지만 SEARCH_RANGE 는 그냥 제가 define 한 상수일겁니다. 실제로 int 값이요..

  5. 비밀댓글입니다

  6. 개발그험난한길 2011.08.26 19:45

    특정 건물 위치에 대하여 제 위치 및 상대 거리, 방위 등을 계산하여 표시하는 어플을 개발 중에 있습니다. 아직 학생이고 졸작으로 하고자 합니다. 저 위에 사진에서 지점들 위치가 표시되었는데 댓글을 읽다 보니 뭐 DB나 CP 같은 생소한 단어와 함께 그런 곳에서 받아와서 HTTP 통신을 통해 String Paser 같은 그런 설명이 보이는데(아 생소하네요 ㅠㅠ)
    그렇다면 위 본문처럼 지점같이 특정 지역 위치를 표시하게 하려면 그 특정 위치 좌표를 담아놓은 어떤 DB같은 게 필요한 가요? 저는 딱 고정되어 불변하지 않는 특정 지역에 위치한 건물만을 가리키면 되어서 구지 DB 같은 게 필요없이 apk 안에만 담아놓으면 된다고 보는데 고견을 내려주심 감사하겠습니다.

    • 특정 건물이 바뀌지 않는다면 굳이 DB를 쓸필요가 없지요.
      위치데이터 1개만 있으면 되니까요.
      하드코딩으로 해도 무관할듯 합니다.

    • 헐.. 2011.12.15 02:08

      단순히 몇개의 특정 위치를 표현하는 것이 목적이시라면 소스 안에 직접 좌표 값 넣고 사용하셔도 충분하실 것 같네용

  7. 김준범 2011.09.06 20:18

    제가 길찾기를 사용하려 합니다
    어떤 지점을 찍고 길찾기를 사용하는게 가능한가요?
    또 혹시 가는 길에서 벗어나면 다시 길을 찾아주고 에러 메시지가 나오게 하는 것이 가능한지.... 질문이 너무 번잡해서 죄송합니다ㅠ.ㅠ
    너무 급한 일이라;;

    • 길찾기 알고리즘은 쉬운게 아닙니다.
      게다가 경로정보를 구글에서 API로 제공하는지도 모르겠네요.
      제일 중요한 길 데이터를 구하시기가 매우 어렵다고 생각이 되네요.

    • 헐.. 2011.12.15 02:11

      구굴 API로 현재 위치와 특정 포인트 사이에 길을 찾아 화면에 오버레이 시키는 방법은 정말 장난이 아닐거라는 생각이 듭니다.
      간단하게 이유를 대자면.. 구글 맵 상에서 도로를 구별할 수 있어야 하는데.. 그냥 맵을 뿐이니까요(구글맵API를 자세히는 몰라서 트래픽 같은 것이 해당 기능을 보조해 줄 수 있지 않을까 생각도 해봤지만 이게 해당 기능을 가능하게 해 줄 수 있다면야 쉽겠지만.. 그게 아니라면.. 정말 난감하지 않을까 생각되네요..-_-)

  8. 저희가 맵뷰를 띄우고 밑에 메뉴를 넣었는데 지금 저희는 맵뷰에 원래 기본 메뉴가 나오고 뒤로 가기를 눌러서 맵이 바둑판으로 나오고 이때 메뉴를 눌르면 저희가 만든 메뉴가 나오거든요...어떡게 되는지 아십니까??

    • 질문은 안드로이드 펍에서 해주세요.
      그부분 잘 모르겠네요. 죄송합니다.

    • 헐.. 2011.12.15 02:17

      저희가 맵뷰를 띄우고 밑에 메뉴를 넣었는데 지금 저희는 맵뷰에 원래 기본 메뉴가 나오고
      -> xml 구성을 확인해 보셔야 하지 않을까요?
      상단 mapview 하단 menu로 생각하고 만들었는데 돌려보니 상단 menu 하단 mpaview 가 나왔다는 의미라면 xml 구성이 잘못 되어서 그런 것이네용

      뒤로 가기를 눌러서 맵이 바둑판으로 나오고
      -> menu 에 있는 뒤로가기를 누르셨다는건지 핸펀의 뒤로 가기를 누르셨다는건지;; menu의 뒤로가기를 눌렀는데 맵이 바둑판처럼 나온 경우라면 뒤로가기에 의해 이동되는 activity나 xml쪽을 확인해보시길 바라며 맵이 바둑판인 경우는 구굴API MD5를 받아오지 않고 기계에서 돌리시면 아마 바둑판만 보일거예용 MD5로 key 받으셔서 map xml에 넣으세용

      이때 메뉴를 눌르면 저희가 만든 메뉴가 나오거든요...어떡게 되는지 아십니까??
      -> 이때 메뉴를 누르면 저희가 나온 #$%^@%@#@ 상황 정리 불가.. ㅎ-_-;;;

  9. 좋은 정보 너무나 감사합니다.~~ 이 내용을 찾아찾아 해매었는데 드디어 ~~~ 감격의 기쁨이~~~ ^^

  10. 나그네 2011.11.09 10:11

    안녕하세요 일단 너무 자료 잘봤구요, ATM 과 지점 마커들은 다른 오버레이에 관리하시는 건가요? 만약 같은 오버레이에서 관리한다면 아이템오버레이를 만들때 마커를 셋팅해주는데 어떻게 같은오버레이에 마커가 2개를 쓸수 있나요?

  11. 나타샤 2011.11.15 02:29

    제가 맵뷰를 띄워서 지금 병원을 검색해서 마커찍어주려고 하는데
    여기서 적절한 답을 찾아가고 싶네요 ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ
    아직 해석이 잘 안되는데 질의를 던질때 어떻게 던질수 있는지 조언점 해주시면 안될까요 ? 구글 맵 써서 어떻게 질의를 던질수 있는지 모르겠네요 ;;;
    대부분 주소로만 던질수 있어서 ;;; 상호명 같은걸론 못 던지네요 ㅠㅠ

    • 구글하고는 연동을 해보지 않아서 그부분은 잘 모르겠네요.
      검색 API 를 제공해 주던가요? 있긴 있는거 같긴 하던데요;;

      http://blog.hansune.com/287

      참조하시면 될듯 합니다.

  12. 오.. 이 부분에서 너무 막혀있었는데 좋은 소스같네요. 1년여나 전에 많드셧다니 대단하세요. 늦게 시작해서 이제 gps부분 하는데 어렵네요 ㅎㅎ

  13. requestLocationUpdate 쪽 부분 버그인지 의미적 오류인지 찾아보다가 이 곳을 우연히 들르게 되었는데 정말 좋은 소스와 주석 잘 보고 갑니다. 중간에 어처구니 없는 댓글 보고 확-_-돌아서 댓글 달기 시작했는데.. 여기저기 마구 달아버렸네요.. ㅎㅎ
    *ps*
    혹시 requestLocationUpdate 부분에서 이상한 점 못 느끼셨나요? 안에 들어가는 인자중에 시간(1/1000 초 기준)과 거리(m) 가 있는데.. 제가 실시간 추적장치를 만들다 보니 GPS 데이터가 원할때 들어오지 않더군요.. 그간 개발할때는 원하는 시간과 이동거리를 넣어주면 해당 시간이나 거리가 움직였을때 업데이트를 하겠지.. 라고 생각했는데.. 뭔가 이상해서 GPS쪽만 간단히 만들어서 돌려봤는데 그렇지 않은듯 하더군요(불확실..-_-) 이 부분에 대해 어떻게 생각하시는지 궁금하네요..

    • 내공이 훌륭하신 분 같습니다
      만들당시엔 딱히 이상한점을 발견하지 못했는데 지금은 OS버전업도 많이되고 이상동작하는 부분도 많을거라 생각합니다 ㅠ
      안드로이드를 떠난지 오래오래 되어서 지금 테스트를 못해보는 상황이네요 ㅠ
      아.. 그리고 다시 보내 requestLocationUpdate 로 계속 현 위치를 추적하는 목적이 아닌 액티비티였다 보니.. (딱 그 당시 주변만 확인하면 되는 '지점찾기' 였지요.) 다니면서 테스트를 해보지 않았었네요.
      도움못드려 죄송합니다 ㅠ 즐 코딩하세요!

  14. 안드로메이드 2012.02.21 14:35

    해당 위치의 주소를 어떻게 하면 가져올 수 있을지 초보자로서 힘들었는데..
    객체를 역추적하여 필요한 부분을 막 붙여 넣기 하니까 되네요.
    좋은 소스를 올려주셔서 정말 감사합니다^^

  15. 안녕하세요 2012.05.09 18:57

    구글맵 떄문에 검색 하다가 들어 오게 되었습니다.
    덕분에 기본적인 위치 정보 및 지도위 마커 표시 등등 구현 했는데요.
    제가 랜덤으로 좌표를 생성 해서 지도 위에 마커 표시를 여러개 해주었을때
    지금 제가 하는거는 100개든 200개든 다 지도에 우선 마커 표시를 해주었는데요.
    이렇게 하니까 마커가 여러개 있을때는 많이 느려지는데요..
    이럴때는 한번에 다 찍는게 아니고 사용자가 볼수 있는 화면에 범위안에 있는 좌표값들만 표시를 할려고 생각 중인데.. 여러개의 마커를 표시 해야 하는 상황일때 제가 생각 하는 방식으로 구현을 해도 될까요? 아니면 더 좋은 방법이 있을까요? 조언을 좀 구하고 싶습니다.. 좋은 자료 감사합니다.

  16. 비밀댓글입니다

  17. 안드로이드 매니아 2012.08.10 14:29


    MapThouchDetector 부분에서

    locM.removeUpdates(locL);
    udateOverlay(currentLocation);
    showNowHere((p.getLatitudeE6() / 1E6) , (p.getLongitudeE6() / 1E6) , true);
    이 부분 이해가 잘안가네요 액티비티에 선언된 두 메소드를 어떻게 가져와 쓰는지 잘 모르겠네요... 워낙 초보자라 그렇습니다

  18. 레드무싸 2012.11.08 22:02

    안녕하세요~개발자 지망생이라기 보다는 궁금함을 주체 못하고 안드로이드 어플을 만들어보다가~ 들르게 되었네요^^ 한 일년..(ㅜㅜ)혼자 구글등을 뒤적뒤적 하면서 배우다보니 실력이 부족하여서 주인장님께서 만들어놓으신 소스를 아직 전부 파악 하지는 못했지만 너무 유용하고 재미있네요~ㅎㅎ 어떤 분께서 말 씀 하셨듯~ 하나를 배우면 열을 더 배워야 한다고 하던 글이 생각 나네요 ㅜㅜ. 소스를 한참 보다가 막혀서 댓글을 보다보니 소스 제공을 운운 하시던 분이 있던데~ㅎㅎ 저도 한참 풀 소스를 찾아다니고 있었는데, 타인이 공들인 것을 갈취하려 했던것같아 부끄러워집니다^^;; 소스부분도 많이 배우고 갑니다만 더 좋은걸 배우고 가네요~

    • 저도 많은분들의 댓글에서 배우는 부분이 참 많았던 포스팅입니다.
      이젠 좀더 양질의 블로그가 많이 생겨서 찾아오시는분들은 잘 없지만,
      이렇게 포스팅을 해두니 나름 책임감 같은것도 생기더라구요.
      사실 소스 제공은 다시 수정을 해서 배포하기가 귀찮았던것이 90% 였구요
      사실 생업이 있는지라 시간을 많이 할애하지 못했던것도 있었습니다.
      지금은 잘 찾아보면 좋은 소스들을 구하기가 쉬우니 구글신께 부탁을 드려보세요 ^^
      저는 지금 개발일로 밥벌어 먹고 있지만 사실 구글 검색해서 소스 찾는것도 일이라고 생각하고 있거든요..

  19. 안녕하세요... 지금 알바 중이라 자세히 읽지 못했으나 유용한것 같아 질문드립니다.
    안드로이드앱에 구글 gps api를 이용해 맵에 표시하는것이 아닌 파일 이름으로 간단한 주소로 출력하고 싶은데 가능할까요?

    • 파일이름이란게 위도 경도인가요?
      파일이름이 무엇을 뜻하는지 잘 모르겠네요..
      위도경도라면 주소로 변경할수 있습니다. 위에 내용이 있네요.
      Geocoder 쪽을 확인해보세요~

  20. 안녕하세요... 구글맵 이용해서 주변음식점 및 마켓을 표시해주는 어플을 만들어 보려고하는데요..
    인터넷 구글 맵에서 파라메타를 넘겨서 검색한것을 넘겨받는걸로 해보려는데요... 그러려면 http통신도 해야되는거 아닌가요.. ㅠ json도 이해도 어렵고 ㅠㅠㅠㅠㅠ 어떻게 해야할지... 방향을 좀 알려주세요..

    • open API를 통해 검색결과를 받아오려면 http 통신 해야죠.. 데이터는 json 으로 받겠지요..
      방향이라... 글쎄요;;
      http 통신이랑 json 을 공부해야겠지요 -_-?
      안드로이드 어플을 공부하시면 자연스레 공부하게 되는것들인데 참고하시는 교과서를 찬찬히 살펴보세요. 답이 다 있을겁니다.

  21. 행복을 찾아가야 행복도 다가옵니다.행복을 찾아가야 행복도 다가옵니다.행복을 찾아가야 행복도 다가옵니다.