hypc 0.1.0

Reader/writer for HPC1 point clouds with SMC1 and GEOT chunks.
Documentation
  • Coverage
  • 41.38%
    24 out of 58 items documented0 out of 19 items with examples
  • Size
  • Source code size: 34.31 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 3.42 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Links
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • 19h

hypc

Crates.io License: MIT

A Rust crate for reading and writing HPC1 point clouds, with first-class support for SMC1 semantic masks and GEOT georeferencing chunks.

This crate provides a simple, fast, and safe way to interact with .hpc files. It is designed to be lightweight, with minimal dependencies (anyhow for error handling and miniz_oxide for zlib compression).

Features

  • HPC1 Core Support: Read and write the base HPC1 point cloud format, including header and quantized position data.
  • TileKey Integration: Natively handles TileKey metadata, allowing for slippy-map style (zoom, x, y) keys or 64-bit name hashes to be embedded directly in the file header.
  • Semantic Masks (SMC1): Full support for reading and writing the SMC1 chunk. This allows for a 2D semantic label grid to be associated with the point cloud, useful for classification and segmentation tasks. Supports both raw and zlib-compressed mask data.
  • Georeferencing (GEOT): Full support for reading and writing the GEOT chunk. This provides a geographic bounding box (CRS:84) for the point cloud, enabling coordinate transformations between the local decode space and longitude/latitude.
  • High-Level Helpers: Provides convenient methods for common tasks, such as:
    • Looking up a semantic class ID from a point's (x, y) coordinate.
    • Converting between geographic coordinates (lon/lat) and the point cloud's local decode space.
    • Looking up a semantic class ID directly from a lon/lat coordinate.
  • Robust Parsing: Gracefully skips unknown trailing chunks, ensuring forward compatibility with future extensions to the format.

Format Specification

The .hpc file format as implemented by this crate consists of a main HPC1 header and payload, followed by zero or more tagged chunks.

Main HPC1 Format

The file begins with a fixed-size 52-byte header, followed by the point data payload.

Offset Size (bytes) Type Field Description
0 4 [u8; 4] Magic Must be b"HPC1".
4 4 u32 (LE) Version Currently must be 1.
8 4 u32 (LE) Flags Bitfield. Bit 0 (1 << 0) indicates a TileKey is present.
12 4 u32 (LE) Count The number of points in the cloud.
16 1 u8 QBits Quantization bits per component. Currently must be 16.
17 11 [u8; 11] Reserved Used for TileKey payload if Flags bit 0 is set. Otherwise, zeroed.
28 12 [f32; 3] Decode Min The minimum [x, y, z] corner of the point cloud's bounding box.
40 12 [f32; 3] Decode Max The maximum [x, y, z] corner of the point cloud's bounding box.
52 Count * 6 [u16; N*3] Payload Quantized positions. Each point is (qx, qy, qz) as little-endian u16.

TileKey Payload

When Flags bit 0 is set, the 11-byte Reserved field is used to store a TileKey. The first byte of the reserved field acts as a type discriminator.

TileKey::XY (Type 0)

Offset in Reserved Size Type Description
0 1 u8 Type (0)
1 1 u8 Zoom level
2 4 u32 (LE) X coordinate
6 4 u32 (LE) Y coordinate
10 1 u8 Scheme

TileKey::NameHash64 (Type 4)

Offset in Reserved Size Type Description
0 1 u8 Type (4)
1 8 u64 (LE) 64-bit hash value
9 2 [u8; 2] Unused (zeroed)

Trailing Chunks

After the main HPC1 payload, any number of tagged chunks can appear. The format is designed to be extensible; unknown chunks are skipped using their provided length.

Each chunk follows a simple [TAG][LENGTH][PAYLOAD] structure:

  • Tag: A 4-byte ASCII identifier (e.g., b"SMC1", b"GEOT").
  • Length: A u32 (LE) specifying the size of the payload in bytes.
  • Payload: The chunk-specific data.

SMC1 Chunk (Semantic Mask)

The SMC1 chunk provides a 2D classification grid that maps onto the point cloud's XY plane.

Tag: b"SMC1"

Payload Layout (Version 1):

Field Type Description
Version u8 1 for the current version.
Encoding u8 0 for Raw (uncompressed), 1 for Zlib-compressed.
Width u16 (LE) Width of the mask grid in pixels.
Height u16 (LE) Height of the mask grid in pixels.
Coord Space u8 0 indicates the mask maps to the point cloud's decode XY space.
Class Count u8 Number of entries in the palette.
Reserved u16 (LE) Zeroed.
Palette [Entry] Class Count entries. Each entry is (class_id: u8, precedence: u8) followed by 2 reserved bytes.
Data Length u32 (LE) The length of the following data block in bytes.
Data [u8] The mask data (row-major), possibly zlib-compressed. After decompression, its size is Width * Height. Each byte is a class_id.

GEOT Chunk (Georeferencing)

The GEOT chunk provides a geographic bounding box for the point cloud, linking it to real-world coordinates.

Tag: b"GEOT"

Payload Layout (Version 1, CRS:84): This is a fixed-size 24-byte payload.

Field Type Description
Version u8 1.
CRS ID u8 1 for CRS:84 (WGS 84, lon/lat in degrees).
Mode u8 0 for BBOX_DEG_Q7 (bounding box in degrees with Q7 quantization).
Reserved u8 Zeroed.
Lon Min (Q7) i32 (LE) Minimum longitude quantized by 1e7. (lon_min * 1e7)
Lat Min (Q7) i32 (LE) Minimum latitude quantized by 1e7. (lat_min * 1e7)
Delta Lon (Q7) u32 (LE) Longitude extent quantized by 1e7. (lon_max - lon_min) * 1e7
Delta Lat (Q7) u32 (LE) Latitude extent quantized by 1e7. (lat_max - lat_min) * 1e7

Usage

Add to Your Project

Add hypc to your Cargo.toml:

[dependencies]
hypc = "0.1.0"

Reading an .hpc File

The easiest way to read a file is with hypc::read_file. This returns a HypcPointCloud struct containing all parsed data.

use hypc::HypcPointCloud;

fn main() -> anyhow::Result<()> {
    // Read the entire file into memory.
    let pc: HypcPointCloud = hypc::read_file("path/to/your/cloud.hpc")?;

    println!("Successfully read {} points.", pc.positions.len());

    // The point positions are dequantized and available as f32 triplets.
    if let Some(first_point) = pc.positions.first() {
        println!("First point position: {:?}", first_point);
    }

    // Check for optional TileKey metadata.
    if let Some(tile_key) = pc.tile_key {
        println!("TileKey present: {:?}", tile_key);
    }

    // Check for optional chunks.
    if pc.has_semantics() {
        let sm = pc.semantic_mask.as_ref().unwrap();
        println!(
            "Semantic mask found: {}x{} with {} classes.",
            sm.width,
            sm.height,
            sm.palette.len()
        );
    }

    if pc.has_geot() {
        let bbox = pc.geog_bbox_deg.as_ref().unwrap();
        println!(
            "Geographic BBox (CRS:84): lon({:.6}, {:.6}), lat({:.6}, {:.6})",
            bbox.lon_min, bbox.lon_max, bbox.lat_min, bbox.lat_max
        );
    }

    Ok(())
}

Using Semantic and Geographic Helpers

The real power of hypc comes from the high-level methods that combine data from different chunks. For example, you can find the semantic class of any geographic coordinate.

fn main() -> anyhow::Result<()> {
    let pc = hypc::read_file("path/to/your/georeferenced_and_classified_cloud.hpc")?;

    // We have a geographic coordinate in Dublin, Ireland.
    let lon_dublin = -6.2603;
    let lat_dublin = 53.3498;

    // The `class_of_lonlat` method automatically:
    // 1. Checks if a GEOT chunk exists.
    // 2. Converts the lon/lat to the point cloud's local decode XY space.
    // 3. Checks if an SMC1 chunk exists.
    // 4. Looks up the class ID in the semantic mask at the converted coordinate.
    // 5. Returns 0 if any step fails (e.g., no chunk, coordinate out of bounds).
    let class_id = pc.class_of_lonlat(lon_dublin, lat_dublin);

    if class_id != 0 {
        // You can map the class ID back to a meaningful name using the palette.
        let class_name = match class_id {
            1 => "Building",
            2 => "Vegetation",
            3 => "Water",
            _ => "Unknown",
        };
        println!(
            "The class at ({}, {}) is ID {} ({})",
            lon_dublin, lat_dublin, class_id, class_name
        );
    } else {
        println!(
            "No classification data available at ({}, {})",
            lon_dublin, lat_dublin
        );
    }

    Ok(())
}

Writing an .hpc File

To write a file, you construct a HypcWrite struct with all the necessary data and pass it to hypc::write_file.

Note that for writing, you must provide the positions in their quantized u16 form.

use hypc::{
    HypcWrite, SemanticMask, Smc1Encoding, GeoCrs, GeoExtentDeg, TileKey,
};

fn main() -> anyhow::Result<()> {
    // 1. Define point data. Positions must be quantized u16s.
    // This represents two points: (0, 32767, 65535) and (100, 200, 300).
    let quantized_positions: Vec<u16> = vec![0, 32767, 65535, 100, 200, 300];
    let decode_min = [0.0, 0.0, -10.0];
    let decode_max = [100.0, 100.0, 50.0];

    // 2. Define optional semantic mask data.
    let smc1 = SemanticMask {
        width: 2,
        height: 2,
        // Class IDs for a 2x2 grid.
        data: vec![1, 2, 1, 3], // Building, Vegetation, Building, Water
        palette: vec![(1, 10), (2, 20), (3, 30)], // (class_id, precedence)
        coord_space: 0,
        // Let the writer handle zlib compression for smaller file size.
        encoding: Smc1Encoding::Zlib,
    };

    // 3. Define optional georeferencing data.
    let geog_bbox_deg = GeoExtentDeg {
        lon_min: -6.26,
        lat_min: 53.34,
        lon_max: -6.25,
        lat_max: 53.35,
    };

    // 4. Define optional TileKey.
    let tile_key = TileKey::XY { zoom: 15, x: 16383, y: 10878, scheme: 0 };

    // 5. Assemble the write parameters.
    let params = HypcWrite {
        quant_bits: 16,
        quantized_positions: &quantized_positions,
        decode_min,
        decode_max,
        tile_key: Some(tile_key),
        geog_crs: Some(GeoCrs::Crs84),
        geog_bbox_deg: Some(geog_bbox_deg),
        smc1: Some(&smc1),
    };

    // 6. Write to file.
    hypc::write_file("path/to/output.hpc", &params)?;
    println!("Successfully wrote output.hpc");

    Ok(())
}

License

This project is licensed under the MIT License.