2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

気象データをOpenSiv3Dで描写する

Last updated at Posted at 2025-12-24

この記事は Siv3D Advent Calendar 2025 19日目です。

Happy Holiday! こんにちは。natsukodachiです。遅刻してごめんなさい。

はじめに

今回はnetCDF形式の気象データをOpenSiv3Dで描写していこうと思います。
気象データの可視化は、ほとんどの場合Gradsかpythonが用いられます。しかし、Fortranなどのコンパイル言語に慣れている気象関係者にとって、syntaxがきかないGradsスクリプトや全く記法が異なるpythonは中々とっつきにくいのではないでしょうか。
そこで、速い・書きやすい・かっこいいの三拍子が揃ったC++で作図します。C++さいこー!

環境・使用ツール

  • OpenSiv3D v0.6
  • netcdf-cxx4:x64-windows-static (静的CRT版)
    • vcpkgからインストールする。詳しくは別記事を参照ください
  • Visual Studio 2026
  • (ncdump)
    • データの確認用

Debugビルド時、Siv3Dは/MTd(静的ビルド)、通常のnetcdfは/MDd(動的ビルド)でビルドされ、これらは両立しない。そのため、netcdf側で静的ビルド版を取ってくる必要がある。

また、VcpkgTripletを明示する必要がある。

  • プロジェクトのプロパティ>vcpkg>Use Static Libraries>はい
  • プロジェクトのプロパティ>vcpkg>Triplet→x64-windows-static

image.png

環境によっては、次のビルドエラーが発生することがあります。

libucrt.lib(stat.obj) : error LNK2005: _fstat64 は既に netcdf.lib(zmap_file.c.obj) で定義されています。

この場合、応急処置として、プロジェクトのプロパティ>リンカー>全般>ファイルを強制的に出力>/Force:Multipleを選択するとビルドが通るようになります。

データ取得・読みこみ

欧州中期予報センター(ECMWF)の再解析データERA5を使用する。Climate Data Store (CDS)からいい感じのデータをとってくる。今回は台風日の気圧を見たいので、台風15号が接近した2025/9/5 UTC 3:00のPMSLを選んだ。

データ読み込みはnetcdf-cxxで行うので(アドカレの主旨とずれるので)詳しい説明は省略する。
緯度、経度、地点毎のデータを格納するArray、Gridを作成し、代入するようにする。

#include <Siv3D.hpp>
#include <netcdf>

using namespace netCDF;

struct weatherData
{
	Grid<double> msl;
	Array<double> lats;
	Array<double> lons;
};

void LoadZ500(weatherData& field, const FilePath& ncPath)
{
	// NetCDF ファイルを開く
	NcFile nc(Unicode::Narrow(ncPath), NcFile::read);
    //緯度、経度、pmslのデータと要素数を取得する
    //配列をresizeする
    //代入する
}

カラーマップを作成する

Colormap01()でカラーマップを作成することができる。第二引数で使用するカラーマップの種類が選べる。

Image CreateColormapImage(const Grid<double>& msl, double vmin, double vmax, ColormapType cmapType)
{
	// 値の範囲を 0.0-1.0 に正規化
	const double invRange = 1.0 / (vmax - vmin);
	const int32 w = static_cast<int32>(msl.width());
	const int32 h = static_cast<int32>(msl.height());

	Image img(w, h);
	for (int32 y = 0; y < h; ++y)
	{
		for (int32 x = 0; x < w; ++x)
		{
			double t = (msl[y][x] - vmin) * invRange;
			t = Clamp(t, 0.0, 1.0);
			img[y][x] = Colormap01F(t, cmapType);
		}
	}
	return img;
}

海岸線を描写する

公式サイトにgeojsonを使った世界地図のサンプルがあるので、これを参考にして日本域を描く。

class CoastlineOverlay
{
public:
	// 可視範囲を受け取り、範囲に入る国だけ抽出
	CoastlineOverlay(const RectF& geoViewRectLonY)
	{
		
		countries_ = GeoJSONFeatureCollection{ JSON::Load(U"example/geojson/countries.geojson") }
			.getFeatures()
			.map([](const GeoJSONFeature& f) { return f.getGeometry().getPolygons(); });

		// 可視範囲内の国だけ抽出
		visibleIndices_.reserve(countries_.size());
		for (auto&& [i, country] : Indexed(countries_))
		{
			if (country.computeBoundingRect().intersects(geoViewRectLonY))
			{
				visibleIndices_ << i;
			}
		}
	}

	// テクスチャの描画領域(destRect)に合わせて海岸線を重ね描き
	void draw(const RectF& destRect, double lonMin, double lonMax, double yMin, double yMax) const
	{
		// グリッドの 1 ピクセルサイズ
		const Vec2 pixelSize = destRect.size / Vec2{ 1.0 * m_w, 1.0 * m_h };
		const Vec2 halfPixel = pixelSize * 0.5;

		// 経度・緯度(y は-latitude)→ピクセル座標へのスケール
		const double sx = (destRect.w - pixelSize.x) / (lonMax - lonMin); // (w-1) 相当
		const double sy = (destRect.h - pixelSize.y) / (yMax - yMin);     // (h-1) 相当

		// 左上原点合わせ(半ピクセル補正込み)
		const Vec2 translate = (destRect.pos + halfPixel) - Vec2{ lonMin * sx, yMin * sy };

		// 座標変換の適用
		const Transformer2D t{ Mat3x2::Scale(Vec2{ sx, sy }).translated(translate) };

		// 画面拡大率に応じた線幅(スケール非依存)
		const double lineThickness = (1.0 / Graphics2D::GetMaxScaling());

		// 可視国のみ描画
		for (const size_t i : visibleIndices_)
		{
			countries_[i].drawFrame(lineThickness, ColorF{ 0.1, 0.1, 0.1, 0.85 });
		}
	}

	// グリッドサイズ(ピクセル数)を設定
	void setGridSize(int32 w, int32 h)
	{
		m_w = w;
		m_h = h;
	}

private:
	Array<MultiPolygon> countries_;   // 国境線ポリゴン
	Array<size_t> visibleIndices_;    // 可視範囲に入る国のインデックス
	int32 m_w = 1;                    // グリッド幅
	int32 m_h = 1;                    // グリッド高さ
};

結果

こういう図が出来上がります。
image.png

コードはこちらです。

近畿中部の気圧が低いことが分かる。

おわり

思ってたより大変だったが、なんとか描けてよかった。
風の矢羽根を描いたり、圧力の等高線を描いたりとかもやりたい。

ちなみに、netCDFはバイナリなので頑張ればSiv3Dオンリーで完結することができる。が、それはまたの機会に。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?