以下はObjective-C的???Geohash | アクトインディ技術部隊報告書と同様のものです。
先日chibaさんがGeohashをdecodeするものをCLで書いていらしたので、それを微妙に参考にしながらCocoaのCoreLocationのCLLocationにgeohashをencode/decodeするメソッド追加してみました。
本当はビット演算がしたいのですが、文字列処理の方が自分には単純で簡単だったため、とりあえず今回は文字列処理にしてみました。
我ながらひどいコードだと思いますので、これからリファクタリングをしていきたいところです。
[CLLocation+Geohash.h]
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
@interface CLLocation(Geohash)
@property (nonatomic, readonly) NSString *geohash;
+ (CLLocation *)locationFromGeohash:(NSString *)aGeohash;
@end
[CLLocation+Geohash.m]
#import "CLLocation+Geohash.h"
#include <math.h>
NSString *int_to_binary(NSUInteger input)
{
if (input == 1 || input == 0)
{
return [NSString stringWithFormat:@"%d", input];
}
return [NSString stringWithFormat:@"%@%d", int_to_binary(input / 2), input % 2];
}
double parse_binary(NSString *binary, double max, double min)
{
double mid = 0.0;
for (NSUInteger i = 0; i < [binary length]; ++i)
{
if ([binary characterAtIndex:i] == '1')
{
min = mid;
}
else
{
max = mid;
}
mid = (max + min) / 2;
}
return mid;
}
NSUInteger binary_to_int(NSString *input)
{
NSUInteger result, length;
result = 0;
length = [input length];
for (NSUInteger i = 0; i < length; ++i)
{
if ([input characterAtIndex:i] == '1')
{
result += pow(2, length - i - 1);
}
}
return result;
}
NSString * generate_binary(double input, double max, double min, int cutoff)
{
NSMutableString *result;
double mid;
result = [NSMutableString string];
for (int i = 0; i < cutoff; ++i)
{
mid = (max + min) / 2;
if (input > mid)
{
[result appendString:@"1"];
min = mid;
}
else
{
[result appendString:@"0"];
max = mid;
}
}
return [NSString stringWithString:result];
}
@implementation CLLocation(Geohash)
- (NSString *)geohash
{
int cutoff = 15;
NSString *base32_characters = @"0123456789bcdefghjkmnpqrstuvwxyz";
NSString *bin_lat, *bin_lng;
NSMutableString *bin_packed, *result;
bin_lat = generate_binary([self coordinate].latitude, 90.0, -90.0, cutoff);
bin_lng = generate_binary([self coordinate].longitude, 180.0, -180.0, cutoff);
bin_packed = [NSMutableString string];
for (int i = 0; i < [bin_lat length]; ++i)
{
[bin_packed appendFormat:@"%c%c", [bin_lng characterAtIndex:i], [bin_lat characterAtIndex:i]];
}
result = [NSMutableString string];
// extract by 5-bit.
for (int i = 0; i < [bin_packed length] / 5; ++i)
{
NSUInteger index;
index = binary_to_int([bin_packed substringWithRange:NSMakeRange(i * 5, 5)]);
[result appendFormat:@"%c", [base32_characters characterAtIndex:index]];
}
return result;
}
+ (CLLocation *)locationFromGeohash:(NSString *)aGeohash
{
NSString *base32_characters = @"0123456789bcdefghjkmnpqrstuvwxyz";
NSMutableString *bin_packed, *bin_lat, *bin_lng;
bin_packed = [NSMutableString string];
for (NSUInteger i = 0; i < [aGeohash length]; ++i)
{
NSString *character;
character = [[NSMutableString stringWithFormat:@"%c", [aGeohash characterAtIndex:i]] lowercaseString];
for (NSUInteger j = 0; j < [base32_characters length]; ++j)
{
if ([character isEqualToString:[NSString stringWithFormat:@"%c", [base32_characters characterAtIndex:j]]])
{
NSMutableString *binary;
binary = [NSMutableString stringWithFormat:@"%@", int_to_binary(j)];
NSUInteger length = [binary length];
for (NSUInteger k = 0; k < 5 - length; ++k)
{
[binary insertString:@"0" atIndex:0];
}
[bin_packed appendString:binary];
break;
}
}
}
bin_lat = [NSMutableString string];
bin_lng = [NSMutableString string];
for (NSUInteger i = 0; i < [bin_packed length]; ++i)
{
if (i % 2)
{
// a latitude is composed of odd bits.
[bin_lat appendFormat:@"%c", [bin_packed characterAtIndex:i]];
}
else
{
// a longitude is composed of even bits.
[bin_lng appendFormat:@"%c", [bin_packed characterAtIndex:i]];
}
}
return [[CLLocation alloc] initWithLatitude:parse_binary(bin_lat, 90.0, -90.0)
longitude:parse_binary(bin_lng, 180.0, -180.0)];
}
@end
NSStringでかなり遅いcharacterAtIndexを使いまくっている。
参考
Geohash – Wikipedia, the free encyclopedia
GeoHashのdecodeのアルゴリズムの解説します & ScalaのGeoHashライブラリを作ってみました(仮) – ゆろよろ日記