Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Python/Javascriptで読む点群

 Python/Javascriptで読む点群

FOSS4G 2020 Japan Online

Taro Matsuzawa aka. btm

November 08, 2020
Tweet

More Decks by Taro Matsuzawa aka. btm

Other Decks in Programming

Transcript

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

    View full-size slide

  2. 自己紹介

    View full-size slide

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

    View full-size slide

  4. A Location Technology Company

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  9. 点群とは

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. Pythonで読む

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. こんな感じのコードになります
    先頭を読む
    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() # 閉じておく

    View full-size slide

  18. こんな感じのコードになります
    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で返ってくるので注意

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. こんな感じになる
    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('(guid_data2,) = struct.unpack('H', f.read(2))
    (guid_data3,) = struct.unpack('H', f.read(2))
    guid_data4 = struct.unpack('<8B', f.read(1*8))

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. Javascriptで読む

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. 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) // 開始
    }
    }

    View full-size slide

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

    View full-size slide

  30. 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)
    })
    }

    View full-size slide

  31. 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を呼ぶ

    View full-size slide

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

    View full-size slide

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

    View full-size slide