Pythonを使用して数値標高モデル(DEM)からMinecraftの地形を作成する

2023-07-16_12.51.38.png

首先

この記事では、Pythonのライブラリであるanvil-parserを使用して、数値標高モデル(DEM)からMinecraft(Java版)のワールドデータを作成する方法を紹介します。この方法により、実世界の地形をMinecraftで再現することが可能になります。

下载DEM数据。

首先,请从基础地图信息下载网站下载数字高程模型(DEM)的数据。

image.png

ダウンロードしたDEMはそのままでは利用できないので、QGISのQuickDEM4JPプラグインを使用してtiffファイルに変換します。

詳しい使い方はこちらの記事を参照してください。
「国土地理院の標高データ(DEM)をQGIS上でサクッとGeoTIFFを作って可視化するプラグインを公開しました!(Terrain RGBもあるよ)」

image.png
image.png
image.png

将它转换为我的世界 (Translate it to Minecraft world)

由于数据已经准备好了,接下来我们将使用Python将其转换为Wycla的世界。

1. 增加DEM的解像度

为了与Minecraft世界的设定相符,我们将使用gdal将DEM栅格分辨率转换为1米 x 1米。由于我们使用的DEM是基于日本地理坐标系,因此1个像素等于1米。

安装

pip install gdal

编码

from osgeo import gdal

# 入力ファイルを開く
tiff = gdal.Open("553616_dem.tiff", gdal.GA_ReadOnly)

# 変換パラメーターを設定
dst_size_x = 1
dst_size_y = 1

# 立方体補間(Cubic Convolution) で解像度を変更する
warp_options = gdal.WarpOptions(xRes=dst_size_x, yRes=dst_size_y, resampleAlg=gdal.GRIORA_Cubic)

# ラスターの変換を実行
dst = gdal.Warp("553616_dem_1m.tiff", tiff, options=warp_options)

# ファイルをクローズ
src = None
dst = None

将DEM数据转换为Minecraft的世界座标系

接下来,我们将把这个DEM的地理坐标系的三维表示调整到Minecraft的世界坐标系的三维表示。

地理坐标系的三维表示分别为X、Y、Z,具体如下所示。

X坐标:表示东西方向的位置。数值随着向东增加而增加,随着向西减少而减少。
Y坐标:表示南北方向的位置。数值随着向北增加而增加,随着向南减少而减少。
Z坐标(如适用):表示高度(海拔)。数值随着向上增加而增加,随着向下减少而减少。

一种方式是,Minecraft的X、Y、Z的三维表示如下。

X座標: 位置東西。數值隨著向東增加而遞增,向西減少而遞減。
Y座標: 高度(海拔)。數值隨著向地上移動增加,向地下移動減少。
Z座標: 位置南北。數值隨著向南增加而遞增,向北減少而遞減。

image.png

另外,由于Minecraft的中心坐标(区块坐标[X,Z])是[0,0],因此需要将该位置设置为本次DEM的中心坐标(尽管保持原坐标位置没有问题,但可能会远离初始重生点)。

考虑到这些,

    • Y座標の情報とZ座標の情報を入れ替える。

 

    • X軸を反転させる。

 

    中心の座標が[0,0]になるように全体をずらす。

需要进行这样的处理。

以下的代码旨在将DEM(数字高程模型)的地理空间数据转换为Minecraft的世界坐标系,并将其与每个像素的中心坐标一起保存在数据框中。

安装

pip install rasterio
pip install numpy
pip install pandas

代码 cì)

# ライブラリのインポート
import rasterio
import numpy as np
import pandas as pd

# 変換したラスターファイルを開く
with rasterio.open("553616_dem_1m.tiff") as src:

    # 幅と高さを取得
    width, height = src.width, src.height

    # 中心点のオフセットを計算
    # 偶数の場合は0.5、奇数の場合は0
    offset_x = 0.5 if width % 2 == 0 else 0
    offset_y = -0.5 if height % 2 == 0 else 0

    # トランスフォーメーション行列から中心座標を計算
    transform = src.transform
    center_x = transform.c + width / 2.0 * transform.a + offset_x
    center_y = transform.f + height / 2.0 * transform.e + offset_y

    # 数値標高データを取得
    data = src.read(1)

    # 各ピクセルの中心座標を取得
    y_indices, x_indices = np.indices(data.shape)
    x_coords = x_indices * transform.a + transform.c + transform.a / 2.0
    y_coords = y_indices * transform.e + transform.f + transform.e / 2.0

    # ピクセル座標と標高値を一次元化
    x_coords = x_coords.ravel()
    y_coords = y_coords.ravel()
    data = data.ravel()

    # マイクラの基準にする中心の座標を引く
    x_coords = x_coords - center_x
    y_coords = y_coords - center_y

    # y軸(北南)を反転。マイクラ座標に合わせるため
    y_coords = y_coords * -1

    # 小数点以下を排除
    x_coords = np.trunc(x_coords).astype(int)
    y_coords = np.trunc(y_coords).astype(int)
    data = np.trunc(data).astype(int)

    # マイクラのブロック座標からregion(.mca)のファイル名を取得
    region_x = (x_coords // 512).astype(int)
    region_z = (y_coords // 512).astype(int)

    # 文字列に変換
    region_x_str = np.char.mod("%d", region_x)
    region_z_str = np.char.mod("%d", region_z)

    # ベクトル化した文字列フォーマット操作を適用
    region = np.vectorize("r.{}.{}.mca".format)(region_x, region_z)

    # データフレームを作成
    df = pd.DataFrame({
        "x": x_coords,
        "z": y_coords,
        "y": data,
        "region": region
    })

首先,打开转换后的栅格文件,并获取其宽度和高度。利用这些信息,计算栅格图像的中心坐标。计算中心坐标时需要设置特定的偏移值。这个偏移值根据栅格的宽度和高度是偶数还是奇数而有所不同。

具体的には、画像の幅と高さが偶数の場合、中心座標がピクセルの角に位置してしまいます。これを避けるため、中心座標を0.5ずつずらすオフセットを設定します。

image.png

このオフセットを用いて、ラスターのトランスフォーメーション(変換行列)を使用して中心座標を計算します。
今回使用したDEMの中心座標は[-32150.744183319344 -60035.32409617323]です。
この計算された中心座標は、マイクラのブロック座標系(x, z)の[0,0]点に対応します。この操作により、マイクラのブロック座標が地理座標系のどの座標点に対応するかが決定されます。

次にラスターの各ピクセルの中心座標を計算します。これらの座標は、後の計算のために一次元の配列に変換(平坦化)されます。
そして、各座標をMinecraftの基準[0,0]に合わせるために、中心座標を引きます。北と南の座標(y軸)は、Minecraftの座標系に合わせるために−1を乗算して反転します。その後、座標と標高データから小数点以下を切り捨て、整数型に変換します。

次に、Minecraftのワールド座標からregion(.mca)のファイル名を取得します。ここで注目すべきは、Minecraftのワールドは、512×512ブロックの領域、いわゆるregionに区切られているという点です。このregionは、それぞれが個別の.mcaファイルとして保存されています。

 

因此,将每个坐标除以512,即可获取对应区域的编号。该值将被转换为整数并转换为字符串。然后,应用格式字符串操作,将每个坐标转换为相应的区域文件名。

image.png

最后,将所有这些数据(坐标、海拔、区域文件名)保存到 Pandas 的数据框中。这个数据框将用于创建 Minecraft 世界数据和其他后处理。

xzyregion0-5688-4643-9999r.-12.-10.mca1-5687-4643-9999r.-12.-10.mca2-5686-4643-9999r.-12.-10.mca3-5685-4643-9999r.-12.-10.mca4-5684-4643-9999r.-12.-10.mca……………10565819456844643-9999r.11.9.mca10565819556854643-9999r.11.9.mca10565819656864643-9999r.11.9.mca10565819756874643-9999r.11.9.mca10565819856884643-9999r.11.9.mca

由于只显示了初始和最后的表格,所以充满了缺失值。

3.ブロックを設置する

作成したPandasデータフレームを使用して、Minecraftのワールドにブロックを配置します。

关于anvil-parser

anvil-parserはPythonで書かれたライブラリで、MinecraftのAnvilファイルフォーマットの読み書きを可能にします。Anvilファイルフォーマットは、Minecraftのワールドデータを表現するために使用されています。
anvil-parserをつかえば、プレイヤーがワールドに入らなくても、Pythonで直接Minecraftのワールドデータを操作し、自分自身でカスタマイズしたワールドを生成するためタスクを簡単に行うことができます。

 

然而,anvil-parser只能安装到256个方块的高度(截至2023年7月)。
从2021年的Minecraft v1.18开始,可以将方块堆叠到384个方块的高度。我想要尽可能地堆叠方块到最大高度,所以选择了一个分支版本,据说可以在384个方块的高度上进行安装。所以这次我安装了这个分支版本并使用了它。

 

安装

pip install git+https://github.com/WoutCherlet/anvil-parser.git

然而,如果直接使用這個,會意外地在一些地方設置了阻擋器,所以我將這段程式碼從32更改為16,這樣它就可以正常運作了(我不確定這個修正是否正確)。

代码 (daima)

# ライブラリのインポート
import anvil
import random

# -9999のデータを削除
df = df.loc[df["y"] != -9999]

# 最小値を取得
min_value = df["y"].min()

# regionごとにグループ分け
grouped = df.groupby(["region"])

# 3種類のブロックを定義
grass = anvil.Block("minecraft", "grass_block")
dirt = anvil.Block("minecraft", "dirt")
stone = anvil.Block("minecraft", "stone")

# 3種類の草を定義
grass_plant = anvil.Block("minecraft", "grass")
tall_grass_l = anvil.Block("minecraft", "tall_grass", properties={"half": "lower"})
tall_grass_u = anvil.Block("minecraft", "tall_grass", properties={"half": "upper"})

# ブロックを設置する処理
def set_blocks(region, x, y, z):

    # 319以下の場合にブロック設置を開始
    if y <= 319:
        # 設定するブロックのリスト(草ブロック1、土ブロック1、石ブロック3のレイヤーをつくる)
        blocks = [grass, dirt, stone, stone, stone]

        # 5%の確率で草を生やす
        if random.random() > 0.95 and y < 319:
            region.set_block(random.choice([grass_plant, tall_grass_l, tall_grass_u]), x, y + 1, z)

        # ブロックのレイヤーを設置する。ブロック設置ができない範囲はbreakで抜ける
        for i, block in enumerate(blocks):
            if -64 <= y - i <= 319:
                region.set_block(block, x, y - i, z)
            else:
                break

for _name, group in grouped:
    # 先頭行をスキップ
    group = group.iloc[1:]

    # regionを作成
    region = anvil.EmptyRegion(0, 0)
    x = group["x"] % 512
    y = group["y"] - min_value - 64
    z = group["z"] % 512

    for xi, yi, zi in zip(x, y, z):
        set_blocks(region, xi, yi, zi)

    # regionを保存
    region.save(group.iloc[1]["region"])

まず、不要なデータを削除します。この場合、データの中で標高値が-9999(欠損値)となっているものを除外します。次に、標高の最小値を取得し、regionに基づいてデータをグループ化します。

次に、Minecraftで使用するいくつかのブロックを定義します。ここでは、「草ブロック」、「土ブロック」、「石ブロック」の3種類のブロックと、3種類の草を定義しています。

次に、関数 set_blocks を定義します。この関数は、指定されたx, y, z座標にブロックを配置します。特定の標高以下の場合(ここでは319以下)、ブロックを配置するように指定されています。ブロックのレイヤーを設置する際、マイクラで配置可能な高さの範囲外(-64 ~ 319)の場合は処理を中断します。また、より自然な地形にしたかったので、5%の確率で、地面に草を生やすようにしています。

その後、データフレームの各regionごとに、指定された座標にブロックを配置します。これは、各regionに対応するグループのデータを順に処理することで実現しています。xとz座標は、Minecraftのregionの大きさ(512)でモジュロ演算を行うことで0から511の範囲に正規化されます。これは、設置する時の値がワールドの絶対座標ではなく、各regionの相対座標でないとブロックの設置処理ができないからです。y座標は、全体の最小値とMinecraftの最小高度(-64)を基準に調整されます。

最后,将布置完成的区域保存为.mca文件。这样就可以将其作为Minecraft的世界数据进行读取。

把创建的.mca文件放进Minecraft存档的region文件夹中就可以了。

实际形成的地貌

2023-07-16_14.48.49.png
2023-07-17_02.34.48.png
2023-07-16_13.02.21.png
2023-07-16_13.27.25.png
2023-07-16_12.42.58.png
2023-07-16_12.58.22.png
2023-07-17_02.30.06.png
2023-07-17_02.23.16.png

我会使用Minecraft地图查看器软件来确认创建的地形。

 

image.png

几乎和阴影图没有什么区别。

额外的
(é de)

uNmineDは表示したワールドマップを画像としてエクスポートできます。

image.png

エクスポートしたマップ画像をgdalのシェルコマンドでtiffに変換します。EPSG:6677で四隅の座標は、先ほどマイクラの起点とした地理座標[-32150.744183319344 -60035.32409617323]から480ファイル分のregionグリッドに分けた時の四隅の座標を指定します。

gdal_translate -a_srs "EPSG:6675" -a_ullr -38294.9648437500000000 -54915.5390625000000000 -26006.9628906250000000 -65155.5468750000000000 minecraft_dem.png minecraft_dem.tif

而且,只要在QGIS中进行拖放操作,你创建的Minecraft世界地图就能完美地叠加在地图上。

image.png
广告
将在 10 秒后关闭
bannerAds