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!
コメント
コメントを投稿