Slide 1

Slide 1 text

FOSS4G 2020 Japan Online 松澤太郎 Python/Javascriptで 読む点群

Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

タイル三兄弟の一味 Twitter: @smellman Georepublic シニアデベロッパ、日本UNIXユーザ会理事、OSGeo日本支部理事、 OpenStreetMap Foundation Japanメンバー React NativeとかRuby on RailsとかPythonとかやってる地理系プログラマー ブレイクコアクラスタ 自己紹介

Slide 4

Slide 4 text

A Location Technology Company

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Open Source GISを得意とする会社 東京、神戸、ミュンヘンにオフィスがあります 神戸オフィスはちょっとエモいので神戸に来たら遊びに来るといいよ さまざまな国や地域のエンジニアが参加 お仕事待ってます(^^ Georepublic

Slide 8

Slide 8 text

弊社社長が政府CIO補佐官に 政府がハックされる事態に 乞うご期待! News

Slide 9

Slide 9 text

バイナリの読み方は仕様がわかっていれば(比較的)簡単 (比較的)簡単であることさえ知られてないのでは? 簡単なフォーマットから始めればいろいろ応用が効く バイナリが読めれば書けるようにもなる オレオレVector Tileなんかも作れちゃう(俺は作ったことがある) なぜバイナリを読む?

Slide 10

Slide 10 text

点群とは(さくっと) Pythonで読む https://github.com/smellman/python_lasreader_example Javascriptで読む https://github.com/smellman/javascript_lasreader_example 今日のお題

Slide 11

Slide 11 text

点群とは

Slide 12

Slide 12 text

地理で使われるデータの一種 レーザー測量やSfMなどで得られたPoint Data群 Point Cloudという 3Dでの地図表現でよく使われるデータ 最近では自動運転とかSmartうんたらかんたら 点群とは https://tiles.smellman.org/kakegawa/index-itowns.html

Slide 13

Slide 13 text

las(LASer format)及びlaz(zip圧縮したもの)が有名 lasの仕様書は公開されている https://www.asprs.org/wp-content/uploads/2010/12/LAS_1_4_r13.pdf las1.2〜1.4が一般的に使われている バージョンの違いはヘッダー及び扱えるものの差 フォーマット

Slide 14

Slide 14 text

Potree 独自のストラクチャを持つもの。専用のコンバータ PotreeConverter を使う。 一時期PotreeConverterが商用ソフトになった(現在はBSDライセンス)。 Cesium or iTown or giro3d 3D Tiles に対応。OSSのコンバータでは entwine か py3dtiles が有名。 点群を表示するWebライブラリ

Slide 15

Slide 15 text

lasデータは仕様が公開されている 静岡県がlasデータをたくさん公開している Creative Commons License 4.0 バイナリを読むプログラムを書くのに良い題材となる 今回はヘッダーを読むプログラムを見て雰囲気を掴んでほしい 今回の本題

Slide 16

Slide 16 text

Pythonで読む

Slide 17

Slide 17 text

Pythonに限らずだいたいのプログラミング言語には標準でバイナリを読むモジュールがある Pythonの場合 struct を利用する https://docs.python.org/ja/3/library/struct.html Pythonで読む

Slide 18

Slide 18 text

lasの仕様書を手元に置きます import struct をします open(file_path, ‘rb’) でファイルを開きます rb = read only, binary structで一つずつ読み込んでいきます 読み方

Slide 19

Slide 19 text

こんな感じのコードになります 先頭を読む import struct f = open(‘your.las’, ‘rb’) file_signature = b"".join(list(struct.unpack('4c', f.read(1*4)))).decode(‘ascii') print(file_signature) f.close() # 閉じておく

Slide 20

Slide 20 text

こんな感じのコードになります File Source IDを読む import struct f = open(‘your.las’, ‘rb’) file_signature = b"".join(list(struct.unpack('4c', f.read(1*4)))).decode(‘ascii') (file_source_id,) = struct.unpack(‘H’, f.read(2)) print(file_source_id) f.close() # 閉じておく tupleで返ってくるので注意

Slide 21

Slide 21 text

f.read関数はファイルを読む開始位置をずらしていきます 例えば二回f.readを使うと… f.read(4) f.read(3) 開始位置が7へ移動する read関数 read(4) 0 read(3) N

Slide 22

Slide 22 text

struct.unpackで読んだバイナリをどのようにアサインするかを決める struct.unpack('4c', f.read(1*4)) char[4] struct.unpack('

Slide 23

Slide 23 text

Lasのフォーマットのドキュメントを読みながらひたすらstruct.unpackを並べていく 随時printするなりして問題ないかチェックする unit testを書いても良い わりと単純にPythonでは読むことができる 逆を言うとJavascriptはクセがすごい あとは突き合わせをしていく

Slide 24

Slide 24 text

こんな感じになる def header(self, f): file_signature = b"".join(list(struct.unpack('4c', f.read(1*4)))).decode('ascii') (file_source_id,) = struct.unpack('H', f.read(2)) (global_encoding,) = struct.unpack('H', f.read(2)) (guid_data1,) = struct.unpack('

Slide 25

Slide 25 text

LASのversion minorによってヘッダの大きさが変わる LAS 1.2 -> 227byte LAS 1.3 -> 235byte LAS 1.4 -> 375byte 必ずversion checkを入れること 読む上で注意点

Slide 26

Slide 26 text

ヘッダーにOffset to point dataという値があるので、ここに f.seek をして読み始める データそのものの読み方も仕様を読みながら。 実際の値はscale factorとoffsetの組み合わせになるとかもちゃんと仕様に書いてあるの で見逃さないこと。 点群そのものを読む

Slide 27

Slide 27 text

Javascriptで読む

Slide 28

Slide 28 text

今回はFile APIとFileReader APIを利用 ブラウザからファイルを読み込む用途を想定 時間があればデモします https://smellman.github.io/javascript_lasreader_example/ Javascriptで読む

Slide 29

Slide 29 text

FileReader APIにイベントハンドラを作成します blobファイルをsliceしたものをFileReader APIで読みます 次のoffsetを指定してまたsliceしてFileReader APIで読みます 次の(ry 読み方

Slide 30

Slide 30 text

sliceで読み取る範囲を指定して readAsArrayBufferで読み取る onloadイベントハンドラが読みとっ て、またreadAsArrayBufferを呼び 出して… イベントハンドラがバイナリを読 む責務を追っている 最後はreader.abort()をする ややこしい class LasReader { constructor(blob) { this.blob = blob } read(callback) { let offset = 0 let reader = new FileReader() let slice = this.blob.slice(offset, 4) reader.onload = (e) => { if (offset == 0) { const file_signature = char_to_string(e.target.result) console.log(`file_signature: ${file_signature}`) offset = 4 slice = self.blob.slice(offset, 6) } else if (offset == 6) { … } else { reader.abort() // call reader.onloaded } reader.readAsArrayBuffer(slice) } reader.onloaded = () => { callback(…) } reader.readAsArrayBuffer(slice) // 開始 } }

Slide 31

Slide 31 text

こういう構造のケースはPromiseを使うと簡単に書くことができます async/await を使えばPythonっぽく書くこともできます Promiseを使おう

Slide 32

Slide 32 text

offsetとreaderをthisで参照可能に する onloadのときにresolveを呼ぶ sliceを作るサイズを引数にして、成 功時にoffsetに加算する Promise class LasParser { constructor(blob) { this.blob = blob this.offset = 0 this.reader = new FileReader() } readSlice(size) { return new Promise((resolve, reject) => { this.reader.onload = (e) => { this.offset += size resolve(e.target.result) } this.reader.onerror = (e) => { console.log('onerror') reject(e) } const slice = this.blob.slice(this.offset, this.offset + size) this.reader.readAsArrayBuffer(slice) }) }

Slide 33

Slide 33 text

async/await async read(target, callback) { let result = await this.readSlice(4) const file_signature = char_to_string(result) result = await this.readSlice(2) const file_source_id = (new DataView(result)).getUint16() result = await this.readSlice(2) const global_encording = (new DataView(result)).getUint16(0, true) result = await this.readSlice(4) const guid_data1 = (new DataView(result)).getUint32(0, true) … const z = new DataView(result) const max_z = z.getFloat64(0, true) const min_z = z.getFloat64(8, true) this.reader.abort() callback(max_x, min_x, max_y, min_y) } await this.readSlice で何バイト読 むか指定 読まれたバイナリの処理の責務が すべてread関数になる 最後はthis.reader.abort()を実行 してcallbackを呼ぶ

Slide 34

Slide 34 text

DataViewなどの処理をするところが外だしできる Promiseを使わないとイベントハンドラ内なのでthisを使いまくるしかない Promiseを使えば変数はほぼconstで指定できるので安心できる letを使うのは一箇所ぐらい ヘッダ以外にもoffsetをずらせばbodyも簡単に読めるよ! Promiseの良いところ

Slide 35

Slide 35 text

Pythonではstructを使うと簡単にバイナリが読めるよ JavascriptはクセがあるけどPromise/async/awaitを使えば簡単に読めるよ 今回作ったプログラム(Las 1.2まで) https://github.com/smellman/python_lasreader_example https://github.com/smellman/javascript_lasreader_example まとめ