mroongaを使ってchikamap.
筆者は趣味でプログラムなどをしています。元々はWindowsをメインで使っていましたが、
今回は、
chikamap.comとは
chikamap.
サイトとしてはデータを表示するだけなので構築は比較的簡単な部類だと思いますが、
地図への地価履歴表示
検索の方法はデータベースに登録してある地価の位置情報を、

その周辺の公共施設、交通機関を表示
この地図に表示された地価のポイントをクリックすることで、

データベース自体には上のような四角いエリア内のデータを検索するような機能は元々あったりしますが、
しかし位置情報からのデータベース検索方法を色々探している内に、
groongaを直接コマンドから実行しても良いのですがchikamap.
データベースへの位置情報データ登録について
地価の情報、point
というspatialデータタイプを使うことになります。しかし素のRailsではmigrationでエラーが出るためactiverecord-mysql2spatial-adapter gemを使っています。これを使うとt.
のようにmigrationが書けます。
# db/migrate/xxxxxxx.rb
create_table :model_names, options: "ENGINE=mroonga" do |t|
# point データタイプ
t.point :location, null: false
end
データはRuby On Railsでいつもやるようにモデルを経由してsave
していけば良いので、
四角いエリアのデータ検索
データを入れた後、
north_west = "#{north_west_longitude} #{north_west_latitude}"
south_east = "#{south_east_longitude} #{south_east_latitude}"
ModelName.where("MBRContains(GeomFromText('LineString(#{north_west}, #{south_east})'), location)")
難しいことは抜きにして、
Google マップAPIのgetBounds()
から表示中の南西と北東の緯度経度
半径何kmのデータ検索
四角いエリアは先述のように検索できるのですが、
実際にはストレージモードの特徴を使い、
# config/initializers/mysql_datadir.rb
# MySQLのコマンドでdatadirを探す
result = ActiveRecord::Base.connection.execute 'show variables like "datadir"'
MYSQL_DATADIR = Hash[*result.first]["datadir"].freeze
# app/controllers/application_controller.rb
# mroongaデータベースファイル(*.mrn)を見つける
def mroonga_database
Pathname(MYSQL_DATADIR).join "#{Rails.configuration.database_configuration[Rails.env]["database"]}.mrn"
end
# table_name | mroongaデータベース登録のテーブル名 = :model_namesなどmigrationでのテーブル名(複数形)と同じ
def radius_search(table_name, longitude, latitude, km)
filter = Shellwords.escape 'geo_in_circle(location,"#{latitude.to_f},#{longitude.to_f}",#{km * 1000})'
# --limit -1を指定しないとデフォルトでは最大10件のみで返される(当初ハマりました)
results = JSON.parse `groonga #{mroonga_database} select #{table_name} --filter '#{filter}' --limit -1`
# 結果配列から_keyインデックス位置の値を取って集める
data = results[1].first
attributes = Hash[*data[1].flatten]
key_index = attributes.keys.index "_key"
stations = data[2..-1]
ids = stations.collect do |station|
station[key_index]
end
# ActiveRecordから対象のデータを検索
relation = table_name.to_s.camelize.singularize.constantize.where(id: ids)
end
色々やっていますが、_key
から集めていき、
まとめ
今回groongaで全文検索の機能は使っていませんが、
一つ問題があるとすれば、
特にmigrationファイルではインデックスにコメントをつけることができず、
# config/initializer/mroonga_index.rb
module MroongaIndex
def mroonga_index(table_name, column_name, options = {})
index_type = if options[:parser].present?
MYSQL_INDEX_FULLTEXT
end
index_sql = %|CREATE #{index_type} INDEX index_#{table_name}_on_#{column_name} ON #{table_name}(#{column_name})|
index_sql = mroonga_fulltext_index(index_sql, options) if index_type == MYSQL_INDEX_FULLTEXT
execute index_sql
end
private
MYSQL_INDEX_FULLTEXT = "FULLTEXT"
def index_comment?
@mysql_version = `mysql --version` rescue nil
@mysql_version =~ /Distrib ([\d\.]+?),/
major, minor, patch = $1.to_s.split(".")
5 <= major.to_i and 5 <= minor.to_i
end
def mroonga_fulltext_index(index_sql, options)
raise <<-ERROR unless index_comment?
mysql version needs to be 5.5 and later
#{@mysql_version || "mysql not installed"}
ERROR
parsers = %w[TokenBigram
TokenMecab
TokenBigramSplitSymbol
TokenBigramSplitSymbolAlpha
TokenBigramSplitSymbolAlphaDigit
TokenBigramIgnoreBlank
TokenBigramIgnoreBlankSplitSymbol
TokenBigramIgnoreBlankSplitSymbolAlpha
TokenBigramIgnoreBlankSplitSymbolAlphaDigit
TokenDelimit
TokenDelimitNull
TokenUnigram
TokenTrigram]
raise "parser not defined" unless parsers.include?(options.fetch(:parser){nil})
parser = options.fetch(:parser)
%|#{index_sql} COMMENT 'parser "#{parser}"'|
end
end
module ActiveRecord
class Migration
include MroongaIndex
end
end
この変更は次のように使います。
# db/migrate/xxxxxxx.rb
create_table :model_names, options: "ENGINE=mroonga" do |t|
t.string :address, null: false
end
# 上のモンキーパッチをこんな感じで使い全文検索のインデックスコメントを入れる
mroonga_index(:model_names, :address, parser: "TokenBigram")
全文検索をするときはMATCH AGAINSTを使います。
# app/controllers/xxx_controller.rb
def search
# 全文検索する時はMATCH AGAINSTで日本語もOK
ModelName.where("MATCH(address) AGAINST(?)", params[:keywords])
end
WindowsからMacに乗り替えて、
すこし前まではインストールはソースからなどと、
全文検索はどうも難しそうだと思われていた方、