Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Tellus から降水量を取得する

livebooks/tellus/precipitation.livemd

Tellus から降水量を取得する

Mix.install([
  {:nx, "~> 0.9"},
  {:evision, "~> 0.2"},
  {:req, "~> 0.5"},
  {:kino, "~> 0.14"},
  {:kino_maplibre, "~> 0.1"}
])

情報の設定

# Tellus のトークンを入力する
token_input = Kino.Input.password("Token")
# 降水量データの商品ID
rain_product_id = "51f86de1-777f-43e6-821f-a2c1c737cb8b"
base_url = "https://sbs.tellus-tools.com"

このノートブックではTellusの降水観測情報API(試用版)を使用しています

©島津ビジネスシステムズ

Tellus の認証

auth_url = "https://www.tellusxdp.com/api/manager/v2/auth/token/"
json_header = {"Content-Type", "application/json"}

get_auth_header = fn product_id ->
  request_body = %{product_id: product_id}
  auth_header = {"Authorization", "Bearer " <> Kino.Input.read(token_input)}

  auth_url
  |> Req.post!(json: request_body, headers: [auth_header, json_header])
  |> then(&amp;{"Authorization", "Bearer " <> &amp;1.body["token"]})
end

降水量の取得

get_precipitation = fn lon, lat, ddate ->
  auth_header = get_auth_header.(rain_product_id)

  "#{base_url}/tellus/wif82af3w39s/rap_af.php?lon=#{lon}&lat=#{lat}&ddate=#{ddate}"
  |> Req.get!(headers: [auth_header])
  |> then(&amp;Jason.decode!(&amp;1.body))
end
# 大分市のデータ
precipitation = get_precipitation.(131.62, 33.23, 20_190_909)
Enum.count(precipitation["prec"])
# 東京23区の経度範囲
lon_list =
  13956..13991
  |> Enum.map(&amp;(&amp;1 / 100))
# 東京23区の緯度範囲
lat_list =
  3552..3582
  |> Enum.map(&amp;(&amp;1 / 100))
Enum.count(lon_list) * Enum.count(lat_list)
precipitation =
  Enum.map(lon_list, fn lon ->
    Enum.map(lat_list, fn lat ->
      get_precipitation.(lon, lat, 20_190_909)
      |> Map.get("prec")
    end)
  end)

降水量の可視化

prec_tensor = Nx.tensor(precipitation)
Nx.reduce_min(prec_tensor)
max_prec =
  prec_tensor
  |> Nx.reduce_max()
  |> Nx.to_number()
alpha =
  prec_tensor
  |> Nx.multiply(255 / max_prec)
  |> Nx.slice_along_axis(0, 1, axis: 2)
{w, h, t} = Nx.shape(prec_tensor)
bgr =
  [255, 0, 0]
  |> Nx.tensor()
  |> Nx.tile([w, h, 1])
[bgr, alpha]
|> Nx.concatenate(axis: 2)
|> Evision.Mat.from_nx_2d()
|> Evision.resize({w * 10, h * 10})
|> Evision.convertScaleAbs(alpha: 3)
|> dbg()
get_mat = fn hour ->
  alpha = Nx.slice_along_axis(prec_tensor, hour, 1, axis: 2)

  [bgr, alpha]
  |> Nx.concatenate(axis: 2)
  |> Evision.Mat.from_nx_2d()
  |> Evision.resize({56 * 10, 20 * 10})
  |> Evision.convertScaleAbs(alpha: 3)
end
0..23
|> Enum.map(fn hour ->
  get_mat.(hour)
end)
|> Kino.Layout.grid(columns: 4)

地図へのオーバーレイ

get_data_url = fn mat ->
  Evision.imencode(".png", mat)
  |> Base.encode64()
  |> then(&amp;"data:image/png;base64,#{&amp;1}")
end
show_map_overlay = fn img_base64 ->
  {left_lon, right_lon, bottom_lat, top_lat} = {139.56, 139.91, 35.52, 35.82}

  center = {(left_lon + right_lon) / 2, (bottom_lat + top_lat) / 2}

  # タイルの中心を地図の中心にする
  MapLibre.new(center: center, zoom: 9, style: :street)
  # 画像をタイルの座標に配置する
  |> MapLibre.add_source(
    "sar-source",
    type: :image,
    url: img_base64,
    coordinates: [
      [left_lon, top_lat],
      [right_lon, top_lat],
      [right_lon, bottom_lat],
      [left_lon, bottom_lat]
    ]
  )
  # 画像をレイヤーとして地図に重ね、透過する
  |> MapLibre.add_layer(
    id: "overlay",
    source: "sar-source",
    type: :raster,
    layout: %{
      "visibility" => "visible"
    }
  )
end
5
|> get_mat.()
|> get_data_url.()
|> show_map_overlay.()
0..11
|> Enum.map(fn hour ->
  {
    hour,
    get_mat.(hour)
    |> get_data_url.()
    |> show_map_overlay.()
  }
end)
|> Kino.Layout.tabs()