Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

R+ShinyでGoogle Mapをいじるアプリケーションを作ってみる

本記事は、Shiny Advent Calendar 2017の7日目の記事です。



Shiny100本ノックも第10弾となりました。

そんな区切りの今回は、Google Mapの地図をいじるShinyアプリを作ってみたいと思います。

機能としては、

  • 検索した場所をプロットする
  • 2点間の距離を求める

というものを作ります。

Google Mapで実現できる内容ですが、今後手持ちのデータと組み合わせることで魅力的なアプリになりそうです。

例えば、

  • 2国間を選択したら貿易情報が地図上で可視化される
  • 都道府県間の人口移動を地図上で可視化する

などパッと思いつくだけでも楽しそうです!
今回はそのための導入編として、まずは地図を動かす機能を実装することを目的とします。

なお、R上で地図をいじることに関しては、以下のブログでとても詳しくまとめてあります。
こちらもご参考にして頂ければと思います。
https://kazutan.github.io/JapanR2015/leaflet_d.html

leafletライブラリ

Rで地図を描くために、今回はleafletライブラリを使います。leafletは、Web上で地図を描くためのJavascript用ライブラリになりますが、R言語でも使用可能となっております。

leafletの公式ドキュメントを見て頂くと、色々なメソッドが用意されているので、ご興味のある方は読んでみてください。
https://rstudio.github.io/leaflet/

また、Google Mapを読み込むために、ggmapというライブラリも使います。

まだインストールしていない方は、いつものように下記コマンドでインストールしておきましょう。

install.packages("leaflet")
install.packages("ggmap")

完成予定のアプリはこんな感じ!

事前準備が整ったところで、これから作るアプリのイメージ図を見てみましょう。

f:id:Np-Ur:20171203111412p:plain
左上に、場所を検索するためのテキスト入力欄があり、右側に地図を表示しております。
デフォルトでは、東京と千葉を入れていますが、その地点に印がついていることが分かるかと思います。

右上に怪しい四角いボタンがありますが、このボタンをクリックすることで、任意の点間の距離を求めることが出来ます。
f:id:Np-Ur:20171203111621p:plain

試しに、東京と千葉の点を結んでみると、下図のように、直線距離が計算されていることがわかります。
f:id:Np-Ur:20171203111749p:plain

このあとコードを紹介しますが、このぐらいの機能であればかなり簡単に作成することができます。

前述したようなデータを用意することで、オリジナル地図アプリもそこまで苦労することなく作ることができると思います。
何より地図を自分のアプリ上で動かすのはとても面白いです!!

server.Rとui.Rのソースコード

何度もやっていますが、Shinyはserver.Rとui.Rの2つのファイルを使うんでしたね。
早速今回のコードを見ていきましょう。

ui.Rのソースコード

library(shiny)
library(leaflet)
library(ggmap)

shinyUI(fluidPage(
  titlePanel("Google Mapを描写"),
  
  sidebarLayout(
    sidebarPanel(
      textInput("search_word1", "ワード1", value="東京"),
      textInput("search_word2", "ワード2", value="千葉"),
       
      h4("実行に数秒時間がかかります。"),
      h4("また、Gmap APIがエラーを返す場合があります"),
      actionButton("submit", "地図を描写")
    ),
    mainPanel(
      leafletOutput("plot", width="100%", height = "900px")
    )
  )
))

ui.Rは特に言及する部分がないのですが…笑

textInputを2つ作って、2つの地点を検索できるようにしております。
(デフォルトではとりあえず、東京と千葉を入力。)

そしてアクションボタンを置いて、このボタンを押されることでserver.R側に値が渡るようにしています。

また、mainPanelのleafletOutputの部分で地図を表示するようにしており、"plot"という変数をserver.Rから受け取る構造になっています。

ということで続いてserver.Rのソースコードを見ていきましょう。

server.Rのソースコード

library(shiny)
library(leaflet)
library(ggmap)

shinyServer(function(input, output) {
  
  values = reactiveValues(geocodes = rbind(c(139.6917, 35.68949), c(140.1233, 35.60506)))
    
  observeEvent(input$submit, {
    geo1 = geocode(input$search_word1)
    geo2 = geocode(input$search_word2)
    
    if(is.na(geo1[1,1])){
      geo1[1,] = values$geocodes[1,]
    }
    
    if(is.na(geo2[1,1])){
      geo2[1,] = values$geocodes[2,]
    }
    
    values$geocodes = rbind(geo1, geo2)
  })
  
  output$plot = renderLeaflet({

    geo1_lng = values$geocodes[1,1]
    geo1_lat = values$geocodes[1,2]
    geo2_lng = values$geocodes[2,1]
    geo2_lat = values$geocodes[2,2]

    map_data = leaflet() %>% 
      addTiles() %>%
      setView(lng = (geo1_lng + geo2_lng)/2,
              lat = (geo1_lat + geo2_lat)/2, zoom = 10) %>%
      addMarkers(lng = geo1_lng, lat = geo1_lat,
                 label = input$search_word1) %>%
      addMarkers(lng = geo2_lng, lat = geo2_lat,
                 label = input$search_word2) %>%
      addMeasure(position = "topright", primaryLengthUnit = "meters", 
                 primaryAreaUnit = "sqmeters", activeColor = "#3D535D",
                 completedColor = "#7D4479")
    return(map_data)
  })
})

コードを具体的に見ていきましょう。

geocodeメソッドで緯度経度を取得

まず、何もボタンが押されない時点で「東京」と「千葉」がプロットされるように、

values = reactiveValues(geocodes = rbind(c(139.6917, 35.68949), c(140.1233, 35.60506)))

と値を設定しています。

reactiveValuesというのは以前にも少し触れました。
www.randpy.tokyo

非常に便利で、一度宣言すると

values$a = 100
values$b = 200

values$a #100
values$b #200

というように値を柔軟に入力したり出力することができます。

次に、

observeEvent(input$submit, {# submitが押されたときに処理をつらつら})

と書くことで、ui.R側でsubmitボタンが押されたときにどんな処理をするかを書いていきます。

ggmapライブラリ中の、geocodeメソッドにて地名を入力すると緯度経度を取得することができます。
ui.R側で入力した「search_word1」と「search_word2」を引数にてgeocodeメソッドを実行します。

なお、リクエストを何度か送っていると、API側のエラーで「NA」が返ってくることがあります。
そこで仮に「NA」が返ってきた場合に、デフォルトの東京と千葉の緯度経度を返すようにちょっと面倒な処理を書いています。

そのあと先ほどreactiveValuesとして定義した、values変数に2点の緯度経度情報を代入しています。

values$geocodes = rbind(geo1, geo2)

renderLeafletメソッドで地図を表示

そして、values$geocodes の値(緯度経度情報)を元に地図を表示しているのが、下の部分です。

output$plot = renderLeaflet({## どんな地図を表示するか処理をつらつら})

renderLeafletというメソッドを使って、可変的に地図を変化させることができます。
(Shiny上でいじれるように、メソッドが用意されてます!素晴らしい!)

そして、

map_data = leaflet() %>%

以降が、具体的に地図をどのように表示するかを変えている部分になります。

まずsetViewで、どの部分にフォーカスするかを決めています。
今回は検索した2地点間の真ん中にフォーカスするように緯度経度情報を計算して渡しています。

addMarkersという関数は、緯度経度情報を入力して、その地点にマーカーをつける処理をしています。
lng=~で経度、lat=~で緯度情報をインプットし、オプションでlabelをつけると、そのマーカーにカーソルをあわせたときに場所名を表示することが出来ます。

そして、addMeasureという部分では、選択した地点間の距離を測れるように設定しています。

今回使用したleafletの機能は、ほんの一部分でしかないので、他にも知りたい方は公式ドキュメントを参考にしてみてください。
https://rstudio.github.io/leaflet/markers.html

最後に

今回leafletとggmapいうライブラリを使って、Shiny上で地図をいじれるアプリケーションを作ってみました。

leafletライブラリは本当に色々な機能が備わっているので、公式ドキュメントを見ながら色々試してみてください。

今回作成した機能に続いて、前述したような、データを元にした地図上での可視化アプリも作ってみたいと思います。
随時記事にする予定ですので、ご期待して頂けますと幸いです。