Geohash の使い道とn進法の罠について

PostGIS の ST_Geohash の考え方には少し修正すべき点があります。Geohash の使い道についての考えと合わせて書いてみます。
Geohash については、d:id:hfu:20080603 のころから興味を持っていて、Geohash の活用については、まだまだあきらめていません。Geohash とは、経緯度を簡略な文字列に置き換える手法で、基本的に MicrosoftGoogle が使っているような、四分木的な手法なのですが、仕様が公開されていていろいろな言語で実装されているということと、符号化に base32 を用いているため、文字列が短くなることが特徴であると考えています。
Geohash の使い道は、一般のデータにおけるハッシュ関数値の使い道と同じで、「いろいろあります」。この、いろいろある使い道について、あまり体系だった解説がされていないところが、必ずしもハッシュ関数の(特に Web における)利用についての知識があるとは限らないジオの世界で、Geohash がよく使われていない理由なのではないかと思っています。

基礎知識

Geohash については、Geohash - Wikipedia に記載されています。

Geohash を線や面に適用する場合の問題点

PostGIS には、ST_Geohash という関数があります。この関数は、点だけではなくて線や面についても適用できるとされており、関数のドキュメントには、次のように記載されています。

Other types return a GeoHash with a variable amount of precision, based on the size of the feature. Larger features are represented with less precision, smaller features with more precision.

http://postgis.refractions.net/docs/ST_GeoHash.html

他の型では可変的な精度量を持つGeoHashを返します. 大きなフィーチャーは低精度で表現され,小さいフィーチャーは高精度で表現されます.

http://www.finds.jp/docs/pgisman/1.5.1/ST_GeoHash.html

しかし、残念ながらこれは正しい認識ではありません。

The idea is that the box implied by the GeoHash will always contain the input feature.

http://postgis.refractions.net/docs/ST_GeoHash.html

という考えは正しいですが、そのことから、小さい形状が長い Geohash で表現され、大きい形状が短い Geohash で表現されることには繋がりません。これは、n進法には「桁上がり」が発生するため*1で、Geohash の解説にも、

Edge case locations in close proximity to each other but on opposite sides of the Equator or a meridian can result in Geohash codes with no common prefix

http://en.wikipedia.org/wiki/Geohash

とあります。いくら極小の形状であっても、Geohash の一文字目の境界である、例えば赤道を跨いでしまえば、その形状の Geohash は決まりません。よって、小さい面には細かな Geohash、大きい面には短い Geohash がつく、という考えは正しくありません。
なお、手持ちの PostGIS の実装では、線や面に対する Geohash は空文字列が戻るようです。

線や面に対する Geohash の使いどころ

まず第一に、Geohash は原則的に点のための技術であるのですが、Geohash を線や面に対して適用するケースとしては、例えば次のようなものが考えられると思います。

  1. 形状の位置から算出される識別子として用いる。
  2. 形状の格納先のインデクスとして用いる。

後者のために用いるには、恐らく形状は Geohash によって決まるグリッドによって分解しなければなりません(タイル化)。これについては、今回のエントリでは取り扱いません。
前者のために用いるには、ST_Geohash のドキュメントのように、形状のBBOXからGeohashを算出する考えは、先述の「桁上がり」の問題から、適切ではありません。

  • 形状の重心の Geohash を算出する。
  • 形状の面積を勘案して、Geohash の桁数を短い方向に調整する。

という手法が適切だと思います。面積の勘案は、特に形状の厳密なベクトル情報が必ずしも伝達されない場合には必要で、面積を勘案することにより、「形状の中心付近をクリックすればその形状の Geohash が得られる」可能性が高くなります。
文字数の調整は、Geohash が示すグリッドの面積が、形状の面積と同程度になるように行うのが妥当だと思いますが、ここで問題となるのが、Geohash が base32 を導入していることにより、「1文字削るだけで、グリッドが 1/32 になってしまう」という特性です。1文字削る前には形状に比べてグリッドが小さすぎたのに、1文字削ると形状よりもグリッドが大幅に大きくなってしまうという問題が発生します。また、入力される形状の特性(識別したいもの同士に重なりがあるか、形状は「細長い」傾向にあるか、等)も考慮する必要があります。
このあたりのことを、実例とプログラムをつけてあとで書くかもしれません。

*1:だから、グレイコードあるいは空間充填曲線的にGeohashを変更した上で、Geohash に don't care 記法を導入すれば、BBOX に対する Geohash がもう少し安定する可能性がありますが、こっちの方向はあまりにもマニアックになるので、今回のエントリでは取りあげません。