webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

現在地の標高を教えてくれるLineBot作った

こんにちは、エンジニアのさもです。

前回は、何かテキストを送ると名言を返してくれるBotを作りました。

今回はその続編で、位置情報を送ると標高を返してくれるBotを作りました。 (注:Lineで現在地を送るにはスマホからしかできないっぽいです)

スポンサーリンク

はじめに

前回の続編なので、Botを作るまでの準備(チャンネルの登録やオウム返しBotが実装済みであること)が出来ている前提とします。

Rails版のオウム返しBotの実装例は以下に書いています。

実装

API

今回はこちらのAPIを利用させてもらいます。

LatLng2Height:::緯度経度から標高算出API

経度、緯度を送ると標高などの情報がxml形式で帰ってきます。

利用方法は、Line経由で送られてきた位置情報から経度、緯度を取り出し、 http://lab.uribou.net/ll2h/?ll=緯度,経度へrubyでアクセスします。

コントローラの実装

githubで紹介されているのはsinatraを使うことを想定しているので、 まずは、オウム返しBotのRails版を書いておきます。

チャンネルシークレットと、アクセストークンはチャンネル登録で取っておいたものを書いてください。

今回は簡単のためソースに書いていますが、実際は環境変数に書いておき、それを取ってくるようにしてください。

require 'line/bot'

class WebhookController < ApplicationController
  protect_from_forgery with: :null_session
  before_action :set_line_client

  CHANNEL_SECRET = '自分のCHANNEL_SECRET'
  CHANNEL_ACCESS_TOKEN = '自分のアクセストークン'

  def callback
    body = request.body.read
    unless @client.validate_signature(body, request.env['HTTP_X_LINE_SIGNATURE'])
      error 400 do 'Bad Request' end
    end
    event = @client.parse_events_from(body)[0]
    case event.type
    when "text"
      message = {
        type: 'text',
        text: event.message['text']
      }
    else
      message = {
        type: 'text',
        text: "メッセージを送ってね"
      }
    end
    @client.reply_message(event['replyToken'], message)
    render :nothing => true, status: :ok
  end

  private

  def set_line_client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = CHANNEL_SECRET
      config.channel_token = CHANNEL_ACCESS_TOKEN
    }
  end
end

それでは、このコードに位置情報が送られてきたら標高を返す処理を追加していきます。

  • ライブラリの追加

APIを利用するために、require 'open-uri'、xml解析のためにrequire 'rexml/document'require 'line/bot'の下の行に追加します

  • caseにメッセージタイプが位置情報だったときの処理を追加します
    when "location"
      message = {
        type: 'text',
        text: return_location_height(event.message)
      }

return_location_heightが実際に返すメッセージを組み立てるメソッドです。

  • return_location_heightの実装

位置情報は以下のフォーマットで送られてきます

{
    "type": "location",
    "title": "my location",
    "address": "〒150-0002 東京都渋谷区渋谷2丁目21−1",
    "latitude": 35.65910807942215,
    "longitude": 139.70372892916203
}

ここで最低限必要なのは、緯度(latitude)と経度(longitude)です。あとは住所なんかを表示させたいのでaddressもとっておきます。

  def return_location_height(message)
    ret_msg = message['address'] + "の標高は"
    lat = message['latitude']
    lon = message['longitude']
    body = open("http://lab.uribou.net/ll2h/?ll=#{lat},#{lon}", &:read)
    doc = REXML::Document.new(body)
    ret_msg += doc.elements['result/height'].text + "mです"
    ret_msg
  end

open("http://lab.uribou.net/ll2h/?ll=#{lat},#{lon}", &:read)の結果は以下のようなxmlです。

<result>
 <version>0.1</version>
 <error>0</error>
 <coordinate><lat>35.681</lat><lng>139.767</lng></coordinate>
 <height>11.87</height>
 <unit>m</unit>
 <dem>SRTM3</dem>
</result>

REXML::Documentを使えば、階層構造になっているxmlをdoc.elements['result/height']のように直感的にアクセスすることが出来ます。

もうxml怖くない!

最終的には、以下のようなコードになります

require 'line/bot'
require 'open-uri'
require 'rexml/document'

class WebhookController < ApplicationController
  protect_from_forgery with: :null_session
  before_action :set_line_client

  CHANNEL_SECRET = '自分のCHANNEL_SECRET'
  CHANNEL_ACCESS_TOKEN = '自分のアクセストークン'

  def callback
    body = request.body.read
    unless @client.validate_signature(body, request.env['HTTP_X_LINE_SIGNATURE'])
      error 400 do 'Bad Request' end
    end
    event = @client.parse_events_from(body)[0]
    case event.type
    when "text"
      message = {
        type: 'text',
        text: return_message
      }
    when "location"
      message = {
        type: 'text',
        text: return_location_height(event.message)
      }
    else
      message = {
        type: 'text',
        text: "メッセージか位置情報を送ってね"
      }
    end
    @client.reply_message(event['replyToken'], message)
    render :nothing => true, status: :ok
  end

  private
  
  def return_message
    open("http://www.meigensyu.com/quotations/view/random") do |file|
      page = file.read
      page.scan(/<div class=\"text\">(.*?)<\/div>/).each do |meigen|
        return meigen[0].encode("sjis")
      end
    end
  end
  
  def return_location_height(message)
    ret_msg = message['address'] + "の標高は"
    lat = message['latitude']
    lon = message['longitude']
    body = open("http://lab.uribou.net/ll2h/?ll=#{lat},#{lon}", &:read)
    doc = REXML::Document.new(body)
    ret_msg += doc.elements['result/height'].text + "mです"
    ret_msg
  end

  def set_line_client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = CHANNEL_SECRET
      config.channel_token = CHANNEL_ACCESS_TOKEN
    }
  end

end

実行例

  • まずbot君との画面で、下の赤枠のボタンを押します

f:id:s-uotani-zetakansu:20171030113026j:plain

  • 次に、下の画像の赤枠で囲んである「+」ボタンを押します

f:id:s-uotani-zetakansu:20171030113058j:plain

  • 位置情報というボタンがあるので、押します。

f:id:s-uotani-zetakansu:20171030113131j:plain

LineにGPSを許可している場合は、地図と、自分のいる位置が表示されるので、自分の位置をタップします。

  • すると、位置情報が送られて、標高が返ってきます

f:id:s-uotani-zetakansu:20171030113300j:plain

最後に

オウム返しBotを応用して標高を返すBotを作ってみました。

山とかで使うと面白そうですね。

サーバに送られてきさいすれば、webエンジニアの領域なので、もっと複雑なことも出来そうです。

以上、現在地の標高を教えてくれるLineBot作ったでした。

読者登録をしていただけると、ブログを続ける励みになりますので、よろしくお願いします。