プログラミングを頑張る土木系専攻大学院生のブログ

主にプログラミングについて開発備忘録的な形で投稿しています。

【2025年10月】 SupabaseでSQLiteからPostgreSQLへ移行する方法!!~ UIの落とし穴と移行手順まとめ

SQLiteで開発していたローカルWebアプリを、PostgreSQLへ移行する必要がありました。
ChatGPTに相談したところ、Supabaseという選択肢を提案してくれました。

「なるほど、PostgreSQLがサーバレスで使えるなら楽そう」と思い、Supabaseを採用してみたのですが……
DB接続情報の場所が分からず大苦戦。

この記事では、Supabaseの最新UIでPostgreSQL接続情報を探す方法
そして FastAPI + SQLModel 環境でPostgreSQLへ切り替える手順をまとめます。


🔧 開発環境

  • バックエンド:FastAPI
  • フロントエンド:React
  • DBSQLite → Supabase(PostgreSQL

✍️ なぜブログ化したのか?

ローカル開発では SQLite を使っていましたが、将来の運用も見据えてPostgreSQL への移行を決意。

ChatGPTに相談したところ、Supabaseを提案されました。

さっそく使ってみたものの…

「接続情報どこ!?見当たらない!!」

SupabaseのUIが変わっており、ChatGPTの回答通りに探しても全然見つからない。
おそらく、同じように迷う方が多いと思うので、この記事が参考になれば幸いです。


🛠 Supabase のセットアップ手順

① アカウント・プロジェクト作成

ここはネットに情報が多くあるので割愛します。

おすすめ記事: Supabaseの概要と環境構築(CraftStadium)


PostgreSQL 接続情報の取得(ここがハマりポイント)

ChatGPTからは以下のような回答がありました。

最新UIでの「PostgreSQL接続情報」の探し方

  1. 「Settings」→「Database」または「Database Settings」へ移動
  2. 「Connection string」や「Connection info」セクションを探す
  3. ここに「接続文字列(Connection string)」や「Host」「Port」「User」「Password」などが個別に表示されているはずです。
    • もし「Connection string」が直接見つからない場合は、下記の情報を組み合わせて自分で作成します。

…でも、Settingsにそんな項目ありませんでした。


✅ 正解:ヘッダーの「Connect(🔌)」アイコンから確認!

Supabaseの最新UIでは、プロジェクト画面右上の「Connect」ボタン(コンセントマーク)から接続情報を確認できます。

  • Host / Port / User / Password / Connection string
  • REST APIやGraphQL APIなどもまとめて表示されます

📷 接続情報の場所(画像)

🔌 Connect ボタンの場所

📋 接続情報一覧画面

🔄 FastAPI 側のDB設定をPostgreSQLに変更する手順

💡 使用ライブラリ

fastapi  
sqlmodel  
sqlalchemy  
uvicorn  
requests  
python-dotenv  
alembic  
psycopg2-binary

.env ファイルを用意・設定

.env ファイルに以下を記述します:

DATABASE_URL=postgresql://postgres:<パスワード>@<db-name>.supabase.co:5432/postgres
  • <パスワード><db-name> はSupabaseの「Connect」で確認した情報に置き換えてください。

database.py の修正

SQLiteからPostgreSQLへの切り替えは、以下のように書き換えます。

✅ 変更前(SQLite

sqlite_file_name = "test.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True, connect_args={"check_same_thread": False})

✅ 変更後(PostgreSQL

import os
from dotenv import load_dotenv
from sqlmodel import SQLModel, create_engine

load_dotenv()
db_url = os.getenv("DATABASE_URL", "sqlite:///test.db")
engine = create_engine(db_url, echo=True)

🔹 PostgreSQLでは connect_args={"check_same_thread": False} は不要です。


③ Alembic の設定変更

alembic.ini の変更:

sqlalchemy.url =

空欄にしておきます。

alembic/env.py の変更:

import os
from dotenv import load_dotenv

load_dotenv()
url = os.getenv("DATABASE_URL")

# configオブジェクトにURLを設定
config.set_main_option("sqlalchemy.url", url)

これで .env 経由で接続できます。


④ 起動・マイグレーション確認

FastAPI起動:

uvicorn main:app --reload

DBマイグレーション実行:

alembic upgrade head

🎉 成功画面(画像)

PostgreSQLに接続された状態でFastAPIが起動できました!


✅ まとめ

項目 ポイント
Supabase UIの罠 接続情報は「Settings」ではなく「Connect」ボタンから
.envの記述 Supabaseの情報を正確に反映
create_engineの引数 SQLitePostgreSQLでは異なる(connect_args不要)
Alembic対応 .envから接続情報を取得する構成に変更

💬 おわりに

Supabaseは簡単にPostgreSQLを使える便利なサービスです。

この記事が、これからSQLitePostgreSQL(Supabase)に移行しようとしている方の助けになれば幸いです!


Pythonで地理情報データをあつかう(rasterioを使ったラスタデータの読み込み)

キーワード: Python , rasterio , GIS , ラスタデータ

都市データの可視化や環境解析、リモートセンシングなど、地理情報データ(GIS)の活用は年々広がっています。

本記事では、Pythonでラスタ(画像形式の地理データ)を扱うための定番ライブラリ「rasterio」について、基本操作から実例までを詳しく解説します。

📘 公式ドキュメント:
👉 https://rasterio.readthedocs.io/en/stable/


🔰 rasterioとは?

rasterio は、GeoTIFFをはじめとするラスタ形式の地理空間データを、Pythonで直感的かつ高速に扱えるライブラリです。

  • GeoTIFF、.img、.asc など、多くの形式に対応
  • NumPyとシームレスに連携
  • GDAL(地理空間データ抽象ライブラリ)をPythonから安全に利用できる
  • メタデータ、位置情報(CRS)、アフィン変換(後で解説します!)なども簡単に取得可能

「地理情報のある画像データ(衛星画像、標高データなど)を読む・処理・書き出す」ことに特化しています。


🗺 本記事で扱う内容

  • rasterioによるGeoTIFFの読み込み
  • バンド情報や座標情報の取得
  • アフィン変換(transform)の理解と使い方
  • ピクセル座標と地理座標の変換

📚 シリーズ記事一覧(予定)

  • [今回] rasterioでGeoTIFFを読み込む・基本情報を取得
  • 🔜 rasterio × matplotlib でラスタ画像を表示する
  • 🔜 地理座標の範囲で画像を切り出す(クリッピング
  • 🔜 rasterioで新しいGeoTIFFを書き出す

📂 1. GeoTIFFファイルの読み込み

import rasterio

# ファイルのパス
tif_path = 'example.tif'

with rasterio.open(tif_path) as src:
    print(src)  # 基本情報を表示

🧾 2. ファイルの基本情報を確認する

with rasterio.open(tif_path) as src:
    print("ファイルパス:", src.name)
    print("バンド数:", src.count)
    print("画像サイズ(高さ, 幅):", src.height, src.width)
    print("座標参照系(CRS):", src.crs)
    print("アフィン変換:", src.transform)
    print("データ型(dtype):", src.dtypes)
    print("バンド名:", src.descriptions)

✅ 出力例(3バンドGeoTIFF)

ファイルパス: example.tif  
バンド数: 3  
画像サイズ(高さ, 幅): 512 512  
座標参照系(CRS): EPSG:32648  
アフィン変換: | 30.00, 0.00, 405000.00|  
               | 0.00,-30.00, 2073000.00|  
データ型: ['uint16', 'uint16', 'uint16']  
バンド名: ('Red', 'Green', 'Blue')  

🖼 3. バンドデータをNumPy配列として読み取る

with rasterio.open(tif_path) as src:
    band1 = src.read(1)  # 1番目のバンド(インデックスは1から)
    print("バンド1の形状:", band1.shape)
    print("最小値:", band1.min(), "最大値:", band1.max())

band1 は NumPyの2次元配列なので、画像処理や統計処理がそのまま可能です。


📌 4. よく使う属性まとめ

属性名 内容
src.count バンド数
src.width, src.height 画像サイズ(ピクセル数)
src.crs 座標参照系(EPSGなど)
src.transform アフィン変換行列(位置・解像度)
src.dtypes バンドごとのデータ型
src.descriptions バンドの説明(名前)
src.read(i) i番目のバンドをNumPy配列で取得(1始まり)

🧭 5. アフィン変換(transform)の仕組み

rasterio.transform プロパティは、ラスタ画像のジオリファレンス(地理参照)情報を表す = 「画像のどのピクセルが、どの地理座標に対応しているか」

📌 「画像のピクセル座標 (列番号, 行番号) → 地理座標(x, y)」 に変換できる。

🔍 transformとは?

.transform プロパティは、ピクセルの位置(行列番号)と地理座標の関係を表すアフィン変換行列です。

たとえば: 入力

with rasterio.open(tif_path) as src:
    print("アフィン変換:", src.transform)

出力

アフィン変換(30.0, 0.0, 405000.0,
       0.0, -30.0, 2073000.0)

これは次のように解釈できます:

パラメータ 意味
a ピクセル幅(x方向解像度) 30.0
b x方向の回転(通常0) 0.0
c 左上ピクセルのx座標 405000.0
d y方向の回転(通常0) 0.0
e ピクセル高さ(y方向解像度・通常負) -30.0
f 左上ピクセルのy座標 2073000.0

📐 ピクセル → 地理座標の変換式

x = a * col + b * row + c
y = d * col + e * row + f

※ 回転がない場合、簡単に:

x = a * col + c
y = e * row + f

🧪 6. ピクセル (100, 200) の地理座標を求める

from rasterio import open

with open('example.tif') as src:
    transform = src.transform
    col, row = 100, 200
    x, y = transform * (col, row)
    print(f"地理座標: ({x}, {y})")

🔁 7. 逆変換(地理座標 → ピクセル

row_col = ~transform * (x, y)
print(f"ピクセル位置: {row_col}")

~transform によって逆変換が可能です。地理座標から対応する画素位置を知るときに便利!


🔧 transformが使われる場面

  • 地図へのオーバーレイ表示(座標一致が必要)
  • 特定の緯度経度からピクセルを抽出
  • QGISなどGISツールとの連携
  • 書き出すGeoTIFFに正確な位置情報を埋め込む

✅ まとめ

  • rasterio は、地理空間ラスターデータをPythonで読み込むための基本ツール
  • .open() で安全に読み込み、.read() でNumPy配列化
  • .transform によって、ピクセル ↔ 地理座標の変換が自在
  • 画像処理・座標処理・可視化の基盤としてとても重要!

📘 次回予告(連載予定)

  • matplotlibfoliumを使ったラスタ画像の可視化
  • rasterioで地理座標範囲を指定して切り出し(clip)
  • バンド演算や正規化などのデータ処理
  • 新しいGeoTIFFを生成・保存する方法

📎 参考リンク


🚀 次の記事を読みたい方は、ブックマーク・読者登録をお願いします!

とにかく「できること(can)」を増やす ─ 理系院生の僕が大事にしているマインド

とにかく「できること(can)」を増やす ─ 理系院生の僕が大事にしているマインド


記事の概要

現在理系院生で就活中の僕が大事にしているマインドについて書いてみました。

今まではプログラミング関連の記事ばかりでしたがたまにはこういうのもありかなと思い作成しました。

就活中や転職活動中の方など、少しでも参考になれば幸いです。

「やりたいこと」が見つからない

就活をしていると、耳にタコができるほど聞く言葉があります。

「自己分析をして、やりたいことを見つけよう」

でも、正直これって難しくないですか?

僕自身も現在、大学院で土木工学を学びながら就職活動をしていますが、ずっとこんなモヤモヤを抱えていました。

「自分のやりたいことが、はっきりしない。見つからない。」

はじめに言うと、今も「やりたいこと」なんて見つかってません。 ですがそれが正解だと思っています。不安なまま進んでいくのが正解だと思っています。 やったこともないのに、それがこの先本当に「やりたいこと」だなんて断定して生きていく必要ってないよねって感じながら 生きています。


やったこともないのに「やりたい」と言い切れる? 

「やりたいこと」って無意識に「今できること」に結びついている

大学3年の頃の僕は、土木工学しか学んでいませんでした。
当時は「公務員になりたい」と思っていましたが、それは建設業界=ブラックという漠然としたイメージから逃げたかっただけかもしれません。

その頃の僕は、今のようにITで課題解決をしたいなんて、1ミリも思っていませんでした。

なぜなら、知らなかったからです。やったことがなかったからです。

多くの人が「自分の現在地(スキルや経験)」の延長線上で、できそうなこと=やりたいことと捉えてしまいがちです。僕もそうでした。


まずは「できること(can)」を増やしてみる

今、僕が就活を通して大事だと感じているのは、

「やりたいこと」を探す前に、「できること(can)」を増やすこと

という考え方です。

やってみる前から「これがやりたい」「あれは違う」と決めつけるのはもったいない。

「できること(can)」を増やせば、色んな仕事にチャレンジできます。しかし、それがなければ仕事の選択肢ってとても限られます。

特に文系大学生の方は資格取得や専門的なスキルを学ぶことを強く勧めます!

医者になりたいって思っても、医学を学ばないと絶対になれないですよね。 スキルが無いと選ぶ権利すら得ることができないのが仕事です。

実際僕は、ITを学びはじめたことでかなり選択肢が増えたことを実感しています。

▼ 実際やってみるとギャップに気づく

僕はかつて、リモートワークに憧れていました。
時間にも場所にも縛られない、自由なライフスタイル(笑)――まさに理想でした。

しかし、実際にフロントエンドエンジニアとして長期インターンでリモート勤務を経験してみると、理想とのギャップに気づきました。

  • コミュニケーションが難しい
  • 仕事とプライベートの境界が曖昧になる
  • 孤独感もある 。僕は結構寂しがりだなって気づきました。

自由=快適 とは限らないことを実感しました。
もちろん、人によっては「最高」だと感じる人もいるでしょう。


「やってみた」からこそ気づけたこと

ただ、やってみたからこそ意外な発見もありました。

たとえば僕は、自分が

「価値提供できた」と感じる瞬間に、大きなやりがいを感じる

ということに気づいたんです。

学んできたAIやプログラミングの知識が思わぬ形であれ、業務の効率化に貢献出来た経験があります。 その際に社員に褒められたのは今でも思い出になっています。


「やりたいこと」は、経験の中で見えてくる

行動していく中で、

  • どんなときに面白いと感じるか
  • どんなときにやりがいを感じるか

といった、自分の内面が少しずつ見えてくるようになります。
その積み重ねの中で、「やりたいこと」が後から浮かび上がってくるのだと思います。


🎓 最後に

「やりたいことを見つけて、それを仕事にしよう」

これは理想ですが、いきなり正解を求めなくてもいいと思います。

むしろ、

「できること(can)」を増やして、試してみる。その中から“やりたい”が見つかればラッキー。

というマインドで動いてみるのも、全然アリです。

迷いながら、悩みながら、それでも前に進んでいく。
そんな就活やキャリアも、きっと悪くないはずだと勝手に思っています!


【Python】都市気温シミュレーションで都市環境を分析してみた!プログラムも公開!

Python】都市気温シミュレーションで都市環境を分析してみた!プログラムはDL可能!


概要

都市の地表面・地中温度、都市気温の時系列変化をPythonでシミュレーションできるプログラムを作成しました。 気象データ(日射・大気放射)をもとに、差分法と熱収支モデルを用いて都市の熱環境を再現します。

以下の計算条件を設定することで、お好みの条件に合わせて、都市気温をシミュレーションできます。

  • 季節 → 夏・冬
  • 蒸発効率 → 植物の蒸散など蒸発による都市気温の冷却効果を調節
  • 人工排熱量 → 都市では車、エアコンなどの家電による人口排熱の影響を考慮
  • 風速
  • 地中の条件(深さ、空間刻み、地中下端温度など)
  • 日射吸収率 

ヒートアイランド現象の分析や都市環境の熱的影響評価、人工排熱や蒸発効率の効果検証など、都市の気温に関する様々な課題に活用できます。 また、Githubリポジトリにて、都市気温の算出過程を簡潔にまとめているので、参考になれば幸いです。


どんなことが分析できる?🔍

  • 都市の気温・地表面温度・地中温度の時間変化
  • 季節(夏・冬)や人工排熱、蒸発効率の違いによる都市気温の変化
  • 地中温度分布や地表面の熱収支
  • ヒートアイランド対策の効果検証
  • 都市計画や環境設計への応用

プログラムの使い方🖥️

Githubリポジトリ

urbanTemperatureSimulation (GitHub)

必要なもの

  • Python(3.8以上推奨)
  • 必要なライブラリ(numpy, pandas, matplotlib, japanize-matplotlib)

実行手順

  1. リポジトリをクローン
  2. 必要なライブラリをインストール bash pip install -r requirements.txt
  3. 入力データ(data/夏の全天日射量と大気放射量.csvdata/冬の全天日射量と大気放射量.csv)を配置。リポジトリをクローンした場合はそのままでOK
  4. src/calcUrbanTemperature.py 内のパラメータ(季節・蒸発効率・人工排熱量など)を設定
  5. プログラムを実行 bash python src/calcUrbanTemperature.py
  6. 結果(CSV・グラフ画像)が出力されます

シミュレーション結果の例📊 蒸発効率と都市気温の関係🌱🌡️

都市の気温は、地表面からの蒸発(潜熱フラックス)が大きく影響します。 ここでは、蒸発効率(β)を変化させた場合に都市気温がどのように変化するか、夏と冬それぞれでシミュレーションした結果を紹介します。


条件

  • 人工排熱は 0 で固定
  • 蒸発効率 β を 0, 0.2, 0.4, 0.6, 0.8, 1.0 の6パターン
  • 夏:最高 31℃、最低 21℃、地表面近傍大気の水蒸気圧 2750Pa、地下初期温度 26℃
    • 冬:最高 21℃、最低 2℃、地表面近傍大気の水蒸気圧 660Pa、地下初期温度 10℃

夏季:蒸発効率による都市気温の変化

蒸発効率 最高気温 (℃) 最低気温 (℃) 平均気温 (℃)
0.0 40.22 20.54 28.56
0.2 36.44 20.91 27.29
0.4 34.83 21.12 26.70
0.6 33.81 21.25 26.31
0.8 33.09 21.34 26.03
1.0 32.53 21.41 25.80

ポイント

  • 蒸発効率が高まるにつれて、最高気温が大幅に低下します。
    • β=0とβ=0.2で約4℃の差!
    • 土壌や水辺、緑地が少しあるだけでもヒートアイランド現象の緩和効果は大きい
  • 最低気温はわずかに上昇
    • 日中の熱の貯留が減り、夜間の放射が減るため最低気温が下がりづらい
    • 平均気温も蒸発効率が高まるほど減少し、蒸発による冷却効果が確認できました

冬季:蒸発効率による都市気温の変化

蒸発効率 最高気温 (℃) 最低気温 (℃) 平均気温 (℃)
0.0 24.49 2.35 11.11
0.2 22.88 2.25 10.39
0.4 21.78 2.17 9.76
0.6 20.89 2.11 9.22
0.8 20.07 2.05 8.75
1.0 19.04 2.00 8.32

グラフの例:冬季、蒸発効率0

冬季の都市気温の変化

ポイント

  • 夏と同様、蒸発効率が高くなるほど最高・平均気温が低下
  • 最高気温の下がり幅は夏場より小さい
  • 最低気温の変化はごくわずか(夏は最低気温が上がったが、冬は僅かに下がった)
  • 平均気温の減少幅は約2.8℃で夏と同等

まとめ・活用例

  • 都市の熱環境を手軽にシミュレーションできるので、研究・教育・都市計画・環境設計など幅広く活用可能!
  • パラメータを変えることで、様々な都市環境や対策の効果を比較できます。

【Python】データ分析ライブラリの使い分け10選|pandas・numpy・mathはこう使い分ける!

Python】データ分析ライブラリの使い分け10選|pandas・numpy・mathはこう使い分ける!

Pythonでデータ処理や数値計算を行う際、あなたはpandasnumpymathなどのライブラリをどう使い分けていますか?

何となく使っていて「これ、どれで書くのが正解?」と迷ったことはないでしょうか?

本記事では、処理速度・記述効率・コードの見通しといった観点から、それぞれのライブラリをどう使い分ければよいか、実例と丁寧な解説つきで10パターン紹介します。


💡 はじめに:各ライブラリの特性を整理

ライブラリ 主な用途 特徴
pandas 表形式データの操作・整形・入出力 表計算のような柔軟性、列・行ベースの操作に強い
numpy 配列・ベクトル・行列の高速計算 Cベースの高速演算、ベクトル化処理に最適
math スカラー値の数式処理 軽量で関数単位の数値処理に特化

✅ パターン①:数値演算はpandasではなくNumPy配列で行う

import pandas as pd
import numpy as np

df = pd.read_csv('data.csv')
values = df['value'].to_numpy()  # NumPy配列に変換
result = np.log(values + 1)      # ログ計算(ベクトル化)

解説:

pandasのSeriesやDataFrameでも数値演算は可能ですが、内部的にはNumPy配列を使っています。そのため、演算処理ではNumPyに変換して行ったほうが処理速度も記述の明確さも向上します。

特に、ループ処理を避けたい場合や、大量の数値に対する一括処理が必要な場面では、NumPy配列に変換してベクトル演算を行うのがベストです。


✅ パターン②:平方根の繰り返し処理はNumPyでベクトル演算する

import numpy as np

arr = np.array([1, 4, 9, 16, 25])
result = np.sqrt(arr)

解説:

math.sqrt()スカラー(単一の値)にしか使えず、リストや配列に対してはループ処理が必要です。これに対し、numpy.sqrt()配列全体に対して高速に一括処理できます。

大量のデータを処理する場合、for文を避けてベクトル演算することで、10倍以上の処理速度改善も珍しくありません


✅ パターン③:定数はmathでなくnumpyから取得する

import numpy as np

theta = np.pi / 2
print(np.sin(theta))  # → 1.0

解説:

math.pinumpy.piはどちらも同じ円周率を表しますが、NumPyの定数はそのまま配列演算に組み込めるという利点があります。

例えば、複数の角度に対して三角関数を使いたいとき、mathではエラーになりますが、numpy.sin()numpy.piの組み合わせならベクトル演算が可能です。


✅ パターン④:条件分岐はNumPyのwhere()でベクトル化する

import numpy as np

scores = np.array([45, 80, 67, 92])
labels = np.where(scores >= 70, 'Pass', 'Fail')

解説:

条件付きラベル付けやフィルタ処理はforif-elseで書くと冗長になりがちですが、np.where()を使えば配列全体に対して高速かつ明解な一括処理が可能です。

コードの可読性を保ちつつ、ループをベクトル処理に置き換えられる代表的なテクニックです。


✅ パターン⑤:apply()は便利だが遅い。数値処理はNumPy優先

# 良い例(ベクトル演算)
df['log'] = np.log(df['value'] + 1)
# 悪い例(遅くなる)
import math
df['log'] = df['value'].apply(lambda x: math.log(x + 1))

解説:

apply()は関数を行や列に適用できる柔軟な機能ですが、内部でPythonの関数を逐次呼び出しているため遅くなることが多いです。

処理対象が単純な数値変換(対数、平方根、正規化など)の場合は、NumPyで一括処理した方が、処理速度も数倍高速になりますし、コードも短く明快です。


✅ パターン⑥:スカラー処理はmath、配列処理はnumpyを使う

import math
print(math.sin(math.pi / 2))  # スカラーにはOK
import numpy as np
angles = np.array([0, np.pi/2, np.pi])
print(np.sin(angles))  # 配列にはNumPy

解説:

mathモジュールは軽量で、スカラー(単一値)に対する関数(sin, log, sqrtなど)を実行するには非常に適しています。

ただし、配列に対してはエラーになります。一方、numpyは配列・行列に対する演算を効率的に処理できるため、用途に応じて明確に使い分けることが重要です。


✅ パターン⑦:統計量(平均・標準偏差)はNumPyで一括取得

arr = np.array([10, 20, 30, 40])
print(np.mean(arr))  # → 25.0
print(np.std(arr))   # → 11.18...

解説:

pandasにも.mean().std()はありますが、DataFrameの構造を意識する必要があります。単純な数値配列やベクトルの統計量を取るときは、NumPyの方が高速かつ直感的です。

統計演算を繰り返すような処理では、オーバーヘッドの少ないNumPyを基本とするのがベストプラクティスです。


✅ パターン⑧:累積計算はNumPyのcumsum()cumprod()で簡単に

x = np.array([1, 2, 3, 4])
print(np.cumsum(x))   # → [1 3 6 10]
print(np.cumprod(x))  # → [1 2 6 24]

解説:

累積和や累積積は、時間軸のデータや指数型増加などの計算でよく使われます。NumPyにはこれ専用の関数があり、ループを使わず1行で実現可能です。

pandasでも似た機能はありますが、配列ベースで作業する場面ではNumPyが明確に便利です。


✅ パターン⑨:線形代数処理はNumPyに任せる(内積・行列積など)

a = np.array([1, 2])
b = np.array([3, 4])

print(np.dot(a, b))       # 内積 → 11
print(np.matmul(a, b))    # 行列積(スカラーの場合は同じ)

解説:

線形代数処理(内積・行列積・逆行列固有値など)は、NumPyが圧倒的に強力です。ベクトル・行列の扱いを簡潔に記述できるうえ、BLASLAPACKといった数値計算ライブラリと連携しており、非常に高速に動作します


✅ パターン⑩:DataFrame演算は「構造が一致」していることが前提

import pandas as pd

df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
df2 = pd.DataFrame({'A': [10, 20], 'B': [30, 40]})

print(df1 + df2)

解説:

pandasのDataFrame同士で演算を行う場合、インデックスや列名が一致していないと期待した結果にならないことがあります。

一方、NumPyでは位置ベースで演算されるため、構造の一致に神経質になる必要はありません。つまり、「構造ありき」の処理はpandas、それ以外はNumPyという判断軸が役立ちます。


🧠 まとめ:こんなふうに使い分けよう!

処理内容 ベストなライブラリ 理由
表形式の前処理・入出力 pandas 読み書き・整形が得意
ベクトル・行列の数値演算 numpy 高速で記述も簡潔
スカラーの数式処理 math 軽量でインポートも早い

Python × Google Earth EngineでLST(地表面温度)を取得する方法【Landsat 8|GeoTIFF出力】

 

夏になりました。熱くなってきた時期ですが、今回はヒートアイランド現象を分析するための情報として重要な 地表面温度(LST) をあつかいます。

本記事では、PythonGoogle Earth Engine(GEE)を組み合わせて、Landsat 8の衛星データからLST画像を取得し、Google DriveにGeoTIFF形式で保存する方法を解説します。

 

Google Earth Engineのデータセットを使えば、大気補正済みのLSTを簡単に入手できます!

 

データセットのページより 

USGS Landsat 8 Level 2, Collection 2, Tier 1  |  Earth Engine Data Catalog  |  Google for Developers

""" 和訳

このデータセットには、ランドサット 8 号の OLI/TIRS センサーによって生成されたデータから導出された、大気補正済みの地表反射率と地表温度が含まれています

"""

 

すぐにLSTを入手できるように、コードについてもそのまま記載しております。
座標や日時などを変更することで誰でも簡単に LSTを入手可能です。

 

注意点:放射率などの補正を含まないので通常の地表面温度とは若干違うようです。


🌡 LSTとは?

**LST(Land Surface Temperature:地表面温度)**とは、衛星データを使って取得できる地表面の温度です。都市のヒートアイランド研究や環境モニタリング、農業管理など幅広く活用されています。


🛰 使用データ:Landsat 8とは?

Landsat 8は、NASAUSGSが運用するリモートセンシング衛星で、30m解像度のマルチスペクトルデータと熱赤外(Thermal Infrared)バンドを提供します。

LSTを取得するには熱赤外バンドが必要ですが、Landsat 8はBand 10(ST_B10)を通じて高精度の地表面温度推定が可能です。

✅ 他のLST取得可能な衛星との違い

  • MODIS:毎日取得できるが解像度が粗い(1km)

  • Sentinel-3:LST製品ありだが、解像度は1km

  • Landsat 8:16日周期だが30m解像度で都市スケールに最適


🧠 開発環境

 


🔧 Google Earth EngineとGoogle Driveの役割

  • Google Earth Engine(GEE)
    → 衛星データの取得、フィルタリング、前処理、解析などをクラウド上で高速に実行

  • Google Drive
    → 処理した画像(GeoTIFF形式など)を保存するクラウドストレージ。GEEとの連携がスムーズ。


📌 Google Earth Engine プロジェクトIDの取得方法

PythonからGoogle Earth Engineを利用する際、ee.Initialize(project='プロジェクトID')GCPGoogle Cloud Platform)プロジェクトIDの指定が必要です。

✅ プロジェクトIDの取得手順:

  1. Google Cloud Console にアクセスし、ログイン

  2. 「プロジェクトを作成」をクリックし、任意のプロジェクト名を入力して作成

  3. 作成後、画面右上の「プロジェクトの選択」でIDを確認可能(例:my-earthengine-project

  4. Earth Engine APIを有効化する(検索バーで "Earth Engine API" を検索 → 「有効にする」)


🧪 Pythonコードとポイント解説

以下は、2025年1月のハノイ地域のLandsat 8データから平均LST画像を生成し、Google DriveにエクスポートするPythonスクリプトです

 

from datetime import datetime
import ee

GGE_PROJECT = 'master-research-465403'  # Google Earth EngineプロジェクトID

### 
##  Parameters
###
# 取得するデータの期間
START_DATE = '2025-01-01'  # 開始日 例: '2025-01-01'
END_DATE = '2025-01-31'  # 終了日 例: '2025-01-31'


# 雲量の閾値(%) これ以下の画像を対象とする
CLOUD_COVER = 20  

# 範囲:  緯度1, 経度1, 緯度2, 経度2の順で指定
REGION = [105.75, 20.95, 106.00, 21.15]  

FILE_NAME_PREFIX = 'hanoi_lst8_202501'  # 出力ファイル名の接頭辞


FOLDER_NAME = 'EarthEngine'  # Google Drive上の保存先フォルダ
SCALE = 30  # 出力画像の解像度(メートル単位)
CRS = 'EPSG:4326'  # 出力画像の座標参照系(CRS) 基本的にはWGS84を使用
MAX_PIXELS = 1e9  # エクスポート可能な最大ピクセル数
###
##
###


# Google Earth Engine APIの初期化
ee.Initialize(project=GGE_PROJECT)

rect = ee.Geometry.Rectangle(REGION)

# Landsat 8 Collection 2, Level 2
dataset = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2') \
    .filterDate(START_DATE, END_DATE) \
    .filterBounds(rect) \
    .filter(ee.Filter.lt('CLOUD_COVER', CLOUD_COVER))  # 雲量20%未満

# 画像のマスク処理(雲、影、雪を除去)
def mask_clouds(img):
    qa = img.select('QA_PIXEL')
    cloud = qa.bitwiseAnd(1 << 3).eq(0)
    shadow = qa.bitwiseAnd(1 << 4).eq(0)
    snow = qa.bitwiseAnd(1 << 5).eq(0)
    mask = cloud.And(shadow).And(snow)
    return img.updateMask(mask)


# LSTバンド(ST_B10)を取得し、摂氏に変換
# 参考: https://www.usgs.gov/landsat-missions/landsat-collection-2-level-2-science-products
def calc_lst(img):
    lst = img.select(['ST_B10']).multiply(0.00341802).add(149.0).subtract(273.15)
    lst = lst.rename('LST_C')
    return lst.copyProperties(img, ['system:time_start', 'system:time_end'])


dataset_masked = dataset.map(mask_clouds)
lst_images = dataset_masked.map(calc_lst)
mean_lst = lst_images.mean()


# 期間内の画像の日付一覧を表示
dates = dataset.aggregate_array('system:time_start').getInfo()
for t in dates:
    dt = datetime.utcfromtimestamp(t / 1000)
    print(dt.strftime('%Y-%m-%d %H:%M:%S'))


# Earth EngineのタスクとしてGeoTIFFでエクスポート(Google Driveに保存)
task = ee.batch.Export.image.toDrive(
    image=mean_lst, # エクスポートする画像
    description='Hanoi_LST_Mesh', # タスクの説明。Earth Engineの「Tasks」タブで表示される名前
    folder=FOLDER_NAME, # Google Drive内の保存先フォルダ名
    fileNamePrefix=FILE_NAME_PREFIX, # 出力ファイル名の接頭辞
    region=rect, 
    scale=SCALE,   # 出力画像の解像度(メートル単位)
    crs=CRS,
    maxPixels=MAX_PIXELS 
)

task.start()
print("Export task started. Check your Google Drive's 'EarthEngine'フォルダ.")

💡 LSTの計算式について補足

LST(℃) = ST_B10 × 0.00341802 + 149.0 - 273.15

Landsat 8のST_B10バンドは**キャリブレーション済み輝度温度(Kelvin)**として格納されています。

# LSTの計算(Kelvin → 摂氏変換) def calc_lst(img): lst = img.select(['ST_B10']).multiply(0.00341802).add(149.0).subtract(273.15) lst = lst.rename('LST_C') return lst.copyProperties(img, ['system:time_start', 'system:time_end'])

この式は、USGS公式ドキュメントに基づいており、温度スケーリングとオフセットを考慮して摂氏温度に変換します。

参考:Landsat Collection 2 Level-2 Science Products | U.S. Geological Survey


☁️ 補足2:なぜ雲の処理が必要なのか?

Landsatの観測データには雲、影、雪が含まれることがあります。これらはLSTの計測に大きな誤差を与えるため、GEEのQA_PIXELバンドを使ってマスク処理を行います。

具体的には、ビット演算で雲・影・雪の判定フラグを抽出し、問題のあるピクセルを除去します。


✅ まとめ

  • Landsat 8のST_B10バンドを使って地表面温度(LST)を取得

  • 雲・影・雪の除去処理を施すことで信頼性を確保

  • GEEから処理 → Google DriveにGeoTIFFで保存可能

  • 都市ごとのLST解析に30m解像度の衛星データを活用可能


🔗 参考リンク


💡 本記事を参考に、他都市や長期データ、季節変化などを分析してみるのもおすすめです。


ご質問やフィードバックはお気軽にコメント欄へどうぞ!😊


YouTubeAPIで、再生リストからデータを一覧取得してみた  FastAPI × React アプリ開発【レシピ提案アプリ⑥】

こんにちは、今回のテーマは YouTubeの再生リストからレシピ動画を一括取得」 です!

前回の記事 ↓ では、Djangoテンプレートでレシピを一覧表示する機能を実装しましたが、今回は レシピを一括追加するための自動化ツール を作っていきます。
今回から使用するフレームワークや言語が変わります。

フロントエンド HTML/CSS  &  JavaScript → React
バックエンド Django → FastAPI 

 

siip.hateblo.jp


🎯 今回やること

以下のような処理を実装して、YouTubeの再生リストからレシピ情報(タイトル・説明・URL・サムネイル)をまとめて取得できるようにします。


📌 実装の目的

  • レシピを1件ずつ手動で入力するのは大変…

  • 自分はYouTube再生リストに料理動画をためてる

  • 再生リストURLを入れるだけで自動登録できたら最高!

 

実装後はこんな感じで、再生リストの動画すべてを一括で追加できました




🛠️FastAPI× Reactでの 実装の流れ(全体像)

  1. フロントエンドに再生リストURLの入力フォームを用意

  2. **バックエンド(FastAPI)**がYouTube Data APIを使って情報を取得

  3. 動画情報をフロントエンドに返却

  4. 必要に応じて編集・一括登録ができるUIを用意


🔑 YouTube Data API v3の準備

まずは、APIを使えるように Google Cloud Consoleで設定します。

APIキーの取得手順

  1. Google Cloud Console にアクセス

  2. 左サイドバー「APIとサービス」 →「ライブラリ」→「YouTube Data API v3」を選択して有効化

  3. 「認証情報」→「認証情報を作成」→「APIキー」を発行

  4. 発行されたキーに制限をかける(重要)


🔐 APIキーのセキュリティ設定

  • .envで管理し、GitHubなどに絶対公開しない

  • API制限を設定(「YouTube Data API v3」のみに許可)

  • IPアドレス制限(FastAPIなどサーバー用途なら)をかける

 


🧪 バックエンドの実装(FastAPI)

APIキーを .env に書いて、FastAPIから読み込みます。

✅ .env の例

YOUTUBE_API_KEY=ここに取得したキーを貼る

✅ FastAPI側のコード

# main.py
import os, requests, re
from fastapi import FastAPI, Body
from dotenv import load_dotenv

load_dotenv()
YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY")

app = FastAPI()

@app.post("/api/youtube/playlist")
def fetch_youtube_playlist(playlist_url: str = Body(..., embed=True)):
    match = re.search(r"[?&]list=([\w-]+)", playlist_url)
    if not match:
        return {"error": "Invalid playlist URL"}

    playlist_id = match.group(1)
    items = []
    nextPageToken = None

    while True:
        params = {
            "part": "snippet",
            "playlistId": playlist_id,
            "maxResults": 50,
            "key": YOUTUBE_API_KEY,
        }
        if nextPageToken:
            params["pageToken"] = nextPageToken

        r = requests.get("https://www.googleapis.com/youtube/v3/playlistItems", params=params)
        data = r.json()
        items.extend(data.get("items", []))
        nextPageToken = data.get("nextPageToken")
        if not nextPageToken:
            break

    result = []
    for item in items:
        snippet = item["snippet"]
        result.append({
            "videoId": snippet["resourceId"]["videoId"],
            "title": snippet["title"],
            "description": snippet.get("description", ""),
            "thumbnail": snippet["thumbnails"]["default"]["url"],
            "url": f"https://www.youtube.com/watch?v={snippet['resourceId']['videoId']}"
        })
    return result

💻 フロントエンドの実装(React)

API呼び出し関数(api.ts)

export async function fetchYoutubePlaylist(playlistUrl: string): Promise<any[]> {
  const res = await fetch("/api/youtube/playlist", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ playlist_url: playlistUrl }),
  });
  return await res.json();
}

プレイリスト追加画面(PlaylistBulkAdd.tsx

import { useState } from "react";
import { fetchYoutubePlaylist } from "./api";

function PlaylistBulkAdd() {
  const [playlistUrl, setPlaylistUrl] = useState("");
  const [videos, setVideos] = useState<any[]>([]);
  const [error, setError] = useState("");

  const fetchPlaylist = async () => {
    try {
      const data = await fetchYoutubePlaylist(playlistUrl);
      setVideos(data);
    } catch {
      setError("取得に失敗しました");
    }
  };

  return (
    <div className="container mt-5">
      <h2>YouTube再生リスト一括追加</h2>
      <input
        type="text"
        className="form-control mb-3"
        placeholder="再生リストのURLを入力"
        value={playlistUrl}
        onChange={(e) => setPlaylistUrl(e.target.value)}
      />
      <button onClick={fetchPlaylist} className="btn btn-primary">取得</button>
      {error && <p className="text-danger">{error}</p>}
      <div className="mt-4">
        {videos.map((v, i) => (
          <div key={i} className="card mb-3">
            <div className="row g-0">
              <div className="col-md-2">
                <img src={v.thumbnail} className="img-fluid" alt="thumbnail" />
              </div>
              <div className="col-md-10">
                <div className="card-body">
                  <h5 className="card-title">{v.title}</h5>
                  <p className="card-text">{v.description}</p>
                  <a href={v.url} target="_blank" className="btn btn-sm btn-outline-secondary">YouTubeで見る</a>
                </div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

export default PlaylistBulkAdd;

 


⚠️ 注意点とポイントまとめ

項目 内容
APIキーの管理 .env + .gitignoreで非公開にする
APIの制限 API/IP制限を必ず設定
再生リストの種類 「非公開」「限定公開」は取得できません
クォータ消費 利用頻度に注意。開発中は取得数を制限する
フォールトトレランス 失敗時のエラーハンドリング必須

✅ まとめ

YouTubeの再生リストからレシピを一括取得する機能を実装しました!

  • 一から入力する手間が大幅に削減され、かなり時短になります。

  • APIを使う際は、セキュリティ設定キーの非公開管理を徹底しましょう。

 

レシピ提案アプリ開発記事一覧はこちらから ↓

siip.hateblo.jp