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

leafletでのSpatialPolygonsDataFrameの扱いのメモ

前回の記事で、data引数にSpatialPolygonsDataFrame(以下、SPDF)を渡したときにどうなるかわからなかったのでメモ。

SpatialPolygonsDataFrameとは

そもそもこいつは何者かというと、spパッケージの地図データ用のdata.frameです。

?"SpatialPolygonsDataFrame-class"を見ると、以下のようなスロットを持っています。

data:
   Object of class "data.frame"; attribute table

polygons:
   Object of class "list"; see SpatialPolygons-class

plotOrder:
   Object of class "integer"; see SpatialPolygons-class

bbox:
   Object of class "matrix"; see Spatial-class

proj4string:
   Object of class "CRS"; see CRS-class

dataは各ポリゴンに関するデータ、polygonsはポリゴンのリストです。あとは細かそうなので省略。

SPDFはdata.frame同様に扱えます。[splitでサブセットを作ると、ポリゴンとそれに紐づくデータが適切にサブセットされます。

l[1:10, ] %>% is
#> [1] "SpatialPolygonsDataFrame" "SpatialPolygons"          "Spatial"                 

SPDF全体のサブセットではなく、データ(dataスロット)だけがほしいときは$を使います。

l$nam %>% is
#> [1] "factor"              "integer"             "oldClass"            "numeric"             "vector"              "data.frameRowLabels"

dataとかpolygonsとか、S4オブジェクトの要素(スロット)に直接アクセスしたいときは@を使います。

l@polygons %>% is
#> [1] "list"   "vector"

l@polygons[[1]] %>% is
#> [1] "Polygons"

leafletによる扱い

SPDFをdata引数に指定した場合は以下のような処理になるみたいです。

ポリゴン部分の扱い

  1. derivePolygons()
  2. polygonData()
  3. polygonData.SpatialPolygonsDataFrame()sp::polygons()を適用してSpatialPolygonsクラスに変換
  4. polygonData.SpatialPolygons()
  5. polygons2coords()
  6. plural2coords()

あとは追えないのでパス。とりあえずむりやりやってみると、以下のようになります。

leaflet:::derivePolygons(l, NULL, NULL, TRUE, TRUE) %>%
  .[[1]] %>%
  str(max.level = 2)
#> List of 1
#>  $ :List of 2
#>   ..$ lng: num [1:255] 141 141 141 141 141 ...
#>   ..$ lat: num [1:255] 43.2 43.2 43.1 43.1 43.1 ...

ということで、lng, latというベクトルを持つリストのリストができています。addPolygons()のドキュメントを見ると、lngのヘルプに以下のような記載があります。

(if not explicitly provided), it will be automatically inferred from data by looking for a column named lng, long, or longitude (case-insensitively)

つまり、lnglatを明示的に指定したりしなくても、ここで変換したポリゴンのdata.frameの緯度経度を勝手に使ってくれます。

データ部分の扱い

データ部分は~XXXという形式で列名を参照することができます。これはどういうことかというと、

color_generator <- colorNumeric("Oranges", domain = c(0, 1000))

leaflet() %>%
  addTiles() %>%
  addPolygons(data = poly, color = color_generator(poly$x))

と、poly$xという形で直接値を渡す部分を、

leaflet() %>%
  addTiles() %>%
  addPolygons(data = poly, color = ~color_generator(x))

のように書けるということです。(color_generator(~x)だとエラーになるので注意)

こっちのほうがかっこよさそうなので、前回のを書き直してみたいと思います。

SpatialPolygonsをSpatialPolygonsDataFrameに

rgeos::gUnaryUnion()した結果は@dataがないSpatialPolygonsというクラスのオブジェクトです。これにデータを紐づけてSPDFをつくります。

SPDFは、data.frameをrownameでポリゴンのIDと紐づけます。なので、rownameのあるdata.frameを作る必要があります。

# d: data.frame
# l_union: SpatialPolygons

orig_ids   <- sapply(l_union@polygons, function(x) x@ID)
target_ids <- sapply(str_split(str_to_lower(orig_ids), " "), `[`, 1)

id_crsp <- data.frame(orig_id   = orig_ids,
                      target_id = target_ids,
                      stringsAsFactors = FALSE)
id_crsp[13,"target_id"] <- "hokkaido"

d_ordered <- right_join(id_crsp, d, by = c( "target_id" = "pref_alpha" ))
rownames(d_ordered) <- id_crsp$orig_id

これをSpatialPolygonsDataFrame()に渡せばおわりです。

s <- SpatialPolygonsDataFrame(l_union, as.data.frame(d_ordered))

描く

これを~を使って書くと、以下のような書き方になります。

color_generator <- colorNumeric("Oranges", domain = range(values))

leaflet(s) %>%
  addTiles() %>%
  addPolygons(color = ~color_generator(チョコレート), fillOpacity = 1, stroke = FALSE)

(結果は前回と同じなので省略)

感想

一度SPDFを作ってからやるとデータとポリゴンが紐づけられるので間違いがなくて安心ですが、ちょっと手間です。rownameでマッチさせるというのがけっこうトリッキーでつらかったです。