DTED Tile Codec¶
Version: 1.0
URI: https://awslabs.github.io/osml-imagery-io/codecs/dted
Codec type: array-to-bytes
Decodes DTED (Digital Terrain Elevation Data) file data sections into NumPy arrays. Performs signed-magnitude to two’s complement conversion, big-endian to native-endian byte swap, per-record header/checksum stripping, column-major to row-major transposition, and optional boundary-post trimming for seamless mosaicking.
DTED is an uncompressed, column-major elevation format defined by MIL-PRF-89020B. Each file contains a single 1-degree cell of signed 16-bit elevation posts. The data section consists of sequential longitude column records, each containing a sentinel byte, block/coordinate headers, elevation values, and a checksum. The codec operates on the data section only (byte offset 3428 onward).
Document Conventions¶
The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” in this document are to be interpreted as described in RFC 2119.
Codec Identifier¶
The value of the name member in the codec metadata MUST be
https://awslabs.github.io/osml-imagery-io/codecs/dted.
Encoded Representation¶
The encoded representation MUST be the data section of a DTED file as defined in
MIL-PRF-89020B Section 3.11. The byte sequence consists of num_lon_lines
sequential data records, each record_size bytes long. Each record contains:
1-byte data block recognition sentinel (
0xAA)3-byte sequential count
2-byte longitude count
2-byte latitude count
num_lat_pointsx 2 bytes of signed-magnitude big-endian elevation values4-byte checksum
The total byte length MUST equal num_lon_lines * record_size.
Rationale: Why DTED Requires a Specialized Codec¶
DTED data cannot be consumed as raw bytes by a standard Zarr codec for three reasons:
Record framing. The data section is not a flat array of elevation values. It consists of variable-length records, each wrapped with an 8-byte header (sentinel, block count, coordinate counts) and a 4-byte checksum. These must be stripped before the pixel data is usable.
Signed-magnitude encoding. DTED uses a non-standard numeric representation: elevations are stored as signed-magnitude big-endian 16-bit integers, not two’s complement. The high bit indicates sign; the remaining 15 bits are the absolute value. This requires explicit conversion to the two’s complement representation that NumPy and all modern systems expect.
Column-major storage with boundary overlap. Elevation posts are stored column-by-column (longitude-first), but Zarr arrays and NumPy use row-major (C) order. Additionally, adjacent 1-degree DTED cells share their boundary posts — the easternmost column of one cell is identical to the westernmost column of its neighbor. For Zarr’s non-overlapping chunk model to work, these shared edges must be trimmed during decode.
The codec handles all three transformations: stripping record framing, converting signed-magnitude to two’s complement, transposing to row-major order, and trimming shared boundary posts.
Multi-cell archives¶
Because trim_* removes the shared boundary posts at decode time, the codec
supports a multi-cell array: one chunk per DTED file, tiled into a single
seamless elevation surface with no duplicated edges and no preprocessing.
Two things are worth stating precisely, because the capability and the tooling are not the same thing:
This library ships no multi-cell producer. OversightMLParser indexes one
URL at a time (plus its .rN R-set companions), yielding a single-cell,
one-chunk array. Assembling an archive is a consumer-side task: emit one chunk
reference per cell into a Zarr store whose codecs chain carries this codec,
with trim_* set from each cell’s position in the grid. Cells absent from the
store — oceans, unreleased areas — resolve to the array’s fill_value, so a
sparse global array needs no file for every 1° cell.
One array covers one DTED latitude zone. Zarr declares codecs per array,
not per chunk, so every chunk in an array shares one configuration — including
num_lon_lines. MIL-PRF-89020B §3.9.2 fixes latitude spacing per DTED level but
makes longitude spacing depend on the geographic zone, so cell dimensions change
with latitude (MIL-PRF-89020B Table II, Level 1):
Zone |
Latitude |
Interval (lat × lon, arc-sec) |
|---|---|---|
I |
0°–50° |
3 × 3 |
II |
50°–70° |
3 × 6 |
III |
70°–75° |
3 × 9 |
IV |
75°–80° |
3 × 12 |
V |
80°–90° |
3 × 18 |
A single array therefore spans Zone I (0°–50°, either hemisphere) cleanly, and a
pole-to-pole mosaic needs one array per zone — a Zarr group of five — or a
resampling step to normalize spacing. record_size is unaffected: it derives from
num_lat_points, which is constant within a level.
The trimming model itself is spec-backed for any zone. MIL-PRF-89020B §3.5.1 defines cells as 1°×1° on whole-degree lines, and §3.5.2 requires that adjacent files have no gaps and that “the only overlap that exists is along adjacent boundaries. All adjacent boundaries shall be coincident.”
Configuration Parameters¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
No |
|
Number of elevation posts per column (row count). |
|
|
No |
|
Number of longitude columns (record count). |
|
|
No |
|
Size of each data record in bytes. |
|
|
No |
|
Rows to discard from the top of the decoded raster. |
|
|
No |
|
Rows to discard from the bottom. |
|
|
No |
|
Columns to discard from the left. |
|
|
No |
|
Columns to discard from the right. |
The defaults correspond to a DTED Level 1, Zone I cell (3-arcsecond spacing, 1201 x 1201 posts, record size = 8 + 1201x2 + 4 = 2414 bytes).
Algorithm¶
Decoding¶
Validate that the input data length equals
num_lon_lines * record_size.For each of the
num_lon_linesrecords: a. Skip the 8-byte header (sentinel + block/longitude/latitude counts). b. Readnum_lat_pointsx 2 bytes of signed-magnitude big-endian elevations. c. Skip the 4-byte checksum. d. Convert each 2-byte value from signed-magnitude to native two’s complement i16.Transpose the column-major data into row-major order.
Apply boundary trimming (if any trim parameters are non-zero).
Return an array with shape
(1, output_rows, output_cols)and dtypeint16, whereoutput_rows = num_lat_points - trim_top - trim_bottomandoutput_cols = num_lon_lines - trim_left - trim_right.
Encoding¶
Encoding is not currently specified. See Implementation Notes.
Overlap-Aware Edge Trimming¶
DTED cells are designed with inherent boundary-post overlap. Adjacent 1-degree cells share their edge row/column — the easternmost column of one cell is identical to the westernmost column of its neighbor. This ensures interpolation continuity but creates a problem for Zarr arrays, which partition coordinate space into non-overlapping chunks.
The trim_* parameters solve this at the codec level. By trimming one row and/or
column from each shared edge, the codec outputs non-overlapping chunks that tile
seamlessly. The Zarr manifest declares the trimmed output shape as the chunk
shape.
For example, trimming the east and south boundary posts of each cell:
{
"name": "https://awslabs.github.io/osml-imagery-io/codecs/dted",
"configuration": {
"num_lat_points": 1201,
"num_lon_lines": 1201,
"record_size": 2414,
"trim_top": 0,
"trim_bottom": 1,
"trim_left": 0,
"trim_right": 1
}
}
This decodes the full 1201x1201 cell and outputs a 1200x1200 array. Adjacent chunks tile perfectly with no duplication, enabling seamless global elevation arrays without any data preprocessing.
Example Configuration¶
Standard Level 1 cell (no trimming)¶
{
"name": "https://awslabs.github.io/osml-imagery-io/codecs/dted",
"configuration": {
"num_lat_points": 1201,
"num_lon_lines": 1201,
"record_size": 2414
}
}
Level 2 cell with boundary trimming for mosaicking¶
{
"name": "https://awslabs.github.io/osml-imagery-io/codecs/dted",
"configuration": {
"num_lat_points": 3601,
"num_lon_lines": 3601,
"record_size": 7214,
"trim_top": 0,
"trim_bottom": 1,
"trim_left": 0,
"trim_right": 1
}
}
References¶
MIL-PRF-89020B — Performance Specification: Digital Terrain Elevation Data (DTED)
RFC 2119 — Key words for use in RFCs to Indicate Requirement Levels
Implementation Notes¶
aws.osml.io.zarr_codecs.DtedTileCodec — see API Reference.
Only the decode path is implemented. Calling encode() raises NotImplementedError.