@@ -366,6 +366,7 @@ Block flag bits:
366366| ---:| ---| ---|
367367| ` 0 ` | ` has_leaf_directories ` | The block has leaf directories |
368368| ` 1 ` | ` has_dict ` | The block carries a per-block zstd dictionary |
369+ | ` 2 ` | ` raw_grid ` | Block stores a native source-grid raster instead of a Hilbert tile pyramid; see "Raw-Grid Blocks" below |
369370
370371Other bits are reserved.
371372
@@ -570,6 +571,103 @@ The payload is zstd-compressed vertical deltas. This codec is valid for `u8` and
570571- Each following row stores ` current - value_above ` .
571572- Arithmetic wraps modulo ` 2^(8 * dtype_bytes) ` .
572573
574+ ### Raw-Grid Blocks
575+
576+ Files encoded with ` --no-tiles ` skip the Web-Mercator pyramid. Each
577+ ` (variable_id, time_id) ` pair becomes one ** raw-grid block** that stores the
578+ native source lat-lon grid chunked into fixed-size sub-tiles. Point queries by
579+ ` (lat, lon) ` are O(1) range requests; the file does not drive a slippy-map
580+ viewer without on-the-fly resampling.
581+
582+ A raw-grid block reuses the 64-byte block header with ` BlockFlagRawGrid `
583+ (` 1 << 2 ` ) set in ` block_flags ` . Field interpretation changes:
584+
585+ - ` root_directory_offset ` and ` root_directory_length ` point at the compressed
586+ raw-grid section (the equivalent of the tile directory).
587+ - ` leaf_directories_offset ` and ` leaf_directories_length ` are zero.
588+ - ` tile_data_offset ` and ` tile_data_length ` describe the concatenated chunk
589+ payload region.
590+ - ` num_addressed_tiles ` and ` num_directory_entries ` hold the total chunk count
591+ (` chunk_count_x * chunk_count_y ` ).
592+ - ` num_tile_contents ` holds the deduplicated chunk count.
593+
594+ The block-table entry's ` codec ` is the dominant chunk codec ID for stats; each
595+ chunk payload still carries its own one-byte codec tag.
596+
597+ #### Raw-Grid Section
598+
599+ The raw-grid section is internally compressed (same compression as the file's
600+ catalogs and directories). After decompression:
601+
602+ | Offset | Size | Type | Field |
603+ | ---:| ---:| ---| ---|
604+ | ` 0 ` | 1 | ` u8 ` | ` schema_version ` , currently ` 1 ` |
605+ | ` 1 ` | 1 | ` u8 ` | ` chunk_size_log2 ` ; chunk side in source pixels = ` 1 << value ` |
606+ | ` 2 ` | 2 | bytes | Reserved, written as zero |
607+ | ` 4 ` | 4 | ` u32 ` | ` nx ` , source grid width in pixels |
608+ | ` 8 ` | 4 | ` u32 ` | ` ny ` , source grid height in pixels |
609+ | ` 16 ` | 8 | ` f64 ` | ` lat0 ` , latitude at source row ` 0 ` |
610+ | ` 24 ` | 8 | ` f64 ` | ` lon0 ` , longitude at source column ` 0 ` |
611+ | ` 32 ` | 8 | ` f64 ` | ` dy ` , latitude step per row (may be negative) |
612+ | ` 40 ` | 8 | ` f64 ` | ` dx ` , longitude step per column (may be negative) |
613+ | ` 48 ` | 8 | ` f64 ` | ` missing_value ` , source NoData sentinel (may be NaN) |
614+ | ` 56 ` | 4 | ` u32 ` | ` chunk_count_x ` , = ` ceil(nx / chunk_size) ` |
615+ | ` 60 ` | 4 | ` u32 ` | ` chunk_count_y ` , = ` ceil(ny / chunk_size) ` |
616+ | ` 64 ` | varint × N | LEB128 ` u64 ` each | ` chunk_offsets ` , one per chunk row-major |
617+ | ... | varint × N | LEB128 ` u64 ` each | ` chunk_lengths ` , one per chunk row-major |
618+
619+ ` chunk_size_log2 ` must be in ` [4, 12] ` , allowing chunk sides of 16..4096
620+ source pixels. ` N = chunk_count_x * chunk_count_y ` . Chunk index is
621+ ` cy * chunk_count_x + cx ` , so chunks are stored in row-major order.
622+
623+ ` chunk_offsets[i] ` is a byte offset relative to the block's ` tile_data_offset ` .
624+ ` chunk_lengths[i] ` is the chunk payload length. A ` (0, 0) ` pair signals
625+ ** absent chunk** ; decoders fill all pixels with NaN without fetching anything.
626+
627+ #### Chunk Payloads
628+
629+ Each chunk payload is a normal tile blob: one codec tag byte followed by the
630+ codec-specific stream. The current encoder uses ` 0x01 ` (constant) when all
631+ quantised values in the chunk are identical, otherwise ` 0x03 `
632+ (bitshuffle + zstd). Lorenzo and delta codecs are not emitted for raw-grid
633+ chunks because edge chunks at the right/bottom border may be non-square.
634+
635+ Chunk pixel count is ` chunk_width(cx) * chunk_height(cy) ` where:
636+
637+ ``` text
638+ chunk_width(cx) = min(chunk_size, nx - cx*chunk_size)
639+ chunk_height(cy) = min(chunk_size, ny - cy*chunk_size)
640+ ```
641+
642+ Pixels inside a chunk are row-major: ` chunk[row*chunk_width + col] ` is the
643+ quantised value at source coordinates ` (cx*chunk_size + col, cy*chunk_size + row) ` ,
644+ which corresponds to lat/lon ` (lat0 + (cy*chunk_size + row)*dy, lon0 + (cx*chunk_size + col)*dx) ` .
645+
646+ #### Point Sampling
647+
648+ To compute the value at ` (lat, lon) ` :
649+
650+ 1 . Compute source-grid coordinates ` gx = (lon - lon0) / dx ` ,
651+ ` gy = (lat - lat0) / dy ` . NaN or out-of-range inputs return NaN.
652+ 2 . Find the four neighbours ` (x0, y0) ` , ` (x1, y0) ` , ` (x0, y1) ` , ` (x1, y1) ` with
653+ ` x0 = floor(gx) ` , ` y0 = floor(gy) ` , ` x1 = x0+1 ` , ` y1 = y0+1 ` clamped to
654+ ` [0, nx-1] ` / ` [0, ny-1] ` .
655+ 3 . For each neighbour, locate the chunk ` cx = x / chunk_size ` ,
656+ ` cy = y / chunk_size ` , fetch and decode the chunk, then read the pixel.
657+ 4 . Bilinearly interpolate: ` wx = gx - x0 ` , ` wy = gy - y0 ` ,
658+ ` v = ((1-wx)*v00 + wx*v10)*(1-wy) + ((1-wx)*v01 + wx*v11)*wy ` .
659+ If any neighbour is NaN, the result is NaN.
660+
661+ For batched queries the reader unions the chunk indices touched by all points
662+ (including the 2×2 bilinear neighbourhood) and coalesces adjacent chunk byte
663+ ranges into shared HTTP-range requests, the same strategy as tile coalescing.
664+
665+ A raw-grid file's header records ` min_zoom = 0 ` , ` max_zoom = 0 ` , and the
666+ authoritative signal that consumers should branch on is ` BlockFlagRawGrid ` on
667+ each block header. Mixing tiled and raw-grid blocks in the same file is
668+ permitted by the wire format but the current encoder does not produce mixed
669+ files.
670+
573671### Lorenzo Zstd (` 0x05 ` )
574672
575673The payload is zstd-compressed 2D Lorenzo residuals. This codec is valid for
0 commit comments