Files
massive-cloud/server/pointcloudMetadata.test.ts

126 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
inspectPointcloudMetadata,
MAX_PLY_HEADER_BYTES,
parsePlyHeader,
PointcloudMetadataParseError,
} from "./pointcloudMetadata";
const temporaryDirectories: string[] = [];
afterEach(() => {
while (temporaryDirectories.length > 0) {
rmSync(temporaryDirectories.pop()!, { recursive: true, force: true });
}
});
function header(lines: string[], newline = "\n") {
return Buffer.from([...lines, "end_header", ""].join(newline), "ascii");
}
describe("parsePlyHeader", () => {
it("解析 ASCII PLY,并以 element vertex 作为精确点数", () => {
const result = parsePlyHeader(header([
"ply",
"format ascii 1.0",
"element vertex 1000000000",
"property float x",
"property float y",
"property double z",
"element face 999",
"property list uchar int vertex_indices",
]));
expect(result).toEqual({
pointCount: 1_000_000_000,
pointDataFormat: "ascii",
bytesPerPoint: 16,
pointAttributes: [
{ name: "x", type: "float32", bytes: 4 },
{ name: "y", type: "float32", bytes: 4 },
{ name: "z", type: "float64", bytes: 8 },
],
});
});
it.each([
["binary_little_endian", "binary_little_endian"],
["binary_big_endian", "binary_big_endian"],
] as const)("解析 %s PLY", (format, expected) => {
const result = parsePlyHeader(header([
"ply",
`format ${format} 1.0`,
"element vertex 42",
"property int16 x",
"property ushort intensity",
"property uchar red",
]));
expect(result.pointDataFormat).toBe(expected);
expect(result.bytesPerPoint).toBe(5);
});
it("支持 CRLF 文件头和类型别名", () => {
const result = parsePlyHeader(header([
"ply",
"format ascii 1.0",
"comment generated",
"element vertex 3",
"property char classification",
"property uint confidence",
"property float32 temperature",
], "\r\n"));
expect(result.pointAttributes).toEqual([
{ name: "classification", type: "int8", bytes: 1 },
{ name: "confidence", type: "uint32", bytes: 4 },
{ name: "temperature", type: "float32", bytes: 4 },
]);
});
it("拒绝无效文件头", () => {
expect(() => parsePlyHeader(Buffer.from("not-ply\n"))).toThrow(PointcloudMetadataParseError);
});
it("拒绝未知的顶点属性类型", () => {
expect(() => parsePlyHeader(header([
"ply",
"format ascii 1.0",
"element vertex 1",
"property half x",
]))).toThrow("未知的顶点属性类型");
});
it("拒绝列表型顶点属性,但忽略其他 element 的列表属性", () => {
expect(() => parsePlyHeader(header([
"ply",
"format ascii 1.0",
"element vertex 1",
"property list uchar float samples",
]))).toThrow("列表型顶点属性");
});
it("非 PLY 格式直接标记为 unsupported,不读取文件", () => {
expect(inspectPointcloudMetadata("/path/does/not/exist.txt", "cloud.txt")).toEqual({
metadataStatus: "unsupported",
pointCount: null,
pointAttributes: null,
pointDataFormat: null,
bytesPerPoint: null,
metadataError: null,
});
});
it("只读取 16KB 文件头,超长文件头标记为解析失败", () => {
const directory = mkdtempSync(join(tmpdir(), "pointcloud-metadata-"));
temporaryDirectories.push(directory);
const filePath = join(directory, "oversized.ply");
writeFileSync(filePath, `ply\nformat ascii 1.0\ncomment ${"x".repeat(MAX_PLY_HEADER_BYTES)}\nend_header\n`);
const result = inspectPointcloudMetadata(filePath, "oversized.ply");
expect(result.metadataStatus).toBe("error");
expect(result.metadataError).toContain("16KB");
});
});