パイクラおじさんの日記

MinecraftでPythonを勉強するおじさんの日記です。

土地利用3次メッシュ

国土交通省のGISのサイト「土地利用3次メッシュ」なるものがあったので、最新版をダウンロードしてマップとして表示してみた。

1kmメッシュと書いてあるので、かなり大きな範囲での情報なので使えない。

f:id:pycra:20171008085840p:plain

80x80のサイズ。

ソースコードはコレ。 ファイル名は埋め込み。

# coding: utf-8

import xml.etree.ElementTree as ET
import numpy as np
import matplotlib.pyplot as plot
import sys


JPGIS_FILE = "JPGIS/5340/L03-a-14_5340-jgd_GML/L03-a-14_5340.xml"

tree = ET.parse(JPGIS_FILE)
root = tree.getroot()

for e in root.getiterator():
    print(e.tag)

tl = root.find('./{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}LanduseMesh/{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}coverage/{http://www.opengis.net/gml/3.2}rangeSet/{http://www.opengis.net/gml/3.2}DataBlock/{http://www.opengis.net/gml/3.2}tupleList')
if tl is None:
    raise Exception("{http://www.opengis.net/gml/3.2}tupleList is not found")

temp = np.array([[107, 66, 0], [221, 148, 0], [71, 104, 33], [255, 208, 138],
    [196, 44, 0], [162, 162, 162], [116, 116, 116], [48, 48, 48],
    [132, 183, 255], [72, 157, 255], [0, 86, 185], [163, 215, 113], [255, 255, 255]], dtype=float)
temp = temp / 255.0
lines = tl.text.split()
array = np.zeros((6400, 3))
i = 0
for l in lines:
    d = np.array(l.split(","), np.float)
    s = sum(d)
    d = d / s
    total = np.zeros((3), dtype=float)
    for j in range(13):
        t = d[j] * temp[j]
        total = total + t
    array[i] = total
    i += 1

print(array)
array = array.reshape((80, 80, 3))
print(array)

plot.imshow(array, interpolation='nearest')
plot.show()

色分けは次の通り。

f:id:pycra:20171008091050p:plain

解析範囲外は白。

参考にしたサイト

NumPy 配列の基礎 — 機械学習の Python との出会い

木を生やす(5)

「木を生やす(1)」で書いた「木を生やす」意味の1.の方「木を生やす場所を決める」を考える。

木を生やす場所を決める方法として、1つには地形から判断する方法がある。次の2つの画像はだいたい同じ範囲の標高データとGoogleマップ衛星写真だけど、だいたい傾斜のキツいところに木が生えている。

f:id:pycra:20170920060300p:plain

f:id:pycra:20171008054846p:plain

ただ、この方法だと平野部の公園とか防風林、防砂林などは判断できない。

他に土地の利用区分みたいなデータがあれば、それで森林とか雑木林とか分かればと思う。

それに道路や鉄道などもそういうデータがありそうだし。

木を生やす(4)

wood.pyをモジュール化してpycraパッケージに追加したい。

そして、他のスクリプトから使えるようにしたい。

pycra/wood.pyとして保存。

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import math
import random
import sys

def leaves(mc, x, y, z, radius, density):
    min_x = int(x - radius)
    max_x = int(x + radius)
    min_y = int(y - radius)
    max_y = int(y + radius)
    min_z = int(z - radius)
    max_z = int(z + radius)
    for dy in range(min_y, max_y+1):
        for dx in range(min_x, max_x+1):
            for dz in range(min_z, max_z+1):
                xx =  dx - x
                yy =  dy - y
                zz =  dz - z
                s = xx * xx + yy * yy + zz * zz
                sq = math.sqrt(s)
                if sq <= radius:
                    block_id = mc.getBlock(dx, dy, dz)
                    if block_id == block.AIR:
                        if sq < radius - 0.5:
                            if random.random() < density:
                                mc.setBlock(dx, dy, dz, block.LEAVES)
                        else:
                            mc.setBlock(dx, dy, dz, block.LEAVES)

def wood(mc, x, y, z):
    height = 4 + int(random.random() * 5)
    mc.setBlocks(x, y, z, x, y + height, z, block.WOOD)
    offset = height * 0.25
    leaves(mc, x, y + height - offset, z, height * 0.5, 0.5)

if __name__ == '__main__':
    # ここからスタート
    mc = minecraft.Minecraft()

    if len(sys.argv) < 4:
        sys.exit("Usage: wood x y z")

    x = float(sys.argv[1])
    y = float(sys.argv[2])
    z = float(sys.argv[3])

    wood(mc, x, y, z)

Pythonメモ

よく見るif __name__ == '__main__':という形式を試してみた。

programming-study.com

呼び出す側

本を読み返してたら、剣で右クリックしたところを検出するスクリプトがあったので、それを参考にして剣で右クリックしたところに木を生やすスクリプトを書いた。

mkwood.pyとして書いた。

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import pycra.wood as wd

mc = minecraft.Minecraft()
mc.events.clearAll()

while True:
    blockHits = mc.events.pollBlockHits()
    if blockHits:
        blockHit = blockHits[0]
        if blockHit.face == 1:
            x = blockHit.pos.x
            y = blockHit.pos.y
            z = blockHit.pos.z
            blockId = mc.getBlock(x, y, z)
            while blockId != block.AIR:
                y = y + 1
                blockId = mc.getBlock(x, y, z)
            wd.wood(mc, x, y, z)

一回叩いただけでも複数のイベントが発生するので、if blockHit.face == 1:というので一回だけ処理するようにした。

if blockHit.face == 1:は(多分)ブロックの上面を叩いたという判定。

実行結果

以前、読み込んだ地形に木を生やしてみた。

f:id:pycra:20171008040319p:plain

結構、離して植えたつもりが案外こんもりしちゃう。

木を生やす(3)

コマンド1つで幹も生やして葉っぱも生成したい。

leaves.pyを関数にして、木(幹)の位置と高さを指定して、それに合わせて葉っぱを生成する。

wood.pyとして、こんな感じに実装。

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import math
import random
import sys

def leaves(mc, x, y, z, radius, density):
    min_x = int(x - radius)
    max_x = int(x + radius)
    min_y = int(y - radius)
    max_y = int(y + radius)
    min_z = int(z - radius)
    max_z = int(z + radius)
    for dy in range(min_y, max_y+1):
        for dx in range(min_x, max_x+1):
            for dz in range(min_z, max_z+1):
                xx =  dx - x
                yy =  dy - y
                zz =  dz - z
                s = xx * xx + yy * yy + zz * zz
                sq = math.sqrt(s)
                if sq <= radius:
                    block_id = mc.getBlock(dx, dy, dz)
                    if block_id == block.AIR:
                        if sq < radius - 0.5:
                            if random.random() < density:
                                mc.setBlock(dx, dy, dz, block.LEAVES)
                        else:
                            mc.setBlock(dx, dy, dz, block.LEAVES)

def wood(mc, x, y, z):
    height = 4 + int(random.random() * 5)
    mc.setBlocks(x, y, z, x, y + height, z, block.WOOD)
    offset = height * 0.25
    leaves(mc, x, y + height - offset, z, height * 0.5, 0.5)

# ここからスタート

mc = minecraft.Minecraft()

if len(sys.argv) < 4:
    sys.exit("Usage: wood x y z")

x = float(sys.argv[1])
y = float(sys.argv[2])
z = float(sys.argv[3])

wood(mc, x, y, z)

こんな感じに4本生成してみた。

/python wood 0 0 0
/python wood 5 0 0
/python wood 5 0 5
/python wood 0 0 5

配置が単純すぎた。

f:id:pycra:20171008020409p:plain

木を生やす(2)

leaves.pyを外はそのままで内部を間引くようにしてみた。

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import math
import random
import sys

# ここからスタート

mc = minecraft.Minecraft()

if len(sys.argv) < 6:
    sys.exit("Usage: leaves x y z radius density")

x = float(sys.argv[1])
y = float(sys.argv[2])
z = float(sys.argv[3])
r = float(sys.argv[4])
d = float(sys.argv[5])

min_x = int(x - r)
max_x = int(x + r)
min_y = int(y - r)
max_y = int(y + r)
min_z = int(z - r)
max_z = int(z + r)

for dy in range(min_y, max_y+1):
    for dx in range(min_x, max_x+1):
        for dz in range(min_z, max_z+1):
            xx =  dx - x
            yy =  dy - y
            zz =  dz - z
            s = xx * xx + yy * yy + zz * zz
            sq = math.sqrt(s)
            if sq <= r:
                block_id = mc.getBlock(dx, dy, dz)
                if block_id == block.AIR:
                    if sq < r - 0.5:
                        if random.random() < d:
                            mc.setBlock(dx, dy, dz, block.LEAVES)
                    else:
                        mc.setBlock(dx, dy, dz, block.LEAVES)

いい気になって、こんなパラメータで実行してみた。

/python leaves 0 100 0 90 0.3

こんなにデカいのね…

f:id:pycra:20171007105840p:plain

ブロックが葉で透過処理が入るからか、近寄るとCPUファンがガンガン回る。

f:id:pycra:20171007230349p:plain

で、当然、葉っぱだけなので枯れて崩壊する。

木を生やす(1)

木を生やしたい。

木を生やすには、次の2つの項目がある。

  1. 木を生やす場所を決める。
  2. 生やす木の形を決めて、実際にブロックを設置する。

木を生やす場所を決めるのは、またの機会にして、今回は木の形を決めて実際にブロックを設置する方を考える。

適切な高さ

適切な高さを見るために、1〜4個の木のブロックを積み上げてみた。

f:id:pycra:20171007034310p:plainf:id:pycra:20171007034319p:plainf:id:pycra:20171007034331p:plainf:id:pycra:20171007034338p:plain

4個くらいの高さが無いと「木」として感じられない。

そのまま11個重ねる(/python setblocks.py 0 0 0 0 10 0 17)と、1個のブロックの太さとの木としてはこれくらいが限界かな?

f:id:pycra:20171007034429p:plain

斧が届くのはここ(Y=6のブロック)まで。だいたいゲーム内で出てくるのも8くらいまでだと感じる。しかも、そこまで高い木は太さも4あるし。 f:id:pycra:20171007034436p:plain

結論

だいたい4〜8くらいが妥当。

葉の形

多分、木の高さに合わせて、葉の形も決まってくる。

まずは高さ4の木

幹4に葉3x3x3を中心3のところに配置してみた。 f:id:pycra:20171007041444p:plain

ちょっと葉がボリューム不足?

葉3x4x3にしてみた。 f:id:pycra:20171007042032p:plain

ちょっと面長?

葉の真ん中に5x2x5を追加。 f:id:pycra:20171007042210p:plain こんもりし過ぎ。

角を削ってみる。 f:id:pycra:20171007042322p:plain まぁ、ありそうだけど、もう1つ高さが欲しくなる。

あと、葉がみっちり詰まっている感じがするので、setblocksで隙間が空く(0.7くらい?)バージョンが欲しい。

バランス的には最初のが一番いいか。

高さ6の木

幹6葉5x5x5の木 f:id:pycra:20171007043444p:plain

右上の角が欠けているのは、勝手に欠け始めた。 隙間が空くバージョン+球形に設置できるといいなぁ。

leaves.pyとして書いてみた。

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import math
import random
import sys

# ここからスタート

mc = minecraft.Minecraft()

if len(sys.argv) < 6:
    sys.exit("Usage: leaves x y z radius density")

x = float(sys.argv[1])
y = float(sys.argv[2])
z = float(sys.argv[3])
r = float(sys.argv[4])
d = float(sys.argv[5])

min_x = int(x - r)
max_x = int(x + r)
min_y = int(y - r)
max_y = int(y + r)
min_z = int(z - r)
max_z = int(z + r)

for dy in range(min_y, max_y+1):
    for dx in range(min_x, max_x+1):
        for dz in range(min_z, max_z+1):
            xx =  dx - x
            yy =  dy - y
            zz =  dz - z
            s = xx * xx + yy * yy + zz * zz
            if math.sqrt(s) <= r:
                block_id = mc.getBlock(dx, dy, dz)
                if block_id == block.AIR:
                    if random.random() < d:
                        mc.setBlock(dx, dy, dz, block.LEAVES)

結構、外側が欠けると美しくないのでdentisyには1.0を指定した。

/python setblocks 0 0 0 0 5 0 17
/python leaves 0 4 0 2.7 1.0

f:id:pycra:20171007051944p:plain

まずは第1段として。

Pythonメモ

乱数はrandomをインポートして、random.random()で[0.0-1.0)の範囲で乱数を返す。

平方根はmathをインポートして、math.sqrt()で計算する。

ワールドタイプ:スーパーフラットの世界

ワールドタイプ:スーパーフラットの世界はどうなっていて、どこまでできるのか?

特に高さと深さの制限を調査する。

初期値

項目 備考
Block* 817 4 245 シード値:"Python World"
Facing South 初期値
雲の中 127〜129 雲の高さ
リスポーン位置 817 4 250 初回

Block*値はシード値が同じだと同じ値になる。

方角

方角
X軸プラス
西 X軸マイナス
Z軸プラス
Z軸マイナス
Y軸プラス
Y軸マイナス

地下

草、土、土、地殻の4つしかない。しかも、地殻も壊せるので、地殻を壊すと奈落の底へ落ちて死ぬ。

死ぬたびにリスポーン位置が変わる。

setBlock, setBlocksの座標系

今まで自分の位置とは関係ないグローバルな座標(と思って)を指定していたけど、自分の近くにブロックが設置されていた。 F3キーで表示されるBlockの座標とはかなり違う。 なので、setBlock, setBlocksの座標系がどうなっているのか調べる。

minecraftAPIのgetBlock, setBlock, setBlocksをコールするだけのスクリプトを書いて、それをコールしてみる。

getblock.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "getBlock({0:d}, {1:d}, {2:d})={3:d}"

mc = minecraft.Minecraft()

if len(sys.argv) < 4:
    sys.exit("getblock.py [x y z]")

pos_x = int(sys.argv[1])
pos_y = int(sys.argv[2])
pos_z = int(sys.argv[3])

blockId = mc.getBlock(pos_x, pos_y, pos_z)

mc.postToChat(FORMAT.format(pos_x, pos_y, pos_z, blockId))

setblock.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "setBlock({0:d}, {1:d}, {2:d}, {3:d})"

mc = minecraft.Minecraft()

if len(sys.argv) < 5:
    sys.exit("setblock.py [x y z b]")

pos_x = int(sys.argv[1])
pos_y = int(sys.argv[2])
pos_z = int(sys.argv[3])
block_id = int(sys.argv[4])

mc.setBlock(pos_x, pos_y, pos_z, block_id)

mc.postToChat(FORMAT.format(pos_x, pos_y, pos_z, block_id))

setblocks.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "setBlocks({0:d}, {1:d}, {2:d}, {3:d}, {4:d}, {5:d}, {6:d})"

mc = minecraft.Minecraft()

if len(sys.argv) < 5:
    sys.exit("setblocks.py [x1 y1 z1 x2 y2 z2 b]")

pos1_x = int(sys.argv[1])
pos1_y = int(sys.argv[2])
pos1_z = int(sys.argv[3])
pos2_x = int(sys.argv[4])
pos2_y = int(sys.argv[5])
pos2_z = int(sys.argv[6])
block_id = int(sys.argv[7])

mc.setBlocks(pos1_x, pos1_y, pos1_z, pos2_x, pos2_y, pos2_z, block_id)

mc.postToChat(FORMAT.format(pos1_x, pos1_y, pos1_z, pos2_x, pos2_y, pos2_z, block_id))

どうも関係がよくわからないので、playerの位置に関するAPIも実装してみた。 player.getPos(), player.getTitlePos(), player.setPos(...), player.setTitlePos(...) の4つ。

getpos.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "player.getPos()=({0:f}, {1:f}, {2:f})"

mc = minecraft.Minecraft()

if len(sys.argv) > 1:
    sys.exit("Usage: getpos.py")

pos = mc.player.getPos()

mc.postToChat(FORMAT.format(pos.x, pos.y, pos.z))

gettilepos.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "player.getTilePos()=({0:f}, {1:f}, {2:f})"

mc = minecraft.Minecraft()

if len(sys.argv) > 1:
    sys.exit("Usage: gettilepos.py")

pos = mc.player.getTilePos()

mc.postToChat(FORMAT.format(pos.x, pos.y, pos.z))

setpos.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "player.setPos({0:f}, {1:f}, {2:f})"

mc = minecraft.Minecraft()

if len(sys.argv) < 4:
    sys.exit("Usage: setpos.py [x y z]")

pos_x = float(sys.argv[1])
pos_y = float(sys.argv[2])
pos_z = float(sys.argv[3])

mc.player.setPos(pos_x, pos_y, pos_z)

mc.postToChat(FORMAT.format(pos_x, pos_y, pos_z))

settilepos.py

# coding: utf-8

import mcpi.minecraft as minecraft
import mcpi.block as block
import sys

FORMAT = "player.setTilePos({0:f}, {1:f}, {2:f})"

mc = minecraft.Minecraft()

if len(sys.argv) < 4:
    sys.exit("Usage: settilepos.py [x y z]")

pos_x = float(sys.argv[1])
pos_y = float(sys.argv[2])
pos_z = float(sys.argv[3])

mc.player.setTilePos(pos_x, pos_y, pos_z)

mc.postToChat(FORMAT.format(pos_x, pos_y, pos_z))

試しにplayer.getPos()を実行してみるとわかった!

F3キーで表示される座標系とは違う、独自の座標系で動いていた。

これはクリエイティブモードのスーパーフラットだけじゃなく、サバイバルモードでもそうだった。

だいたい、自分の初期位置は0 0 0の近く(だいたい4マス以内)になる。

だから初期位置にいればスクリプトで0 0 0原点でsetBlockしても見える範囲に設置される。

スーパーフラットでは、0 0 0は地表の1つ上。ここに何かを設置すると、「地表面に何かを置いた状態」になる。

上空の制限

高さ制限は255がMAXのもよう。それを超えるとF3キーの画面でOutside of world...と表示される。

setBlockでは251より上に設置できない。 試しにそのブロックの上に立ったらF3キーの画面のBlockの項のY値が256になった。

地下の制限

地下にsetBlock, setBlocksで-4以上の深さにはブロックを設置できない。