ある地点の近傍のツイートの取得方法！setGeoCodeや計算による半径の指定とGeohash（ジオハッシュ）による矩形の指定　#Twitter4J

Twitterの検索ではある地点の緯度経度と半径を指定することで、その地点からその半径内にあるツイートを取得することができます。

Twitter4Jでは次のように記述します。
（「//★位置情報（例　新宿駅を中心に半径3km）」の箇所です）

```java
import java.util.List;
import twitter4j.*;

public class TwitterSearchGeo {

    public static void main(String[] args) {

        try {

            //自動的に認証してくれる
            //（バージョン2.2.4以降はgetInstance()ではなくgetSingleton()を推奨）
            new TwitterFactory();
            Twitter twitter = TwitterFactory.getSingleton();

            String strText;

            //検索語を指定する（※ここでいろいろとオプションを設定できる）
            //日時はGSTだから多めに取っておくこと
            Query query = new Query("since:2012-11-30 until:2016-09-01");


            //★位置情報（例　新宿駅を中心に半径3km）
            GeoLocation geo = new GeoLocation(35.690921,139.700258);
            query.setGeoCode(geo, 3.0, Query.KILOMETERS);


            //検索結果を取得
            QueryResult result = twitter.search(query);

            //検索結果のツイートを取得
            List&lt;Tweet&gt; twiSearches = result.getTweets();

            //ツイートの表示
            for (Tweet tweet : twiSearches) {

                //改行記号を除去（半角スペースに変換）
                strText = tweet.getText().replaceAll("\r\n"," ");
                strText = strText.replaceAll("\r"," ");
                strText = strText.replaceAll("\n"," ");

                //タブ記号を除去（半角スペースに変換）
                strText = strText.replaceAll("\t"," ");


                //コンソールに出力する（位置情報、日時、ユーザ名、ツイート）
                System.out.println(tweet.getGeoLocation() + "\t" +tweet.getCreatedAt().toString() + "\t" + tweet.getFromUser() + "\t" + strText);
            }

        } catch(TwitterException te){
            te.printStackTrace();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}
```

グーグルマップにマッピングするとこのようになります。
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121202212429105.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/201212022148128f4.png" alt="新宿駅半径3km_small" border="0" height="400" width="396"></a>


なお、注意する点は<strong>位置情報（GeoLocation）がないツイートの場合はアカウントのプロフィール情報から取得する仕様のため、指定場所に含まれない場所からのツイートが多量に紛れてしまうことです。</strong>
上の例だとプロフィール情報の場所が「新宿」であるツイートも取得されてしまいます。
<strong>不要な場合（ほとんどの場合でしょう）は除去しましょう。</strong>
上の地図でも不要なものは除去してあります。


# 他の方法（単純距離、地球の丸さを考慮、Geohash）
先の方法はTwitter APIに備わっている機能を使いました。
この方法は検索する場合に有効です。
つまり他の方法で取得した位置情報付きツイートには使えないので別に判別する処理が必要です。

以下に方法を3つ紹介します。
なお参考にさせていただいたページはこちらです。ありがとうございました。
<blockquote><p>・<a href="http://blog.epitaph-t.com/?p=172">なんとなく始めてみた、プログラマー雑記 ≫ データベース内の緯度・経度を利用して半径500m以内を検索する方法</a>
・<a href="http://d.hatena.ne.jp/ilo/20090212/1234448136">MySQLで指定した緯度経度から半径nメートル内検索っぽいのを実現するSQL</a>
・<a href="http://ke-tai.org/blog/2010/01/29/geohash/">GPS座標を短い文字列で扱えるGeoHashが面白い | ke-tai.org - インフィニットループ</a>
・<a href="http://blog.masuidrive.jp/index.php/2010/01/13/geohash/">緯度経度を文字列で表すGeoHash - @masuidrive blog</a>
・<a href="http://blog.masuidrive.jp/index.php/2010/01/15/geohash-algorithm/">Geohashのアルゴリズム - @masuidrive blog</a>
・<a href="http://blog.masuidrive.jp/wp-content/uploads/2010/01/geohash.html">geohash demonstrator</a></p></blockquote>

## 単純に距離を測定する方法
座標上の2点間の距離は三平方の定理を利用した次の式で求めることができます。
<blockquote><p>A(x<sub>1</sub>, y<sub>1</sub>) と B(x<sub>2</sub>, y<sub>2</sub>) の距離ABは、<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221242627a.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221242627a.png" alt="2点間の距離の公式" align="absmiddle" border="0" height="34" width="218"></a></p></blockquote>
この距離が指定範囲内であればいいということになるはずです。

次に緯度経度は原点からの距離を表しているわけではないので変換する必要があります。
指定した半径が何度に当たるのかを計算します。

地球の子午線弧長は約40008kmのため、割り算すると緯度1秒の長さは約30.87mとなります。
例えば3kmは約97.18173秒（=3000/30.87）、つまり約0.026995度（=(3000/30.87)/3600）となります。

この結果から緯度 35.690921±0.026995 かつ 経度 139.700258±0.026995の範囲にあるものを求めればいいことになります。


試しに新宿駅から半径3kmを青でマッピングしてみると次のようになります。
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121202212428911.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121202214811068.png" alt="新宿駅半径3km（円あり）_small" border="0" height="400" width="396"></a>

<span style="font-size:large;">う～ん</span>、<strong>一部満たしてないですね。。。</strong>
そもそも最初のツイートの位置がやや楕円状に分布していました。

この理由は<span style="color:#FF0000">緯度は距離とほぼ対応するのですが経度は場所によって対応が大きく変化するためです。</span>
この件については下に書きます。
なお、<strong>指定する範囲が狭かったり、そこまでの精度を求めない場合はこれで問題ないですね。</strong>


## 地球の丸さを考慮して測定する方法
<span style="font-size:large;">・緯度</span>
緯度によって僅かながら緯度1秒の長さに違いがあります。
緯度35度上の緯度1秒の長さは約30.8mとなります。
　→<a href="http://ja.wikipedia.org/wiki/%E7%B7%AF%E5%BA%A6#.E7.B7.AF.E5.BA.A61.E7.A7.92.E3.81.AE.E9.95.B7.E3.81.95">緯度 - 緯度1秒の長さ - Wikipedia</a>


<span style="font-size:large;">・経度</span>
緯度は高くなるにつれ半径は小さくなり、北極点・南極点では0となります。

計算方法はまずその地点の球体の切断面の半径を考えます。
地球の半径は6378137mであり、切断面の半径をrとすると次のようになります。

r = 6378137 × cos(北緯 / 180 × <img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/PI.png" alt="PI.png" align="absmiddle" border="0" height="10" width="11">)

北緯を使うことに要注意です！


これを円周の長さ2<img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/PI.png" alt="PI.png" align="absmiddle" border="0" height="10" width="11">rに代入して円周を求めます。

円周 = 2<img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/PI.png" alt="PI.png" align="absmiddle" border="0" height="10" width="11"> × 6378137 × cos(北緯 / 180 × <img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/PI.png" alt="PI.png" align="absmiddle" border="0" height="10" width="11">)

あとは円周から経度1度当たりの距離、1秒当たりの距離を求めます。
1度当たりの距離 = 円周 / 360
1秒当たりの距離 = 円周 / (360 × 60 × 60)


例えば新宿駅（北緯35.690921度)の場合、
円周は約32547966.09952mとなり、1秒当たりの距離は約25.11mとなります。


## Geohash（ジオハッシュ）による矩形エリアの指定
これまでは指定した半径内を考えてきました。
他の方法としてGeohash（ジオハッシュ）を用いた<strong>矩形</strong>エリアによる方法もあります。

<blockquote><p><strong>Geohash（ジオハッシュ）</strong>（<a href="http://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%AA%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5">Wikipediaより抜粋</a>）<br>
経緯度に基づくジオコーディング方法の一つである。パブリックドメインになっている。階層的な空間データ構造であり、空間を分割していくことによって表現する。
ジオハッシュは、任意の精度で表現できる、文字列の末尾を削っていくと徐々に精度が落ちる、といった特徴がある。
そのため、近隣の2地点を表すコードは、似たような文字列から構成されることが多い。同時に、より多くの文字列が一致すれば、当該2点がより近いことを表す。</p></blockquote>
Geohashは，地図上の矩形エリアをあるアルゴリズムにより文字列化して表現する手法です。
このGeohashが特徴的なのは、単に文字列になるのではなくGeohashの文字列が長ければ長いほど詳細な情報を持つことです。
つまり、文字列が短い場合は矩形エリアは広くなり、長くなると矩形エリアは狭くなります。
また先頭文字列が一致するGeohashの矩形エリアは，その一致している文字列が示す矩形エリアに内包されています。

GeoHashの利点は次の点です。
<blockquote><p>・場所の絞り込みが簡単に行え、隣接エリアの取得も容易である。
　（多くの言語で隣接エリアの取得ができます）
・範囲が前方一致で検索できるので文字列マッチやSQLのlike文などとも相性がよく、
　高速な処理が可能となる。
・座標と精度を短い文字列で持つことができるので、URLで扱いやすくなる。</p></blockquote>
<a href="http://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%AA%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5#.E4.BE.8B">文字列化のアルゴリズム</a>は難しいですが、有志が各プログラミング言語用に作成してくれているので実装は容易です。
（てぃーはJava用のものを使いました。）


例えば新宿駅（緯度35.690921、経度139.700258）は「xn774cqdevyk」となります。
（図は<a href="http://blog.masuidrive.jp/wp-content/uploads/2010/01/geohash.html">geohash demonstrator</a>より作成。図では大文字）
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121202212440fd1.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121202212440fd1.png" alt="先頭10文字" border="0" height="411" width="600"></a>

小さくてよく分からないので拡大してみます。
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221243980d.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221243980d.png" alt="先頭10文字（拡大）" border="0" height="289" width="400"></a>
ほんの小さい矩形エリアが分かります。

また先頭6文字「xn774c」と7文字「xn774cq」では次のようになります。
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121203015253787.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/20121203015253787.png" alt="先頭6文字、7文字" border="0" height="411" width="600"></a>
7文字「xn774cq」のエリアが6文字「xn774c」のエリアの一部であることが分かります。


矩形エリアはあらかじめ決められているので目的の場所がエリアの中心にあればいいのですが、端になる場合はある程度の大きさのエリアと隣接エリアを指定する必要があります。

例えば先頭6文字「xn774c」とその隣接エリアは次のようになります。
<a href="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221243003c.png""><img src="http://blog-imgs-52.fc2.com/t/e/a/teapipin/2012120221243003c.png" alt="先頭6文字（隣接も）" border="0" height="411" width="600"></a>

希望の範囲を絞るのにはやや大変かもしれません＾＾


以上、ある地点の近傍の範囲を取得する方法は様々です。
状況とスキルに応じて使い分けていきましょう。