iPhoneアプリでGoogleの逆ジオコーディングを使ってMKPlacemarkを構築するには?(日本国内版)
iPhoneアプリで逆ジオコーディングを行うMKReverseGeocoderには、PBRequesterErrorDomainエラーで突然逆ジオコーディングができなくなる問題があることがわかった。
琴線探査: iPhoneの逆ジオコーディングでPBRequesterErrorDomainエラー。何故?
そこで、MKRverseGeocoderのfailover用として「The Google Geocoding Web Service」を使って逆ジオコーディングする方法を試した。
The Google Geocoding Web Service - Google Maps API Web Services - Google Code
結果は上々。逆ジオコーディングの精度も高く、MKReverseGeocoderで取得できない情報もその気になれば取得できることがわかった。ただ問題は、Googleの逆ジオコーディングの結果から、いかにMKPlacemarkを構築するか、ということだった。
アプリケーションによっては一つの文字列になっている「Formatted Address」さえあれば事足りる場合もあるけど、今回はGoogleの逆ジオコーディングの結果を解析して、国、県、郡、区、市、村、町・字、街区、地番の各レベルごとに情報を認識する必要があった。
住所の扱いは非常に複雑で、とりあえず日本国内だけサポートするのが精一杯だったのだけど、例えばこのようにすれば可能だ。
このコードをコンパイルするには、特にStig Brautaset氏の「json-framework」が必要だ。
琴線探査: iPhoneアプリでJSONを簡単に扱うには?「json-framework」 by Stig Brautaset
プログラム的に日本国内の数百地点をランダムにテストし、おかしな解析をしていないか調べたけど、今のところ見つかっていない。
日本の住所の大まかなパターンは前に調べたので最低でもこれらのパターンは網羅したつもり。
琴線探査: 日本の住所のパターンは一体いくつあるか?
しかし、細かい部分でまだ思いもよらない住所パターンがあるかもしれない。
------
もしこのコードがiPhone開発者の同志の皆様の役に立つのであれば嬉しいのですが、責任は一切持てませんので、よろしくお願いします。
そして、もしこのコードを使っておかしな解析結果を見つけた方は、是非コメントでお知らせいただけると助かります。その他にも改善点などをご指摘いただければ、これも助かります。よろしくお願いします。
最後に、この素晴らしい「json-framework」の作者のStig Brautaset氏に敬礼!
Thank you very much, Stig!
琴線探査: iPhoneの逆ジオコーディングでPBRequesterErrorDomainエラー。何故?
そこで、MKRverseGeocoderのfailover用として「The Google Geocoding Web Service」を使って逆ジオコーディングする方法を試した。
The Google Geocoding Web Service - Google Maps API Web Services - Google Code
結果は上々。逆ジオコーディングの精度も高く、MKReverseGeocoderで取得できない情報もその気になれば取得できることがわかった。ただ問題は、Googleの逆ジオコーディングの結果から、いかにMKPlacemarkを構築するか、ということだった。
アプリケーションによっては一つの文字列になっている「Formatted Address」さえあれば事足りる場合もあるけど、今回はGoogleの逆ジオコーディングの結果を解析して、国、県、郡、区、市、村、町・字、街区、地番の各レベルごとに情報を認識する必要があった。
住所の扱いは非常に複雑で、とりあえず日本国内だけサポートするのが精一杯だったのだけど、例えばこのようにすれば可能だ。
/** * * Googleの逆ジオコーディング開始 * */ - (void)startReverseGeocoderGoogle:(CLLocationCoordinate2D)coordinate { //逆ジオコーディング成功フラグ BOOL isRGeoOK = NO; //アプリケーション取得 UIApplication *app = [UIApplication sharedApplication]; //ネットワークインジケーターON app.networkActivityIndicatorVisible = YES; //URL構築 NSString *urlStr = [NSString stringWithFormat:@"http://maps.google.com/maps/api/geocode/json?latlng=%f,%f&sensor=false", coordinate.latitude, coordinate.longitude]; NSURL *url = [NSURL URLWithString:urlStr]; //逆ジオコーディング結果の文字列(JSON) NSString *rgeoRetStr = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; if (rgeoRetStr != nil) { NSLog(@"Googleの逆ジオコーディングレスポンス\n%@", rgeoRetStr); //逆ジオコーディング結果の文字列をJSONとしてパースしてルートディクショナリを構築 NSDictionary* dict = [rgeoRetStr JSONValue]; //ステータスを取得 NSString *status = [dict objectForKey:@"status"]; //ステータスを判断 if ([status isEqualToString:@"OK"]) { //結果は幾つもある場合がある NSArray *results = [dict objectForKey:@"results"]; //大抵信用出来る情報は配列の頭に来る NSDictionary *result = [results objectAtIndex:0]; //MKPlacemark用の空addressDictionaryを作る NSMutableDictionary *addrDict = [NSMutableDictionary dictionaryWithCapacity:7]; //フォーマットされた住所文字列を取得してaddressDictionaryに追加 NSString *formattedAddress = [result objectForKey:@"formatted_address"]; NSLog(@"formatted_address:%@", formattedAddress); [addrDict setObject:formattedAddress forKey:@"FormattedAddress"]; //FormattedAddressLinesをaddressDictionaryに追加 NSArray *formattedAddresLines = [NSArray arrayWithObject:formattedAddress]; [addrDict setObject:formattedAddresLines forKey:@"FormattedAddressLines"]; //Thoroughfare判断用全角数字配列(ループで作成しまくらないようにループ外で作成) NSArray *nums = [NSArray arrayWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9", nil]; //フォーマットされた住所文字列の中身を区切ったものが入っている配列 //「東京都, 日本」のように大きい区分が後ろに来るので逆からスキャンする NSArray *addressComponents = [result objectForKey:@"address_components"]; for (int i = addressComponents.count - 1; i >= 0; i--) { //「addressComponent」には、「long_name」「short_name」「types」が入っている NSDictionary *addressComponent = [addressComponents objectAtIndex:i]; NSString *longName = [addressComponent objectForKey:@"long_name"]; //「types」には文字列の属性(「country」や「political」)が含まれる //必要なのは先頭の文字列(countryなど) NSArray *types = [addressComponent objectForKey:@"types"]; NSString *type = [types objectAtIndex:0]; //「type」属性で判断してMKPlacemark用のaddressDictionaryを構築していく NSLog(@"addressComponent longName=%@, type=%@", longName, type); //「country」には国名(日本など)と国コード(JPなど)が含まれる if ([type isEqualToString:@"country"]) { [addrDict setObject:longName forKey:@"Country"]; NSString *shortName = [addressComponent objectForKey:@"short_name"]; [addrDict setObject:shortName forKey:@"CountryCode"]; } //「administrative_area_level_1」には都道府県が含まれる if ([type isEqualToString:@"administrative_area_level_1"]) { [addrDict setObject:longName forKey:@"State"]; } //「locality」には郡、区、市町村レベルが含まれる if ([type isEqualToString:@"locality"]) { //郡はSubAdministrativeAreaにマッピング if ([longName hasSuffix:@"郡"]) { [addrDict setObject:longName forKey:@"SubAdministrativeArea"]; } //区 else if ([longName hasSuffix:@"区"]) { //東京都でない場合(政令指定都市)はkuにマッピング //(「ku」はMKPlacemarkのプロパティーにはない独自のキー) NSRange searchResult = [formattedAddress rangeOfString:@"東京都"]; if (searchResult.location == NSNotFound) { [addrDict setObject:longName forKey:@"ku"]; } //東京都の場合はCityにマッピング else { [addrDict setObject:longName forKey:@"City"]; } } //市町村レベル else { [addrDict setObject:longName forKey:@"City"]; } } //「sublocality」には町・字レベル(〜町)、街区レベル(〜丁目)、地番レベル(7-3)が含まれる if ([type isEqualToString:@"sublocality"]) { //全角数字で終わっているかどうか BOOL isEndWithNum = NO; for (id str in nums) { if ([longName hasSuffix:str]) { isEndWithNum = YES; break; } //全角数字で終わっているならSubThoroughfareとみなす if (isEndWithNum) { //地番レベル if (![addrDict valueForKey:@"SubThoroughfare"]) { [addrDict setObject:longName forKey:@"SubThoroughfare"]; } //地番レベルがすでに存在するなら「-」で連結する else { NSString *subThroughfare = [[addrDict valueForKey:@"SubThoroughfare"] stringByAppendingString:@"-"]; subThroughfare = [subThroughfare stringByAppendingString:longName]; [addrDict setObject:subThroughfare forKey:@"SubThoroughfare"]; } } else { //町・字レベル(〜町) if (![addrDict valueForKey:@"SubLocality"]) { [addrDict setObject:longName forKey:@"SubLocality"]; } //街区レベル(〜丁目) //全角数字で終わっていなければThoroughfareとみなす //〜丁目で終わらない場合もある //(徳島県鳴門市撫養町南浜東浜〜、岩手県花巻市高松第8地割〜など) else if (![addrDict valueForKey:@"Thoroughfare"]) { [addrDict setObject:longName forKey:@"Thoroughfare"]; } } } } //キーと値を列挙(デバッグ用) for (id key in addrDict) { NSLog(@"addrDict key=%@, value=%@", key, [addrDict valueForKey:key]); } //MKPlacemark構築 MKPlacemark *pmark = [[MKPlacemark alloc] initWithCoordinate:coordinate addressDictionary:addrDict]; // ---------- ここでMKPlacemarkを使って何かをする ---------- //MKPlacemark解放 [pmark release], pmark = nil; //逆ジオコーディング成功フラグを立てる isRGeoOK = YES; NSLog(@"Googleの逆ジオコーディング成功!"); } else { NSLog(@"Googleの逆ジオコーディング失敗 status:%@", status); } } else { NSLog(@"Googleの逆ジオコーディング失敗:レスポンスなし"); } //逆ジオコーディングが失敗したら if (!isRGeoOK) { //何かする } //ネットワークインジケーターOFF app.networkActivityIndicatorVisible = NO; }
このコードをコンパイルするには、特にStig Brautaset氏の「json-framework」が必要だ。
琴線探査: iPhoneアプリでJSONを簡単に扱うには?「json-framework」 by Stig Brautaset
プログラム的に日本国内の数百地点をランダムにテストし、おかしな解析をしていないか調べたけど、今のところ見つかっていない。
日本の住所の大まかなパターンは前に調べたので最低でもこれらのパターンは網羅したつもり。
琴線探査: 日本の住所のパターンは一体いくつあるか?
しかし、細かい部分でまだ思いもよらない住所パターンがあるかもしれない。
------
もしこのコードがiPhone開発者の同志の皆様の役に立つのであれば嬉しいのですが、責任は一切持てませんので、よろしくお願いします。
そして、もしこのコードを使っておかしな解析結果を見つけた方は、是非コメントでお知らせいただけると助かります。その他にも改善点などをご指摘いただければ、これも助かります。よろしくお願いします。
最後に、この素晴らしい「json-framework」の作者のStig Brautaset氏に敬礼!
Thank you very much, Stig!
コメント
コメントを投稿