ドキュメント / 使い方

配信データを読む

MIRGRID は、セルのデータを MGVX packet という形式で配信しています。この文書では、packet を読んで描画に使える配列にする方法、粗いレベルで軽く読む方法、packet を作る方法を説明します。

以下の例は、最初に次のように読み込んだものとして書いています。Node.js で試すときは、init() の呼び方をはじめにのとおりに変えてください。

import init, {
  encode_voxel_packet,
  packet_metadata,
  decode_voxel_packet,
  decode_voxel_packet_lod,
  decode_voxel_packet_in_frame,
  voxel_faces_from_packet,
  FrameJs,
  VoxelizerJs,
} from '@mirgrid/web-sdk';

await init();

MGVX packet とは

MGVX packet は、たくさんのセルを 1 つのバイト列にまとめたものです。先頭にヘッダがあり、そのあとにセルの列が続きます。セルごとに、番号・色・由来・feature id(セルがどの地物に属するかを表す番号)などを持っています。

中身の並びを知らなくても、この SDK の関数で読み書きできます。packet は Uint8Array で受け渡します。

packet の関数には、xorKey という 1 バイトの引数があります。packet のバイト列に XOR をかけて配信しているときの鍵です。XOR をかけていない packet では 0 を渡します。この文書の例はすべて 0 です。

例に使う packet を用意する

アプリでは、配信元から受け取ったバイト列をそのまま使います。

const response = await fetch(url);
const bytes = new Uint8Array(await response.arrayBuffer());

この文書では、手元で試せるように encode_voxel_packet で小さな packet を作って使います。はじめにで求めた東京駅のあたりのセル(レベル 24)から、4 × 4 × 2 個のセルを並べます。作り方はpacket を作るで説明します。

const [baseX, baseY, baseZ] = [4428917, 11738702, 12088151];
const records = [];
for (let z = 0; z < 2; z++) {
  for (let y = 0; y < 4; y++) {
    for (let x = 0; x < 4; x++) {
      records.push({
        vx: baseX + x, vy: baseY + y, vz: baseZ + z,
        r: 60 * x, g: 60 * y, b: 200,
        featureIdPrimary: 100 + y,
      });
    }
  }
}
const bytes = encode_voxel_packet(records, 24);
console.log(bytes.length);
717

packet の情報を読む

packet_metadata は、ヘッダに書かれた情報を返します。セルを読み出さないので、すぐに終わります。

console.log(packet_metadata(bytes, 0));
{
  voxelLevel: 24,
  columnMask: 31,
  headerLength: 40,
  byteLength: 717,
  vxBase: 4428917,
  vyBase: 11738702,
  vzBase: 12088151,
  count: 32
}

voxelLevel はセルのレベル、count はセルの数です。vxBase・vyBase・vzBase は、packet の中のセルの番号の基準(各軸の最小値)です。columnMask は、packet にどの項目が入っているかを表します。byteLength は packet 全体の、headerLength はヘッダの大きさ(バイト)です。

セルを読み出す

decode_voxel_packet は、packet のセルを描画に使える配列にします。引数は、packet、xorKey、描画の原点の経度・緯度・楕円体高、レベルの上限(粗いレベルで読むを参照。使わないときは null)です。

const decoded = decode_voxel_packet(bytes, 0, 139.767125, 35.681236, 40, null);
const positions = decoded.positions;
const colors = decoded.colors;
const featureIds = decoded.feature_ids_primary;
console.log(decoded.count);
console.log(...positions.subarray(0, 3));
console.log(...colors.subarray(0, 4));
console.log(...featureIds.subarray(0, 8));
console.log(decoded.feature_ids_secondary[0]);
32
-0.15693441033363342 0.005584695842117071 0.20313210785388947
0 0 200 0
100 100 100 100 101 101 101 101
4294967295

戻り値の DecodedVoxels からは、次の値を読めます。

プロパティ 内容
count セルの数
positions セルの中心を、原点から見た ENU(m)で並べた Float32Array(1 セル 3 要素)
colors r・g・b と、セルの由来などを入れた 1 バイトを並べた Uint8Array(1 セル 4 バイト)
feature_ids_primary セルが属する地物の番号(Uint32Array)
feature_ids_secondary 2 つ目の地物の番号(Uint32Array)
normal_masks 表に出ている面の向きを表すビット(1 セル 1 バイトの Uint8Array)

positions は ENU なので、Float32Array のままでも GPU に渡せます(ENU に変換するを参照)。colors の 4 バイト目は透明度ではありません。下位 4 ビットにセルの由来(1 なら 3D モデル、2 なら点群、3 なら両方)が入っています。feature id が無いセルには 4294967295 が入ります。

これらのプロパティは、読むたびに WebAssembly のメモリから新しい配列にコピーします。何度も使うときは、上の例のように一度変数に入れてください。

DecodedVoxels は WebAssembly のメモリを使っています。使い終わったら free() で解放してください。

decoded.free();

粗いレベルで読む

decode_voxel_packet の最後の引数にレベルを渡すと、それより細かいセルを、そのレベルのセルにまとめて読みます。遠くの景色のように細かさが要らないところでは、セルの数を減らして描画を軽くできます。

const coarse = decode_voxel_packet(bytes, 0, 139.767125, 35.681236, 40, 22);
console.log(coarse.count);
coarse.free();
8

レベル 24 の 32 個のセルが、一辺 4 m のレベル 22 のセル 8 個にまとまりました。

中心からの距離でレベルを変える

decode_voxel_packet_lod は、セルごとに、ある中心からの距離に応じてレベルを決めて読みます。中心の近くは細かく、遠くは粗く読めます。

引数は順に、packet、xorKey、描画の原点(経度・緯度・楕円体高)、中心の経度・緯度、距離のしきい値 r1・r2・r3・r4(m)、距離の測り方、高さ方向の倍率、レベル 24 より細かく読むかどうか、です。中心の高さには描画の原点の高さを使います。

セルのレベルは、中心からの距離で次のように決まります。

中心からの距離 レベル
r1 以内 24(最後の引数が true なら、r1 の 3/4 以内で 25、1/2 以内で 26、1/4 以内で 27、1/8 以内で 28、1/16 以内で 29、1/32 以内で 30)
r2 以内 22
r3 以内 21
r4 以内 20
r4 の 1.5 倍以内 19
r4 の 3 倍以内 18
それより遠い 17

最後の引数が false のときは、r1 の半分以内が 24、r1 以内が 23 になり、レベル 24 より細かくは読みません。どの場合も、packet のレベルより細かくはなりません。

距離の測り方が 0 なら、中心からの 3 次元の距離で比べます。1 なら、水平の距離が r 以内で、かつ高さの差が r × 高さ方向の倍率以内のときに「r 以内」とみなします。

次の例では、r1 を 3 m にしています。中心から 3 m 以内のセルはレベル 24 のまま、それより遠いセルはレベル 22 にまとまります。読んだセルのレベルは、colors の 4 バイト目の上位 4 ビットに「レベル − 17」として入っています。

const lod = decode_voxel_packet_lod(
  bytes, 0,
  139.767125, 35.681236, 40,
  139.767125, 35.681236,
  3, 200, 400, 800,
  0, 1, true,
);
const lodColors = lod.colors;
const perLevel = {};
for (let i = 0; i < lod.count; i++) {
  const level = 17 + (lodColors[4 * i + 3] >> 4);
  perLevel[level] = (perLevel[level] ?? 0) + 1;
}
console.log(lod.count, perLevel);
lod.free();
18 { '22': 6, '24': 12 }

現場に揃えた格子の packet を読む

VoxelizerJs.siteAuto で作った packet は、現場に揃えた格子の番号でセルを持っています(現場に揃えた格子と地球基準の格子を参照)。packet のバイト列には格子の情報が入っていないので、作ったときの格子の記述子と組にして読みます。記述子を付けずに読むと、地球基準の格子の番号として読まれ、まったく別の場所に出てしまいます。

次の例では、東に 1 m ずつ離れた 3 つの点から、現場に揃えた格子の packet を作っています。

const site = VoxelizerJs.siteAuto(24);
site.setInput({ coordSystem: 'local', origin: [139.767125, 35.681236, 40] });
site.pushPoints(new Float64Array([0, 0, 0, 1, 0, 0, 2, 0, 0]));
const sitePacket = site.toPacket();
const siteFrame = site.frameDescriptor();
site.free();
console.log(siteFrame);
{
  kind: 'site',
  datum: 'JGD2011',
  epoch: 2011.4,
  anchor: '0000000002e9380374bc9c0000000000',
  yaw: 0
}

decode_voxel_packet_in_frame には、記述子から作った FrameJs と、その格子の座標での原点を渡します。positions は、セルの中心の格子の座標から原点を引いた値になります。現場に揃えた格子の座標は、格子の基準の点から見た東・北・上(m)です。

const frame = FrameJs.fromDescriptor(siteFrame);
const inFrame = decode_voxel_packet_in_frame(sitePacket, 0, frame, 0, 0, 0, null);
const framePositions = inFrame.positions;
for (let i = 0; i < inFrame.count; i++) {
  console.log(...framePositions.subarray(3 * i, 3 * i + 3));
}
inFrame.free();
44.4921875 -18.5078125 -43.5078125
45.4921875 -18.5078125 -43.5078125
46.4921875 -18.5078125 -43.5078125

3 つのセルは、東の値だけが 1 m ずつ違い、北と上の値は同じです。格子が東・北・上に沿っているので、東に並んだ点はそのまま東に並んだセルになります。

decode_voxel_packet_in_frame は ENU への回転をしません。FrameJs.ecef() を渡したときの positions は、ECEF の軸のまま原点を移しただけの値で、decode_voxel_packet の ENU とは違います。Float32Array の丸めを避けるため、原点はセルの近くの点にしてください。

packet から面を作って描く

voxel_faces_from_packet(bytes, xorKey, frame, origin) は、packet のセルから表に出ている面を作ります。結果は VoxelizerJs.faces() と同じ形なので、そのまま描画器に渡せます(ファイルをセルにして表示するを参照)。

frame には FrameJs か格子の記述子を渡します。地球基準の格子の packet なら null です。origin は描画の原点 [経度, 緯度, 楕円体高] で、null ならセル全体を囲む箱の中心になります。

const faces = voxel_faces_from_packet(bytes, 0, null, [139.767125, 35.681236, 40]);
console.log(faces.cellCount, faces.faceCount);
const siteFaces = voxel_faces_from_packet(sitePacket, 0, siteFrame, null);
console.log(siteFaces.frameKind, siteFaces.cellCount, siteFaces.faceCount);
32 64
site 3 14

ブラウザでは、描画器にレイヤとして置きます。ほかのレイヤと重ねるときは、描画器の原点 renderer.originLlh を origin に渡してください。

import { createVoxelRenderer } from '@mirgrid/web-sdk/render.mjs';

const canvas = document.querySelector('canvas');
const renderer = createVoxelRenderer(canvas);
const response = await fetch(url);
const packetBytes = new Uint8Array(await response.arrayBuffer());
const packetFaces = voxel_faces_from_packet(packetBytes, 0, null, renderer.originLlh);
renderer.setLayer('packet', packetFaces);

クリックしたセルを調べる

packet のレイヤでは、VoxelizerJs.cellInfo が使えません。代わりに、面の格子の座標とセル 0 の番号(index0)からセルの番号を求め、FrameJs の cellAt で空間アドレスと中心を調べます。

function packetCellAt(clientX, clientY) {
  const hit = renderer.pick(clientX, clientY);
  if (!hit || hit.layerId !== 'packet') return null;
  const grid = packetFaces.coords.subarray(4 * hit.face, 4 * hit.face + 3);
  const index = packetFaces.index0.map((value, axis) => value + grid[axis]);
  const packetFrame = FrameJs.ecef(); // 現場に揃えた格子なら FrameJs.fromDescriptor(記述子)
  const cell = packetFrame.cellAt(index[0], index[1], index[2], packetFaces.level);
  packetFrame.free();
  return cell;
}

cellAt は { cellId, centerEcef } を返します。地球基準の格子なら cellId は空間アドレスです。クリックとドラッグの見分け方は、クリックしたセルを調べるの例と同じです。

packet を作る

encode_voxel_packet(records, level) は、セルの配列から packet を作ります。セルは次の項目を持つオブジェクトで、vx・vy・vz 以外は省けます。

項目 内容 省いたとき
vx・vy・vz セルの番号 省けません
r・g・b 色(0〜255) 0
sourceType セルの由来(1 なら 3D モデル、2 なら点群、3 なら両方) 0
normalMask 表に出ている面の向きを表すビット 63(6 面すべて)
featureIdPrimary 地物の番号 無し(4294967295)
featureIdSecondary 2 つ目の地物の番号 無し(4294967295)

番号の基準(vxBase など)は、各軸の最小の番号から自動で決まります。作った packet は、この文書の関数でそのまま読めます。

const twoCells = encode_voxel_packet([
  { vx: 4428917, vy: 11738702, vz: 12088151, r: 200, g: 40, b: 40 },
  { vx: 4428918, vy: 11738702, vz: 12088151, r: 40, g: 200, b: 40, featureIdPrimary: 7 },
], 24);
const check = decode_voxel_packet(twoCells, 0, 139.767125, 35.681236, 40, null);
console.log(check.count, ...check.colors, ...check.feature_ids_primary);
check.free();
2 200 40 40 0 40 200 40 0 4294967295 7

点群や 3D モデルのファイルから packet を作るときは、VoxelizerJs の toPacket() を使います(MGVX に保存するを参照)。

使い終わったら解放する

DecodedVoxels と FrameJs は、使い終わったら free() で解放してください。

frame.free();

packet_metadata と voxel_faces_from_packet の結果は普通の JavaScript の値なので、解放は要りません。

関連する文書

packet の位置を ENU や緯度・経度で扱う方法は位置と空間アドレスで、ファイルから packet を作って描く方法はファイルをセルにして表示するで説明しています。関数の引数の一覧はAPI リファレンスにあります。