ジオポの符号化、復号化をいろんな言語で書いてみる Part.2

このエントリーをはてなブックマークに追加
はてなブックマーク - ジオポの符号化、復号化をいろんな言語で書いてみる Part.2
Share on Facebook

いろんな言語で書いてみると楽しい。他の人の役に立つサンプルコードというよりも、自分のために書いてるところが大きいです。

そんなわけで、今回はC++、Perl、RubyとPythonで書いてみた。

サンプルコード

C++
/*
 * GeoPo Encode in C++
 * @author : Shintaro Inagaki
 * @param geopo, lat lng, scale (pointer)
 */
void geopoEncode(char *geopo, double &lat, double &lng, int &scale)
{
	// 64characters (number + big and small letter + hyphen + underscore)
	char chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
	int i;

	// Change a degree measure to a decimal number
	lat = (lat + 90) / 180 * pow(8.0, 10.0);
	lng = (lng + 180) / 360 * pow(8.0, 10.0);

	// Compute a GeoPo code from head and concatenate
	for(i = 0;i < scale;i++){
		geopo[i] = chars[(int)(floor(fmod(lat / pow(8.0, 9.0 - i), 8.0)) + floor(fmod(lng / pow(8.0, 9.0 - i), 8.0)) * 8)];
	}
	geopo[i] = '\0';
}		

/*
 * GeoPo Decode in C++
 * @author : Shintaro Inagaki
 * @param geopo, lat lng, scale (pointer)
 */
void geopoDecode(char *geopo, double &lat, double &lng, int &scale)
{
	// 64characters (number + big and small letter + hyphen + underscore)
	char chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
	int order, i;

	scale = strlen(geopo);

	for(i = 0;i < scale;i++){
		// What number of character that equal to a GeoPo code (0-63)
		order = (strchr(chars, geopo[i]) - chars);
		// Lat/Lng plus geolocation value of scale 
		lat = lat + fmod(order, 8.0) * pow(8.0, 9.0 - i);
		lng = lng + floor((double)order / 8.0) * pow(8.0, 9.0 - i);
	}
	// Change a decimal number to a degree measure, and plus revised value that shift center of area
	lat = lat * 180 / pow(8.0, 10.0) - 90 + 180 / pow(8.0, scale) / 2;
	lng = lng * 360 / pow(8.0, 10.0) - 180 + 360 / pow(8.0, scale) / 2;
}	

専らライトウェイトなインタプリタ言語ばかりやってるので、C++とかJavaは仕事で扱ったくらいしか経験なく、コンパイル環境すら整えてないので、MicrosoftのVisual C++をダウンロードする作業から開始した。

標準ライブラリのstdio.h、math.hとstring.h を使ってるから、適宜importしてください。Cではコンパイルしてないけど、Cでもokなんじゃないかな?

関数とかは前回のObjective-Cとほとんど同じ。pow()fmod()floor()とか。

今回はcharsを配列形式のcharにしてジオポ符号を取り出す形。最後にヌル文字を加えるのをお忘れ無く。

復号化は、strchr()でジオポの符号を検索してその位置を返すんだけど、返す型がポインタ。だから、そのポインタの値をcharsの先頭ポインタで引いてやることによって、位置のintを取り出すようにした。

Visual C++って使いづらくない?慣れだと思うけど使っててアレルギーでそうだった…特にポインタミスって色んなアプリを引き連れにハングアップさせたり…(自分が悪いけど)

Perl
#
# GeoPo Encode in Perl
# @author : Shintaro Inagaki
# @param %location (Hash) [lat (Float), lng (Float), scale(Int)]
# @return $geopo (String)
#
sub geopoEncode(%location) {
	# 64characters (number + big and small letter + hyphen + underscore)
	$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";

	$geopo = "";
	$lat = $location{'lat'};
	$lng = $location{'lng'};
	$scale = $location{'scale'};

	# Change a degree measure to a decimal number
	$lat = ($lat + 90) / 180 * 8 ** 10;
	$lng = ($lng + 180) / 360 * 8 ** 10;

	# Compute a GeoPo code from head and concatenate
	for($i = 0; $i < $scale; $i++) {
		$geopo .= substr($chars, int($lat / 8 ** (9 - $i) % 8) + int($lng / 8 ** (9 - $i) % 8) * 8, 1);
	}
	return $geopo;
}		

#
# GeoPo Decode in Perl
# @author : Shintaro Inagaki
# @param $geopo (String)
# @return %location (Hash) [lat (Float), lng (Float), scale(Int)]
#
sub geopoDecode($geopo) {
	# 64characters (number + big and small letter + hyphen + underscore)
	$chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
	# Hash for geolocation
	%location = ();

	for ($i = 0; $i < length($geopo); $i++) {
		# What number of character that equal to a GeoPo code (0-63)
		$order = index($chars, substr($geopo, $i, 1));
		# Lat/Lng plus geolocation value of scale 
		$location{'lat'} += $order % 8 * 8 ** (9 - $i);
		$location{'lng'} += int($order / 8) * 8 ** (9 - $i);
	}

	# Change a decimal number to a degree measure, and plus revised value that shift center of area
	$location{'lat'} = $location{'lat'} * 180 / 8 ** 10 - 90 + 180 / 8 ** length($geopo) / 2;
	$location{'lng'} = $location{'lng'} * 360 / 8 ** 10 - 180 + 360 / 8 ** length($geopo) / 2;
	$location{'scale'} = length($geopo);

	return %location;
}		

今から5年くらい前はPHPじゃなくてPerlばかり使ってた。あまりたいしたコードは書いてないけど、どんなスパゲッティコードでもソースを読み取ることができる能力は自慢w まぁ、僕自身がスパゲッティを生産してたわけだけど。

PHPとほぼ変わらない。ユーザ関数はsubでもfunctionでも、連想配列(ハッシュ)は%hoge$hoge{‘hoge’}みたいになるので注意。ちなみに、ただの配列だと@hoge

%で余り、**でべき乗、int()で整数化、length()で長さ、+で文字の結合、substr()で文字の切り出し、index()で文字の位置。

PerlもモダンPerlが話題になってるから、再勉強してみようかな。使ってた当時は豊富なライブラリ・モジュールにお世話になりっぱなしで、クラスすら作った経験ないんだよね。

Ruby
#
# GeoPo Encode in Ruby
# @author : Shintaro Inagaki
# @param location (Hash) [lat (Float), lng (Float), scale(Int)]
# @return geopo (String)
#
def geopoEncode(location)
	# 64characters (number + big and small letter + hyphen + underscore)
	chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
	
	geopo = ""
	lat = location['lat']
	lng = location['lng']
	scale = location['scale']
	
	# Change a degree measure to a decimal number
	lat = (lat + 90.0) / 180 * 8 ** 10; # 90.0 is forced FLOAT type when lat is INT
	lng = (lng + 180.0) / 360 * 8 ** 10; # 180.0 is same

	# Compute a GeoPo code from head and concatenate
	for i in 0..scale
		geopo += chars[(lat / 8 ** (9 - i) % 8).floor + (lng / 8 ** (9 - i) % 8).floor * 8, 1];
	end
	return geopo;
end		


#
# GeoPo Decode in Ruby
# @author : Shintaro Inagaki
# @param geopo (String)
# @return location (Hash) [lat (Float), lng (Float), scale(Int)]
#
def geopoDecode(geopo)
	# 64characters (number + big and small letter + hyphen + underscore)
	chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
	# Hash for geolocation
	location = {}

	lat = 0.0
	lng = 0.0
	scale = geopo.size # Scale is length of GeoPo code
	order = 0

	for i in 0..(scale - 1)
		# What number of character that equal to a GeoPo code (0-63)
		order = chars.index(geopo[i])
		# Lat/Lng plus geolocation value of scale
		lat = lat + order % 8 * 8 ** (9 - i)
		lng = lng + (order / 8).floor * 8 ** (9 - i)
	end

	# Change a decimal number to a degree measure, and plus revised value that shift center of area
	location['lat'] = lat * 180 / 8 ** 10 - 90 + 180 / 8 ** scale / 2
	location['lng'] = lng * 360 / 8 ** 10 - 180 + 360 / 8 ** scale / 2
	location['scale'] = scale

	return location
end		

Rubyも必要になった経験がなかったから、あまり知らない。だけど、ライトウェイトなインタプリタ言語はひとつの言語を知っていれば、サンプルコードを真似して書ける。その中でもRubyは一番とっつきやすい(PHPからPerlから乗換え)と思う。

ユーザ関数はdefでスタートendで終わる。forループもforでスタートendで終わる。自然とインデントするので見やすい。連想配列(ハッシュ)も普通の変数と区別なし。

%で余り、**でべき乗、floor()で切り捨て、sizelengthメソッドどっちでも文字の長さ、+で文字の結合、配列と同じ操作で文字列から文字を取り出せる(sliceメソッド使ってもいい)、indexで文字の位置

PHP版Ruby on RailsといわれてるCodeIgniterがわかりつつあるので、RoRに移行してもスムースにいけるかな。ただ、本当にRubyじゃないとダメっていう案件がなさすぎる。

Python
#
# GeoPo Encode in Python
# @author : Shintaro Inagaki
# @param location (Dictionary) [lat (Float), lng (Float), scale(Int)]
# @return geopo (String)
#
def geopoEncode(location) :
       # 64characters (number + big and small letter + hyphen + underscore)
       chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"

       geopo = ""
       lat = location['lat']
       lng = location['lng']
       scale = location['scale']

       # Change a degree measure to a decimal number
       lat = (lat + 90.0) / 180 * 8 ** 10 # 90.0 is forced FLOAT type when lat is INT
       lng = (lng + 180.0) / 360 * 8 ** 10 # 180.0 is same

       # Compute a GeoPo code from head and concatenate
       for i in range(scale):
               order = int(lat / (8 ** (9 - i)) % 8) + int(lng / (8 ** (9 - i)) % 8) * 8
               geopo = geopo + chars[order]

       return geopo		


#
# GeoPo Decode in Python
# @author : Shintaro Inagaki
# @param geopo (String)
# @return location (Dictionary) [lat (Float), lng (Float), scale(Int)]
#
def geopoDecode(geopo) :
       # 64characters (number + big and small letter + hyphen + underscore)
       chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"

       lat = 0.0
       lng = 0.0
       scale = len(geopo) # Scale is length of GeoPo code
       order = 0

       for i in range(scale):
               # What number of character that equal to a GeoPo code (0-63)
               order = chars.find(geopo[i])
               # Lat/Lng plus geolocation value of scale
               lat = lat + order % 8 * 8 ** (9 - i)
               lng = lng + int(order / 8) * 8 ** (9 - i)

       # Change a decimal number to a degree measure, and plus revised value
that shift center of area
       location['lat'] = lat * 180 / 8 ** 10 - 90 + 180 / 8 ** scale / 2
       location['lng'] = lng * 360 / 8 ** 10 - 180 + 360 / 8 ** scale / 2
       location['scale'] = scale

       return location		

GoogleのクラウドでHelloWorldくらいまで勉強した気がw その後、GPSロガーとのシリアル通信プログラム(PySerial)書いたときに大活躍!ライトウェイトな言語なくせして、ネイティブなデバイスが簡単に扱えた衝撃はでかかった。

…えっと。あまりにもRubyと変わらないのであまり書くこともないという。

Python最大の特徴であるインデントに注意して、あとはfind()が文字の位置、range()で範囲が便利になるよってくらい。

そうだそうだ、RubyとPythonは符号化のローカル変数の代入時にわざとfloatの数値を足してあげて、intで引数がわたされた場合でも強制的にfloatになるようにしてあるよ。

Pythonの話題もよく耳にするようになってきたし、何よりソースコードが強制的にキレイになるっていうのがいいね。

まとめ

コンバイラ言語はめんどくさい。ライトウェイト言語ばんざーい。

暇があったら、Haskell(関数型言語)に挑戦したいね。

タグ: , , , ,

コメントは受け付けていません。