Chapter 14

WKBとEWKBを見てみる

boiledorange73
boiledorange73
2020.10.26に更新

はじめに

ジオメトリ表現として、WKT/EWKTを紹介しました。WKTについては「ジオメトリタイプ一覧をながめる」「WKTとジオメトリとの変換」、EWKTについては、とりあえずPostGISのテーブル作ってみようかをご覧下さい。

また、WKT/EWKTと全く異なる表現があることは、これまでの記事で若干触れているところではあるのですが、もういちど触れてみましょう。WKTのPOINTをSELECTで出すと、次のような結果を得ます。

db=# SELECT 'POINT(1 2)'::GEOMETRY;
                  geometry
--------------------------------------------
 0101000000000000000000F03F0000000000000040
(1 行)

この結果文字列が、POINT(1 2)と等価だけれども異なる書式に基づく表現であろうこと、そしてそれがWKT/EWKTよりも内部ではよく使われそうであることが分かると思います。

この表現はWKB、より正確に言うと、WKBを16進数で表したHEXWKB表現です。

PostGISは、WKT/EWKTのほかに、WKB/EWKB(bytea型)とHEXWKB/HEXEWKB(文字列型)が正式な表現として受け付けられます。

今回は、WKB/EWKBについて紹介します。

なお、本記事ではHEXWKB/HEXEWKBで説明しているのにWKB/EWKBと表示していますが、双方の構造は同じですので、気にしないで下さい。

ポイントのWKBを先頭から見ていく

POINT(1 2)と等価なWKT表現である0101000000000000000000F03F0000000000000040は、WKBのHEX表現で、2文字は1バイトに対応します。この文字列を分解してみましょう。

先頭1バイトは01で、ビッグエンディアンかリトルエンディアンかの別を表現します。01はリトルエンディアンを意味します。

続く4バイトは01000000で、32ビット整数0x00000001を意味します。これ示タタイプを表現しています。

続く8バイトは64ビット浮動小数点数000000000000F03Fで、X値を表現しています。さらに続く8バイト00000000000040はY値を表現しています。

まとめると、次のようになります。

  • 01 - エンディアン(01はリトルエンディアン)
  • 01000000 - ジオメトリタイプ(0x00000001はポイント)
  • 000000000000F03F 0番X値
  • 0000000000000040 0番Y値

ラインストリング、ポリゴンのWKB

順次、ラインストリング、ポリゴンのWKBも見ていきましょう。
この際、可変長データの直前に、データの要素数が付くことに注意してみてください。

db=# SELECT 'LINESTRING(1 2, 2 2)'::GEOMETRY;
                                      geometry

------------------------------------------------------------------------------------
 010200000002000000000000000000F03F000000000000004000000000000000400000000000000040
(1 行)
  • 01 - エンディアン(01はリトルエンディアン)
  • 02000000 - ジオメトリタイプ(ラインストリング)
  • 02000000 ポイント数
  • 000000000000F03F 0番X値
  • 0000000000000040 0番Y値
  • 0000000000000040 1番X値
  • 0000000000000040 1番Y値

「ポイント数」というのが出てきました。上述しましたが、WKBでは、可変長データの直前に、データの要素数が出現します。ラインストリングでは可変長のポイント配列を持つ(ポイントの1次配列です)ので、ポイントの要素数が出現します。

db=# SELECT 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1))'::GEOMETRY;

                                                                              geometry
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 01030000000200000005000000000000000000000000000000000000000000000000002440000000000000000000000000000024400000000000002440000000000000000000000000000024400000000000000000000000000000000004000000000000000000F03F000000000000F03F0000000000000040000000000000F03F00000000000000400000000000000040000000000000F03F000000000000F03F
(1 行)
  • 01 - エンディアン(ビッグエンディアン)
  • 03000000 - ジオメトリタイプ(ポリゴン)
  • 02000000 - リング数
  • 05000000 - 0番リングポイント数
  • 0000000000000000 0000000000000000 - 0番リング0番ポイントのX,Y値
  • 0000000000002440 0000000000000000 - 0番リング1番ポイントのX,Y値
  • 0000000000002440 0000000000002440 - 0番リング2番ポイントのX,Y値
  • 0000000000000000 0000000000002440 - 0番リング3番ポイントのX,Y値
  • 0000000000000000 0000000000000000 - 0番リング4番ポイントのX,Y値
  • 04000000 - 1番リングポイント数
  • 000000000000F03F 000000000000F03F - 1番リング0番ポイントのX,Y値
  • 0000000000000040 000000000000F03F - 1番リング1番ポイントのX,Y値
  • 0000000000000040 0000000000000040 - 1番リング2番ポイントのX,Y値
  • 000000000000F03F 000000000000F03F - 1番リング3番ポイントのX,Y値

ポリゴンは可変長のリング配列を持っているので、リングデータの直前にリング数が出てきました。各リングは可変長のポイント配列を持っているので、ポイント配列の直前にポイント数が出てきます。

SRID付きポイントのEWKB

上の例ではSRIDが付いていないジオメトリでしたが、SRIDが付くジオメトリについても見てみましょう。ここではポイントのみ扱いますが、ヘッダ部に変更がある以外はSRIDが付いていないジオメトリと同じですので、省略します。

db=# SELECT 'SRID=4612;POINT(1 2)'::GEOMETRY;
                      geometry
----------------------------------------------------
 010100002004120000000000000000F03F0000000000000040
(1 行)
  • 01 - エンディアン(リトルエンディアン)
  • 01000020 - ジオメトリタイプ(後述)
  • 04120000 - 整数(後述)
  • 000000000000F03F 0000000000000040 - X値、Y値

ジオメトリタイプが01000020となっています。32ビット整数0x20000001となります。0x20000000がSRIDがついていることを示すフラグです。

SRIDフラグがある場合には、ジオメトリタイプに続く4バイトが、SRIDを示す32ビット整数を表現しています上の例では04120000となっていますが、32ビット整数0x00001204で、10進数表記すると4612です。

座標値リストに入る直前に32ビット整数値が挿入されます。このため、アタマから読んで、SRIDの有無を確認したうえでないと座標値の読み取りで位置ズレが生じる恐れがあります。ストリーム通りに読んで行く方が安全です。

マルチ系ジオメトリのWKB

マルチ系ジオメトリについてみてみます。これもマルチポイントのみ示すこととし、マルチラインストリング、マルチポリゴンと同じ理屈で生成できるので省略します。

ラインストリングやポリゴンで出てきましたが、可変長データの直前に、その要素数が出現します。マルチ系ジオメトリも同じで、可変長の単一ジオメトリの配列が出現する直前に、単一ジオメトリの要素数が出てきます。

db=# SELECT 'MULTIPOINT(1 2, 11 2)'::GEOMETRY;
                                                geometry

--------------------------------------------------------------------------------------------------------
 0104000000020000000101000000000000000000F03F0000000000000040010100000000000000000026400000000000000040
(1 行)
  • 01 - エンディアン(リトルエンディアン)
  • 04000000 - ジオメトリタイプ(マルチポイント)
  • 02000000 - 単一ジオメトリ数
  • 01 - 0番ジオメトリ エンディアン
  • 01000000 - 0番ジオメトリ ジオメトリタイプ(ポイント)
  • 000000000000F03F 0000000000000040 - 0番ジオメトリ X値、Y値
  • 01 - 1番ジオメトリ エンディアン
  • 01000000 - 1番ジオメトリ ジオメトリタイプ(ポイント)
  • 0000000000002640 0000000000000040 - 1番ジオメトリ X値、Y値

個々の単一ジオメトリは、切り出すと、ひとつのジオメトリとして独立して表現されるものです。

SRID付きマルチジオメトリのEWKB

ここも、SRID付きマルチポイントについてのみ紹介します。ここまでについて十分に理解できていれば、一点だけ除いて、素直に読めるかと思います。

db=# SELECT 'SRID=4612;MULTIPOINT(1 2, 11 2)'::GEOMETRY;
                                                    geometry
----------------------------------------------------------------------------------------------------------------
 010400002004120000020000000101000000000000000000F03F0000000000000040010100000000000000000026400000000000000040
(1 行)
  • 01 - エンディアン(リトルエンディアン)
  • 04000020 - ジオメトリタイプ(SRIDマルチポイント)
  • 04120000 - SRID
  • 02000000 - 単一ジオメトリ数
  • 01 - 0番ジオメトリ エンディアン
  • 01000000 - 0番ジオメトリ ジオメトリタイプ(ポイント)
  • 000000000000F03F 0000000000000040 - 0番ジオメトリ X値、Y値
  • 01 - 1番ジオメトリ エンディアン
  • 01000000 - 1番ジオメトリ ジオメトリタイプ(ポイント)
  • 0000000000002640 0000000000000040 - 1番ジオメトリ X値、Y値

マルチジオメトリはSRID付きですが、単一ジオメトリは「SRIDなし」となります。

4次元ラインストリングを見てみる

db=# SELECT 'SRID=4612;LINESTRING(1 2 3 4, 2 2 4 5)'::GEOMETRY;
                                                                          geometry
------------------------------------------------------------------------------------------------------------------------------------------------------------
 01020000E00412000002000000000000000000F03F0000000000000040000000000000084000000000000010400000000000000040000000000000004000000000000010400000000000001440
(1 行)
  • 01 - エンディアン
  • 020000E0 - ジオメトリタイプ(後述)
  • 04120000 - SRID (4612)
  • 02000000 - ポイント数
  • 000000000000F03F 0000000000000040 0000000000000840 0000000000001040 - 0番X, Y, Z, M値
  • 0000000000000040 0000000000000040 0000000000001040 0000000000001440 - 1番X, Y, Z, M値

0x00000002 はラインストリング、0x20000000がSRID付きフラグで、足した0x20000002でSRID付きラインストリングとなる、というのは前に示しました。
さらに、Z値を持つことを示すフラグが、0x80000000、M値は0x40000000に、それぞれなります。
すべて足し合わせると、最上位バイトは0x2+0x8+0x4=0xEであり、SRID付きで、Z値とM値を取るラインストリングは0xE0000002であることが分かります。

座標値は、構成ポイントごとに、X値、Y値、Z値、M値の順からなる要素長4の倍精度浮動小数点数配列が並びます。

まとめ

可変長は直前に要素数

可変長データが出現する場合には、その直前に要素数が出現するようになっています。

順序

  • 先頭1バイトはエンディアンを表現
  • 次の4バイトは、32ビット整数で、ジオメトリタイプを表現
  • 前の32ビット整数で0x20000000ビットが立っている場合は、次の4バイト(32ビット整数)で、SRIDを表現(0x20000000ビットが立っていない場合は存在しない)
  • 次からジオメトリタイプごとに異なる
    • ポイントは、次の16バイト(2×64ビット浮動小数点数)でX値、Y値を表現
    • ラインストリングは、次の4バイト(32ビット整数)でポイント数を表現、さらに続く(ポイント数)×16バイトで各ポイントのX値、Y値を表現
    • ポリゴンは、次の4バイト(32ビット整数)でリング数を表現、次からリングごとの表現
      • 次の4バイトでポイント数を表現、さらに続く(ポイント数)×16バイトで各ポイントのX値、Y値を表現
    • マルチ系ジオメトリは、次の4バイト(32ビット整数)で単一ジオメトリ数を表現し、単一ジオメトリをひとつずつ表現(ただしSRIDは付かない)

ジオメトリタイプ

ジオメトリタイプは、32ビット整数で次のとおり表現されます。

  • 0x00000001 - ポイント
  • 0x00000002 - ラインストリング
  • 0x00000003 - ポリゴン
  • 0x00000004 - マルチポイント
  • 0x00000005 - マルチラインストリング
  • 0x00000006 - マルチポリゴン
  • 0x00000007 - ジオメトリコレクション

ただし、これに0x20000000ビットを立てるとSRID付きEWKBとなります。

  • 0x20000001 - SRID付きポイント
  • 0x20000002 - SRID付きラインストリング
  • 0x20000003 - SRID付きポリゴン
  • 0x20000004 - SRID付きマルチポイント
  • 0x20000005 - SRID付きマルチラインストリング
  • 0x20000006 - SRID付きマルチポリゴン
  • 0x20000007 - ジオメトリコレクション

また、Z値、M値を持つ場合は、次のビットが立ちます。

  • 0x80000000 - Z値を持つ
  • 0x40000000 - M値を持つ

おわりに

PostGISで使用するジオメトリ表現のひとつである、WKB/EWKBについて説明しました。

バイナリなので、JavaScriptで直接扱うのは難しいですが、WKT/EWKTよりも読み取りプログラムを書くのが容易なので、PostGISクライアントのプログラムを書くなら、覚えておいて損はないと思います。