読者です 読者をやめる 読者になる 読者になる

TitaniumMobile勉強記

Web系エンジニア向けのキャリアアドバイザーやってましたが現在はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連(Node.js、Titanium)あたり

TiGeoHash使ってregionChangedイベントを意図通り制御できたお話

Titanium™ Advent Calendar 2013 17日目です。

先日、休日出勤した振替休を取得してるのですが、このブログを書かないといけないし、来月やるイベントの構成考えないといけなし、昨夜ちょっとだけもくもくとコード書いてたAlloyの続きをやりたいし、でも子供の幼稚園の迎えがあるからあまり時間的余裕がないなぁ・・となんか休みっぽくない朝のひとときを過ごしてます

7日目にもぎゃさんが、regionChangedの抑制というエントリ書かれていましたが、これを読んでて、私も似たような機能を違うアプローチで実装しましたので今回はTIPS的なお話をしようと思います。

位置情報アプリに欠かせない機能とは?

TIPSに入る前に少し前置きを。 CraftBeerFanのアプリの一番最初のバージョンで地図上でお店検索する画面では

  1. 現在位置の緯度経度情報取得
  2. 取得した緯度経度情報からACSに登録してるお店情報を検索
  3. 検索結果を地図上に表示

という機能だけ実装してました。

ただ、これだとやっぱり使い勝手が悪く、リリース直後に使っていただいた方から、スクロールした先のお店情報を読み込む機能が欲しいという要望があり、実際自分で使っていてもこれはイマイチだなと思いました。

サンプルアプリ程度ならいいのかもしれませんが、AppStoreに公開するようなアプリで、MapViewを使ってるなら、 スクロールした先のお店情報を読み込む機能は必須ということを当時思い知ったので、次のバージョンでこの実装に着手することを決めました。

regionChanged イベントだけで処理するのは現実的ではなかった

スクロールした先のお店情報を読み込むのに、regionChanged イベントをうまく処理すればいいのかと思ったのですが、これちょっとのスクロールですぐにイベントが発火してしまいます。

欲しいのはそういうのではなく、一定の距離スクロールしたら、お店の情報を読み込む処理だったので、regionChanged イベントだけで、どうにかするのはちょっと厳しい感じがしてました。

地図にマス目を描くようなことが出来ないのか考えてみた

言葉で説明すると長くなるので、絵にしてみました。

f:id:h5y1m141:20131217101109p:plain

こういう感じでマス目があって、現在位置が、どのマス目にいるのか計算して把握できれば

  • 現在、Aの場所にいたとする
  • スクロールをしても、その距離が小さくAのマス目から抜けてなければ、お店情報は読み込まない
  • スクロールをして、Aのマス目から抜けた場合には、その場所の緯度経度情報を取得した上でACSにクエリー投げる

ということが出来るんじゃないかと考えました。

なんか、こういのをどっかで見た記憶が・・・と思ったのですが、昨年のTitanium Mobile Advent Calendar 2012 で 【12日目】Titanium Mobile で GeoHash を取り扱うというエントリを@ryugoo_ さんが書いてました。

TiGeoHash

※ GeoHashについてはmasuidriveさんが緯度経度を文字列で表すGeoHashの記事を合わせて読んで理解を深めました

TiGeoHash をどう使うか?

実際にどう使ってるのかコードを抜粋しながら解説していきます。なお、以下サンプルコードははCoffeeScriptです

モジュール読み込みなど

ここはそれほど難しいことはやってないので、解説省略します

tiGeoHash = require("/lib/TiGeoHash")
geoHashResult = []
mapView = Titanium.Map.createView
  mapType: Titanium.Map.STANDARD_TYPE
  # 以下省略   

MapViewのregionchangedイベントを設定

まずはコードを以下に記載します

precision = 6   # 解説1

mapView.addEventListener('regionchanged',(e)=>
  regionData = mapView.getRegion() 
  latitude = regionData.latitude
  longitude = regionData.longitude

  geoHashResult = tiGeoHash.encodeGeoHash(latitude,longitude,precision)  # 解説2
  lastGeoHashValue = geoHashResult[geoHashResult.length-1]   # 解説3
  
  if geoHashResult.geohash is lastGeoHashValue  # 解説4
    geoHashResult.push(geoHashResult.geohash)
  else
    geoHashResult.push(geoHashResult.geohash)    
    _nearBy(latitude,longitude) 
)

解説1

precision = 6というマジックナンバーが出てしまうのですが、これは何かというと、GeoHashの特徴として、その値の長さによって精度が変わる・・と書いてもイマイチピントこないかもしれません。

先ほどの絵でいうと、仮にGeoHashの値を得て、先頭の1文字だけを取り出した結果、以下のようにAからL という形になったとします。(なお実際にこの領域の緯度経度情報からGeoHash値計算すると違う値になります)

f:id:h5y1m141:20131217101109p:plain

「これだとかなり広範囲になってしまうので、もう少し狭い範囲でスクロールがあった場合に処理をしたい」

という要望があった場合に、GeoHashの先頭2文字目までを取り出すと、このような感じになります。

f:id:h5y1m141:20131217102534p:plain

こういう感じで、GeoHash値の先頭から何文字目までを取得するかを決めることで、マス目の作り方を大きくしたり細かくしたりというのが設定できます。

この数字を大きくしたり小さくしたりしながら、スクロール完了時にイベント発火させるかどうか制御できるのですが、これは実機などで動作確認しながら微調整した結果なのでアプリによってはこの数字は変更させる必要があるかと思います

解説2

TiGeoHashモジュールを使って、現在の緯度経度情報から、GeoHash値を得ます。

解説3

スクロールするたびに緯度経度情報からGeoHash値を計算して、配列に格納してます。その配列に格納されてる最後のGeoHash値を取り出すことで、直前にどのマス目にいたのか把握できます

解説4

現在位置からえられたGeoHash値と、上記記載した配列最後のGeoHash値の比較をして、値が異なれば、マス目が変わったという判断になるので、そこで初めてACSに対してクエリー投げる処理を実施してます。_nearBy(latitude,longitude) の処理内容は省略します

まとめ

GeoHashは何となくとっつきづらい印象があったのですが、頭のなかで地図上にマス目を描くイメージを持ったことで、その印象が大分変わりましたので、位置情報連携アプリを作ろうと思ってる人のお役に立てればと思います。

明日はumi_uyuraさんが、Alloy with Stylus というネタを書いてくれるみたいで、Alloyが急に気になってきたので個人的にとても楽しみです