use num_enum::TryFromPrimitive;
use crate::codecs::morton::{deinterleave_u64, interleave_u32};
use crate::codecs::presence_coding::PresenceCoding;
use crate::codecs::varint::parse_varint;
use crate::decoder::{ColumnStorage, GeometryType, ParsedMValue, ParsedNested, StreamMeta};
use crate::{
Analyze as _, DecodeState, Layer01, Lazy, LazyParsed, MValueColumn, MltError, MltRefResult,
MltResult, Nested, Parsed,
};
#[cfg(feature = "unstable-v2")]
#[derive(Debug, Clone)]
pub struct Layer02<'a, S: DecodeState = Lazy> {
pub(crate) layer: Layer01<'a, S>,
pub(crate) nested: Vec<Nested<'a, S>>,
pub(crate) m_values: Vec<MValueColumn<'a, S>>,
pub(crate) layout: LayerLayout,
}
#[cfg(feature = "unstable-v2")]
pub type ParsedLayer02<'a> = Layer02<'a, Parsed>;
#[cfg(feature = "unstable-v2")]
impl<'a, S: DecodeState> Layer02<'a, S> {
#[must_use]
pub fn layer(&self) -> &Layer01<'a, S> {
&self.layer
}
#[must_use]
pub fn into_layer(self) -> Layer01<'a, S> {
self.layer
}
#[must_use]
pub fn layout(&self) -> LayerLayout {
self.layout
}
}
#[cfg(feature = "unstable-v2")]
impl Layer02<'_, Lazy> {
pub fn for_each_stream(&self, cb: &mut dyn FnMut(StreamMeta)) {
self.layer.for_each_stream(cb);
for nested in &self.nested {
if let LazyParsed::Raw(raw) = nested {
raw.for_each_stream(cb);
}
}
for mvalue in &self.m_values {
if let LazyParsed::Raw(raw) = mvalue {
raw.for_each_stream(cb);
}
}
}
pub fn for_each_column_storage(&self, cb: &mut dyn FnMut(ColumnStorage)) {
self.layer.for_each_column_storage(cb);
for mvalue in &self.m_values {
if let LazyParsed::Raw(raw) = mvalue
&& let Some(storage) = raw.storage()
{
cb(storage);
}
}
for nested in &self.nested {
if let LazyParsed::Raw(raw) = nested {
raw.for_each_string_storage(cb);
}
}
}
}
#[cfg(feature = "unstable-v2")]
impl ParsedLayer02<'_> {
#[must_use]
pub fn m_values(&self) -> &[ParsedMValue<'_>] {
&self.m_values
}
#[must_use]
pub fn nested(&self) -> &[ParsedNested<'_>] {
&self.nested
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
#[repr(u8)]
pub(crate) enum DataType02 {
Id = 0x00,
LongId = 0x01,
Bool = 0x02,
I8 = 0x03,
U8 = 0x04,
I32 = 0x05,
U32 = 0x06,
I64 = 0x07,
U64 = 0x08,
F32 = 0x09,
F64 = 0x0A,
Str = 0x0B,
Struct = 0x0C,
List = 0x0D,
Map = 0x0E,
}
impl DataType02 {
#[must_use]
pub(crate) fn has_name(self) -> bool {
!matches!(self, Self::Id | Self::LongId)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Interior02 {
Struct,
List,
Map,
}
impl Interior02 {
#[must_use]
pub(crate) fn has_lengths(self) -> bool {
matches!(self, Self::List | Self::Map)
}
}
impl From<Interior02> for DataType02 {
fn from(interior: Interior02) -> Self {
match interior {
Interior02::Struct => Self::Struct,
Interior02::List => Self::List,
Interior02::Map => Self::Map,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NodePresence {
AllPresent,
Stream,
}
impl NodePresence {
const ALL_PRESENT: u8 = 0b0000_0000;
const STREAM: u8 = 0b0001_0000;
pub(crate) const SHAPES: u8 = 0b0100_0000;
#[must_use]
pub(crate) fn parse(nibble: u8) -> Option<Self> {
match nibble & !Self::SHAPES {
Self::ALL_PRESENT => Some(Self::AllPresent),
Self::STREAM => Some(Self::Stream),
_ => None,
}
}
#[must_use]
fn to_nibble(self) -> u8 {
match self {
Self::AllPresent => Self::ALL_PRESENT,
Self::Stream => Self::STREAM,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum NodeKind02 {
Leaf(ValueType02),
Struct,
List,
Map,
}
impl NodeKind02 {
#[must_use]
pub(crate) fn interior(self) -> Option<Interior02> {
match self {
Self::Leaf(_) => None,
Self::Struct => Some(Interior02::Struct),
Self::List => Some(Interior02::List),
Self::Map => Some(Interior02::Map),
}
}
}
impl From<NodeKind02> for DataType02 {
fn from(kind: NodeKind02) -> Self {
match kind {
NodeKind02::Leaf(values) => values.into(),
NodeKind02::Struct => Self::Struct,
NodeKind02::List => Self::List,
NodeKind02::Map => Self::Map,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct NodeType02 {
pub(crate) presence: NodePresence,
pub(crate) shapes: bool,
pub(crate) data: NodeKind02,
}
impl NodeType02 {
#[must_use]
pub(crate) fn new(presence: NodePresence, data: NodeKind02) -> Self {
Self {
presence,
shapes: false,
data,
}
}
#[must_use]
pub(crate) fn shaped(mut self) -> Self {
self.shapes = true;
self
}
pub(crate) fn parse(byte: u8) -> MltResult<Self> {
let err = || MltError::ParsingColumnType(byte);
let (nibble, data) = ColumnType02::fields(byte);
let presence = NodePresence::parse(nibble).ok_or_else(err)?;
let shapes = nibble & NodePresence::SHAPES != 0;
let data = DataType02::try_from(data).map_err(|_| err())?;
let data = match data {
DataType02::Id | DataType02::LongId => return Err(err()),
DataType02::Struct => NodeKind02::Struct,
DataType02::List => NodeKind02::List,
DataType02::Map => NodeKind02::Map,
DataType02::Bool => NodeKind02::Leaf(ValueType02::Bool),
DataType02::I8 => NodeKind02::Leaf(ValueType02::I8),
DataType02::U8 => NodeKind02::Leaf(ValueType02::U8),
DataType02::I32 => NodeKind02::Leaf(ValueType02::I32),
DataType02::U32 => NodeKind02::Leaf(ValueType02::U32),
DataType02::I64 => NodeKind02::Leaf(ValueType02::I64),
DataType02::U64 => NodeKind02::Leaf(ValueType02::U64),
DataType02::F32 => NodeKind02::Leaf(ValueType02::F32),
DataType02::F64 => NodeKind02::Leaf(ValueType02::F64),
DataType02::Str => NodeKind02::Leaf(ValueType02::Str),
};
Ok(Self {
presence,
shapes,
data,
})
}
#[must_use]
pub(crate) fn to_byte(self) -> u8 {
self.presence.to_nibble()
| if self.shapes { NodePresence::SHAPES } else { 0 }
| DataType02::from(self.data) as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueType02 {
Bool,
I8,
U8,
I32,
U32,
I64,
U64,
F32,
F64,
Str,
}
impl From<ValueType02> for DataType02 {
fn from(values: ValueType02) -> Self {
match values {
ValueType02::Bool => Self::Bool,
ValueType02::I8 => Self::I8,
ValueType02::U8 => Self::U8,
ValueType02::I32 => Self::I32,
ValueType02::U32 => Self::U32,
ValueType02::I64 => Self::I64,
ValueType02::U64 => Self::U64,
ValueType02::F32 => Self::F32,
ValueType02::F64 => Self::F64,
ValueType02::Str => Self::Str,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IdWidth02 {
Id32,
Id64,
}
impl From<IdWidth02> for DataType02 {
fn from(width: IdWidth02) -> Self {
match width {
IdWidth02::Id32 => Self::Id,
IdWidth02::Id64 => Self::LongId,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum SharedDictKind {
Plain = 0b0000_0000,
Fsst = 0b0001_0000,
}
impl SharedDictKind {
#[must_use]
pub(crate) fn parse(nibble: u8) -> Option<Self> {
match nibble {
0b0000_0000 => Some(Self::Plain),
0b0001_0000 => Some(Self::Fsst),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Presence02 {
AllPresent,
Inline(PresenceCoding),
Shared(u8),
}
impl Presence02 {
const ALL_PRESENT: u8 = 0b0000_0000;
const SHARED_BASE: u8 = 0b0100_0000;
#[must_use]
pub(crate) fn parse(nibble: u8, shared_count: u8) -> Option<Self> {
match nibble {
Self::ALL_PRESENT => Some(Self::AllPresent),
_ if nibble < Self::SHARED_BASE => {
PresenceCoding::from_byte(nibble >> 4).map(Self::Inline)
}
_ => {
let index = (nibble - Self::SHARED_BASE) >> 4;
(index < shared_count).then_some(Self::Shared(index))
}
}
}
#[must_use]
pub(crate) fn is_optional(self) -> bool {
!matches!(self, Self::AllPresent)
}
#[must_use]
fn to_nibble(self) -> u8 {
match self {
Self::AllPresent => Self::ALL_PRESENT,
Self::Inline(coding) => (coding as u8) << 4,
Self::Shared(index) => {
debug_assert!(index < LayerLayout::MAX_SHARED_PRESENCE);
Self::SHARED_BASE + (index << 4)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ColumnType02 {
pub(crate) presence: Presence02,
pub(crate) data: DataType02,
}
impl ColumnType02 {
pub(crate) const PRESENCE_MASK: u8 = 0b1111_0000;
pub(crate) const DATA_TYPE_MASK: u8 = 0b0000_1111;
#[must_use]
pub(crate) fn new(presence: Presence02, data: DataType02) -> Self {
Self { presence, data }
}
#[must_use]
pub(crate) fn fields(byte: u8) -> (u8, u8) {
(byte & Self::PRESENCE_MASK, byte & Self::DATA_TYPE_MASK)
}
pub(crate) fn parse(byte: u8, shared_count: u8) -> MltResult<Self> {
let err = || MltError::ParsingColumnType(byte);
let (presence, data) = Self::fields(byte);
let data = DataType02::try_from(data).map_err(|_| err())?;
let presence = Presence02::parse(presence, shared_count).ok_or_else(err)?;
Ok(Self { presence, data })
}
#[must_use]
pub(crate) fn split(self) -> ColumnKind02 {
let values = |values| {
ColumnKind02::Values(ValuesColumn02 {
presence: self.presence,
values,
})
};
match self.data {
DataType02::Id => ColumnKind02::Id(IdWidth02::Id32),
DataType02::LongId => ColumnKind02::Id(IdWidth02::Id64),
DataType02::Bool => values(ValueType02::Bool),
DataType02::I8 => values(ValueType02::I8),
DataType02::U8 => values(ValueType02::U8),
DataType02::I32 => values(ValueType02::I32),
DataType02::U32 => values(ValueType02::U32),
DataType02::I64 => values(ValueType02::I64),
DataType02::U64 => values(ValueType02::U64),
DataType02::F32 => values(ValueType02::F32),
DataType02::F64 => values(ValueType02::F64),
DataType02::Str => values(ValueType02::Str),
DataType02::Struct => ColumnKind02::Nested(NestedColumn02 {
presence: self.presence,
root: Interior02::Struct,
}),
DataType02::List => ColumnKind02::Nested(NestedColumn02 {
presence: self.presence,
root: Interior02::List,
}),
DataType02::Map => ColumnKind02::Nested(NestedColumn02 {
presence: self.presence,
root: Interior02::Map,
}),
}
}
#[must_use]
pub(crate) fn to_byte(self) -> u8 {
self.presence.to_nibble() | self.data as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ColumnKind02 {
Id(IdWidth02),
Values(ValuesColumn02),
Nested(NestedColumn02),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct NestedColumn02 {
pub(crate) presence: Presence02,
pub(crate) root: Interior02,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ValuesColumn02 {
pub(crate) presence: Presence02,
pub(crate) values: ValueType02,
}
impl ValuesColumn02 {
pub(crate) fn parse_m_value(byte: u8, shared_count: u8) -> MltResult<Self> {
match ColumnType02::parse(byte, shared_count)?.split() {
ColumnKind02::Values(column) => Ok(column),
ColumnKind02::Id(_) | ColumnKind02::Nested(_) => Err(MltError::ParsingColumnType(byte)),
}
}
}
impl From<ValuesColumn02> for ColumnType02 {
fn from(column: ValuesColumn02) -> Self {
Self::new(column.presence, column.values.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Column02 {
Values(ColumnType02),
SharedDict(SharedDictKind),
}
impl Column02 {
pub(crate) const SHARED_DICT: u8 = 0x0F;
pub(crate) fn parse(byte: u8, shared_count: u8) -> MltResult<Self> {
let err = || MltError::ParsingColumnType(byte);
let (high, data) = ColumnType02::fields(byte);
if data == Self::SHARED_DICT {
return SharedDictKind::parse(high)
.map(Self::SharedDict)
.ok_or_else(err);
}
ColumnType02::parse(byte, shared_count).map(Self::Values)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, strum::IntoStaticStr)]
#[repr(u8)]
pub enum GeoLayout {
Points = 0x00,
PointsDict = 0x01,
MultiPoints = 0x02,
MultiPointsDict = 0x03,
Lines = 0x04,
LinesDict = 0x05,
MultiLines = 0x06,
MultiLinesDict = 0x07,
Polygons = 0x08,
PolygonsDict = 0x09,
MultiPolygons = 0x0A,
MultiPolygonsDict = 0x0B,
TessPolygons = 0x0C,
TessPolygonsWithOutlines = 0x0D,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VertexStorage {
Plain,
Dict,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Topology {
Flat,
Parts(Vec<u32>),
PartsAndRings { parts: Vec<u32>, rings: Vec<u32> },
}
impl Topology {
pub(crate) fn streams(&self) -> (Option<&[u32]>, Option<&[u32]>) {
match self {
Self::Flat => (None, None),
Self::Parts(parts) => (Some(parts), None),
Self::PartsAndRings { parts, rings } => (Some(parts), Some(rings)),
}
}
#[must_use]
pub(crate) fn with_rings(self) -> Self {
match self {
Self::Flat => Self::PartsAndRings {
parts: Vec::new(),
rings: Vec::new(),
},
Self::Parts(parts) => Self::PartsAndRings {
parts,
rings: Vec::new(),
},
rings @ Self::PartsAndRings { .. } => rings,
}
}
}
impl GeoLayout {
#[must_use]
pub(crate) fn from_topology(
topology: &Topology,
has_geo: bool,
vertices: VertexStorage,
) -> Self {
use VertexStorage as V;
match (topology, has_geo, vertices) {
(Topology::Flat, false, V::Plain) => Self::Points,
(Topology::Flat, false, V::Dict) => Self::PointsDict,
(Topology::Flat, true, V::Plain) => Self::MultiPoints,
(Topology::Flat, true, V::Dict) => Self::MultiPointsDict,
(Topology::Parts(_), false, V::Plain) => Self::Lines,
(Topology::Parts(_), false, V::Dict) => Self::LinesDict,
(Topology::Parts(_), true, V::Plain) => Self::MultiLines,
(Topology::Parts(_), true, V::Dict) => Self::MultiLinesDict,
(Topology::PartsAndRings { .. }, false, V::Plain) => Self::Polygons,
(Topology::PartsAndRings { .. }, false, V::Dict) => Self::PolygonsDict,
(Topology::PartsAndRings { .. }, true, V::Plain) => Self::MultiPolygons,
(Topology::PartsAndRings { .. }, true, V::Dict) => Self::MultiPolygonsDict,
}
}
#[must_use]
pub(crate) fn tessellated(has_outlines: bool) -> Self {
if has_outlines {
Self::TessPolygonsWithOutlines
} else {
Self::TessPolygons
}
}
#[must_use]
pub(crate) fn has_geo_lengths(self) -> bool {
matches!(
self,
Self::MultiPoints
| Self::MultiPointsDict
| Self::MultiLines
| Self::MultiLinesDict
| Self::MultiPolygons
| Self::MultiPolygonsDict
| Self::TessPolygonsWithOutlines
)
}
#[must_use]
pub(crate) fn has_part_lengths(self) -> bool {
matches!(
self,
Self::Lines
| Self::LinesDict
| Self::MultiLines
| Self::MultiLinesDict
| Self::Polygons
| Self::PolygonsDict
| Self::MultiPolygons
| Self::MultiPolygonsDict
| Self::TessPolygonsWithOutlines
)
}
#[must_use]
pub(crate) fn has_ring_lengths(self) -> bool {
matches!(
self,
Self::Polygons
| Self::PolygonsDict
| Self::MultiPolygons
| Self::MultiPolygonsDict
| Self::TessPolygonsWithOutlines
)
}
#[must_use]
pub(crate) fn is_dict(self) -> bool {
matches!(
self,
Self::PointsDict
| Self::MultiPointsDict
| Self::LinesDict
| Self::MultiLinesDict
| Self::PolygonsDict
| Self::MultiPolygonsDict
)
}
#[must_use]
pub(crate) fn allows_m_values(self) -> bool {
!matches!(self, Self::Points | Self::PointsDict | Self::TessPolygons)
}
#[must_use]
pub(crate) fn is_tess(self) -> bool {
matches!(self, Self::TessPolygons | Self::TessPolygonsWithOutlines)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Extent02(u8);
impl Extent02 {
const MAX_EXPONENT: u8 = 0b0000_1111;
pub(crate) const EXPONENT_MASK: u8 = Self::MAX_EXPONENT;
const EXPONENT_OFFSET: u32 = 6;
pub(crate) fn new(extent: u32) -> MltResult<Self> {
if extent.is_power_of_two()
&& let Some(code) = extent.ilog2().checked_sub(Self::EXPONENT_OFFSET)
&& code <= u32::from(Self::MAX_EXPONENT)
{
#[expect(clippy::cast_possible_truncation, reason = "16 < 256")]
Ok(Self(code as u8))
} else {
Err(MltError::UnsupportedExtent02(extent))
}
}
#[must_use]
pub(crate) fn from_byte(byte: u8) -> Self {
Self(byte & Self::EXPONENT_MASK)
}
#[must_use]
pub(crate) fn get(self) -> u32 {
1 << (u32::from(self.0) + Self::EXPONENT_OFFSET)
}
#[must_use]
pub(crate) fn to_nibble(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct LayerHeader02 {
pub(crate) m_values: bool,
pub(crate) uniform_type: Option<GeometryType>,
pub(crate) extent: Extent02,
}
impl LayerHeader02 {
pub(crate) const M_VALUES_MASK: u8 = 0b1000_0000;
pub(crate) const UNIFORM_TYPE_MASK: u8 = 0b0111_0000;
const TYPES_STREAM: u8 = 0;
#[must_use]
pub(crate) fn new(
extent: Extent02,
uniform_type: Option<GeometryType>,
m_values: bool,
) -> Self {
Self {
m_values,
uniform_type,
extent,
}
}
#[must_use]
fn fields(byte: u8) -> (u8, u8, u8) {
(
byte & Self::M_VALUES_MASK,
(byte & Self::UNIFORM_TYPE_MASK) >> 4,
byte & Extent02::EXPONENT_MASK,
)
}
fn parse_uniform_type(byte: u8) -> MltResult<Option<GeometryType>> {
let (_, nibble, _) = Self::fields(byte);
if nibble == Self::TYPES_STREAM {
return Ok(None);
}
GeometryType::try_from(nibble - 1)
.map(Some)
.map_err(|_| MltError::ParsingLayerHeader02(byte))
}
pub(crate) fn parse(byte: u8) -> MltResult<Self> {
let (m_values, _, _) = Self::fields(byte);
Ok(Self {
m_values: m_values != 0,
uniform_type: Self::parse_uniform_type(byte)?,
extent: Extent02::from_byte(byte),
})
}
#[must_use]
pub(crate) fn to_byte(self) -> u8 {
let m_values = if self.m_values {
Self::M_VALUES_MASK
} else {
0
};
let uniform = self
.uniform_type
.map_or(Self::TYPES_STREAM, |t| t as u8 + 1);
m_values | (uniform << 4) | self.extent.to_nibble()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayerLayout {
pub shared_presence: u8,
pub shared_coded: bool,
pub geometry: GeoLayout,
}
impl LayerLayout {
pub(crate) const SHARED_CODED_BIT: u8 = 0b1000_0000;
pub(crate) const SHARED_PRESENCE_MASK: u8 = 0b0111_0000;
pub(crate) const GEO_LAYOUT_MASK: u8 = 0b0000_1111;
pub(crate) const MAX_SHARED_PRESENCE: u8 = 7;
#[must_use]
pub(crate) fn new(geometry: GeoLayout, shared_presence: u8, shared_coded: bool) -> Self {
debug_assert!(shared_presence <= Self::MAX_SHARED_PRESENCE);
debug_assert!(shared_presence > 0 || !shared_coded);
Self {
shared_presence,
shared_coded,
geometry,
}
}
#[must_use]
pub(crate) fn fields(byte: u8) -> (bool, u8, u8) {
(
byte & Self::SHARED_CODED_BIT != 0,
(byte & Self::SHARED_PRESENCE_MASK) >> 4,
byte & Self::GEO_LAYOUT_MASK,
)
}
pub(crate) fn parse(byte: u8) -> MltResult<Self> {
let (shared_coded, shared_presence, geometry) = Self::fields(byte);
if shared_coded && shared_presence == 0 {
return Err(MltError::ParsingLayerLayout02(byte));
}
let geometry =
GeoLayout::try_from(geometry).map_err(|_| MltError::ParsingGeoLayout(geometry))?;
Ok(Self {
shared_presence,
shared_coded,
geometry,
})
}
#[must_use]
pub(crate) fn to_byte(self) -> u8 {
debug_assert!(self.shared_presence <= Self::MAX_SHARED_PRESENCE);
let coded = if self.shared_coded {
Self::SHARED_CODED_BIT
} else {
0
};
coded | (self.shared_presence << 4) | self.geometry as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ColumnCounts {
pub(crate) columns: u32,
pub(crate) m_values: u32,
}
impl ColumnCounts {
pub(crate) fn parse(input: &[u8], m_values: bool) -> MltRefResult<'_, Self> {
if !m_values {
let (input, columns) = parse_varint::<u32>(input)?;
return Ok((
input,
Self {
columns,
m_values: 0,
},
));
}
let (input, code) = parse_varint::<u64>(input)?;
let (columns, m_values) = deinterleave_u64(code);
if m_values == 0 {
return Err(MltError::EmptyMValueSection);
}
Ok((input, Self { columns, m_values }))
}
#[must_use]
pub(crate) fn to_varint(self) -> u64 {
if self.m_values == 0 {
u64::from(self.columns)
} else {
interleave_u32(self.columns, self.m_values)
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
const ALL_SHARED: u8 = LayerLayout::MAX_SHARED_PRESENCE;
fn parts_and_rings() -> Topology {
Topology::PartsAndRings {
parts: vec![1],
rings: vec![4],
}
}
#[rstest]
#[case::id(0b0000_0000, Presence02::AllPresent, DataType02::Id)]
#[case::opt_id(
0b0001_0000,
Presence02::Inline(PresenceCoding::Bitmap),
DataType02::Id
)]
#[case::i32(0b0000_0101, Presence02::AllPresent, DataType02::I32)]
#[case::opt_f64(
0b0001_1010,
Presence02::Inline(PresenceCoding::Bitmap),
DataType02::F64
)]
#[case::runs(0b0010_0101, Presence02::Inline(PresenceCoding::Runs), DataType02::I32)]
#[case::indices(
0b0011_1010,
Presence02::Inline(PresenceCoding::Indices),
DataType02::F64
)]
#[case::str(0b0000_1011, Presence02::AllPresent, DataType02::Str)]
#[case::first_shared(0b0100_0101, Presence02::Shared(0), DataType02::I32)]
#[case::last_shared(0b1010_1010, Presence02::Shared(6), DataType02::F64)]
fn column_type_byte_roundtrip(
#[case] byte: u8,
#[case] presence: Presence02,
#[case] data: DataType02,
) {
let typ = ColumnType02::parse(byte, ALL_SHARED).unwrap();
assert_eq!(typ, ColumnType02::new(presence, data));
assert_eq!(typ.to_byte(), byte);
}
#[rstest]
#[case::values(
0b0001_0101,
Column02::Values(ColumnType02::new(
Presence02::Inline(PresenceCoding::Bitmap),
DataType02::I32
))
)]
#[case::plain_shared_dict(0b0000_1111, Column02::SharedDict(SharedDictKind::Plain))]
#[case::fsst_shared_dict(0b0001_1111, Column02::SharedDict(SharedDictKind::Fsst))]
fn column_byte_names_the_shape_it_holds(#[case] byte: u8, #[case] column: Column02) {
assert_eq!(Column02::parse(byte, ALL_SHARED).unwrap(), column);
}
#[rstest]
#[case::smallest(64, 0x00)]
#[case::mvt_default(4096, 0x06)]
#[case::wider_than_a_morton_code(65_536, 0x0A)]
#[case::largest(2_097_152, 0x0F)]
fn an_extent_round_trips_through_its_nibble(#[case] extent: u32, #[case] byte: u8) {
let coded = Extent02::new(extent).unwrap();
assert_eq!(coded.to_nibble(), byte);
assert_eq!(coded.get(), extent);
assert_eq!(Extent02::from_byte(byte), coded);
}
#[rstest]
#[case::zero(0)]
#[case::one(1)]
#[case::below_the_smallest(32)]
#[case::not_a_power_of_two(80)]
#[case::a_multiple_of_the_smallest(192)]
#[case::a_power_of_two_plus_a_multiple_of_the_smallest(33_024)]
#[case::just_over_the_largest(2_097_153)]
#[case::a_power_of_two_over_the_largest(4_194_304)]
fn an_extent_outside_the_nibble_is_rejected(#[case] extent: u32) {
let err = Extent02::new(extent).unwrap_err();
assert!(matches!(err, MltError::UnsupportedExtent02(e) if e == extent));
}
#[rstest]
#[case::extent_only(0x06, false, None, 4096)]
#[case::m_values(0x86, true, None, 4096)]
#[case::uniform_points(0x16, false, Some(GeometryType::Point), 4096)]
#[case::uniform_multi_polygons(0x60, false, Some(GeometryType::MultiPolygon), 64)]
#[case::m_values_over_a_uniform_type(0xCF, true, Some(GeometryType::MultiPoint), 2_097_152)]
fn layer_header_byte_roundtrip(
#[case] byte: u8,
#[case] m_values: bool,
#[case] uniform_type: Option<GeometryType>,
#[case] extent: u32,
) {
let header = LayerHeader02::parse(byte).unwrap();
assert_eq!(
header,
LayerHeader02::new(Extent02::new(extent).unwrap(), uniform_type, m_values)
);
assert_eq!(header.extent.get(), extent);
assert_eq!(header.to_byte(), byte);
}
#[rstest]
#[case::reserved_uniform_type(0x70)]
#[case::reserved_uniform_type_over_an_extent(0x7F)]
#[case::reserved_uniform_type_with_m_values(0xF6)]
fn layer_header_byte_rejects_the_reserved_uniform_type(#[case] byte: u8) {
let err = LayerHeader02::parse(byte).unwrap_err();
assert!(matches!(err, MltError::ParsingLayerHeader02(b) if b == byte));
}
#[rstest]
#[case::reserved_shared_dict_corpus(0b0010_1111, ALL_SHARED)]
#[case::shared_ref_past_declared_count_over_a_nested_root(0b1001_1100, 4)]
fn column_byte_rejects_unassigned(#[case] byte: u8, #[case] shared_count: u8) {
let err = Column02::parse(byte, shared_count).unwrap_err();
assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
}
#[rstest]
#[case::shared_dict_is_not_a_data_type(0b0000_1111, ALL_SHARED)]
#[case::shared_ref_past_declared_count_over_a_nested_root(0b1001_1100, 4)]
#[case::shared_ref_past_declared_count(0b1001_0101, 3)]
#[case::shared_ref_one_past_the_last_bitfield(0b1011_0101, ALL_SHARED)]
#[case::reserved_shared_nibble(0b1111_0101, ALL_SHARED)]
#[case::shared_ref_without_shared_columns(0b0100_0101, 0)]
#[case::shared_ref_past_declared_count(0b0101_0101, 1)]
#[case::unassigned_inline_coding(0b0101_0101, 0)]
fn column_type_byte_rejects_unassigned(#[case] byte: u8, #[case] shared_count: u8) {
let err = ColumnType02::parse(byte, shared_count).unwrap_err();
assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
}
#[rstest]
#[case::points(Topology::Flat, false, VertexStorage::Plain, GeoLayout::Points)]
#[case::points_dict(Topology::Flat, false, VertexStorage::Dict, GeoLayout::PointsDict)]
#[case::multi_points(Topology::Flat, true, VertexStorage::Plain, GeoLayout::MultiPoints)]
#[case::lines_dict(Topology::Parts(vec![2]), false, VertexStorage::Dict, GeoLayout::LinesDict)]
#[case::multi_lines(Topology::Parts(vec![2]), true, VertexStorage::Plain, GeoLayout::MultiLines)]
#[case::polygons(parts_and_rings(), false, VertexStorage::Plain, GeoLayout::Polygons)]
#[case::multi_polygons_dict(
parts_and_rings(),
true,
VertexStorage::Dict,
GeoLayout::MultiPolygonsDict
)]
fn a_stream_set_picks_its_layout(
#[case] topology: Topology,
#[case] has_geo: bool,
#[case] vertices: VertexStorage,
#[case] expected: GeoLayout,
) {
let layout = GeoLayout::from_topology(&topology, has_geo, vertices);
let (parts, rings) = topology.streams();
assert_eq!(layout, expected);
assert_eq!(layout.has_geo_lengths(), has_geo);
assert_eq!(layout.has_part_lengths(), parts.is_some());
assert_eq!(layout.has_ring_lengths(), rings.is_some());
assert_eq!(layout.is_dict(), vertices == VertexStorage::Dict);
assert!(!layout.is_tess());
}
#[rstest]
#[case::tess(false, GeoLayout::TessPolygons)]
#[case::tess_with_outlines(true, GeoLayout::TessPolygonsWithOutlines)]
fn a_tessellated_stream_set_picks_its_layout(
#[case] has_outlines: bool,
#[case] expected: GeoLayout,
) {
let layout = GeoLayout::tessellated(has_outlines);
assert_eq!(layout, expected);
assert!(layout.is_tess());
assert!(!layout.is_dict());
assert_eq!(layout.has_geo_lengths(), has_outlines);
assert_eq!(layout.has_part_lengths(), has_outlines);
assert_eq!(layout.has_ring_lengths(), has_outlines);
}
#[rstest]
#[case::flat(Topology::Flat)]
#[case::parts(Topology::Parts(vec![2]))]
#[case::parts_and_rings(parts_and_rings())]
fn a_topology_widened_to_rings_keeps_both_streams(#[case] topology: Topology) {
let widened = topology.with_rings();
let (parts, rings) = widened.streams();
assert!(parts.is_some() && rings.is_some());
}
#[rstest]
#[case::points(0b0000_0000, 0, false, GeoLayout::Points)]
#[case::multi_polygons(0b0000_1010, 0, false, GeoLayout::MultiPolygons)]
#[case::one_shared_presence(0b0001_0100, 1, false, GeoLayout::Lines)]
#[case::max_shared_presence(0b0111_0000, ALL_SHARED, false, GeoLayout::Points)]
#[case::one_coded_shared_presence(0b1001_0100, 1, true, GeoLayout::Lines)]
#[case::max_coded_shared_presence(0b1111_0110, ALL_SHARED, true, GeoLayout::MultiLines)]
fn layer_layout_byte_roundtrip(
#[case] byte: u8,
#[case] shared_presence: u8,
#[case] shared_coded: bool,
#[case] geometry: GeoLayout,
) {
let layout = LayerLayout::parse(byte).unwrap();
assert_eq!(
layout,
LayerLayout::new(geometry, shared_presence, shared_coded)
);
assert_eq!(layout.to_byte(), byte);
}
#[rstest]
#[case::no_bitfield(0b1000_0000)]
#[case::no_bitfield_over_a_geo_layout(0b1000_1010)]
fn layer_layout_byte_rejects_a_coding_flag_with_nothing_to_code(#[case] byte: u8) {
let err = LayerLayout::parse(byte).unwrap_err();
assert!(matches!(err, MltError::ParsingLayerLayout02(b) if b == byte));
}
#[rstest]
#[case::bool(0b0000_0010, ValueType02::Bool)]
#[case::opt_i32(0b0001_0101, ValueType02::I32)]
#[case::shared_f64(0b0100_1010, ValueType02::F64)]
#[case::str(0b0000_1011, ValueType02::Str)]
fn m_value_type_byte_roundtrip(#[case] byte: u8, #[case] values: ValueType02) {
let column = ValuesColumn02::parse_m_value(byte, ALL_SHARED).unwrap();
assert_eq!(column.values, values);
assert_eq!(ColumnType02::from(column).to_byte(), byte);
}
#[rstest]
#[case::id(0b0000_0000)]
#[case::opt_id(0b0001_0000)]
#[case::long_id(0b0000_0001)]
#[case::shared_dict(0b0000_1111)]
#[case::struct_root(0b0000_1100)]
#[case::list_root(0b0000_1101)]
#[case::map_root(0b0000_1110)]
fn m_value_type_byte_rejects_what_a_vertex_cannot_hold(#[case] byte: u8) {
let err = ValuesColumn02::parse_m_value(byte, ALL_SHARED).unwrap_err();
assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
}
#[test]
fn an_m_value_column_cannot_name_a_bitfield_the_layer_lacks() {
let byte = 0b1001_0101;
let err = ValuesColumn02::parse_m_value(byte, 4).unwrap_err();
assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
}
#[rstest]
#[case::points(GeoLayout::Points, false)]
#[case::points_dict(GeoLayout::PointsDict, false)]
#[case::multi_points(GeoLayout::MultiPoints, true)]
#[case::lines(GeoLayout::Lines, true)]
#[case::multi_polygons_dict(GeoLayout::MultiPolygonsDict, true)]
#[case::tess_polygons(GeoLayout::TessPolygons, false)]
#[case::tess_polygons_with_outlines(GeoLayout::TessPolygonsWithOutlines, true)]
fn only_a_layout_with_per_feature_vertex_counts_takes_m_values(
#[case] layout: GeoLayout,
#[case] allowed: bool,
) {
assert_eq!(layout.allows_m_values(), allowed);
}
#[rstest]
#[case::i32_leaf(
0b0000_0101,
NodePresence::AllPresent,
NodeKind02::Leaf(ValueType02::I32)
)]
#[case::optional_str_leaf(
0b0001_1011,
NodePresence::Stream,
NodeKind02::Leaf(ValueType02::Str)
)]
#[case::struct_node(0b0000_1100, NodePresence::AllPresent, NodeKind02::Struct)]
#[case::optional_list_node(0b0001_1101, NodePresence::Stream, NodeKind02::List)]
#[case::map_node(0b0000_1110, NodePresence::AllPresent, NodeKind02::Map)]
fn node_type_byte_roundtrip(
#[case] byte: u8,
#[case] presence: NodePresence,
#[case] data: NodeKind02,
) {
let typ = NodeType02::parse(byte).unwrap();
assert_eq!(typ, NodeType02::new(presence, data));
assert_eq!(typ.to_byte(), byte);
}
#[rstest]
#[case::shaped_struct(0b0100_1100, NodePresence::AllPresent, NodeKind02::Struct)]
#[case::shaped_map_over_nulls(0b0101_1110, NodePresence::Stream, NodeKind02::Map)]
fn the_shapes_bit_reads_beside_a_nodes_own_presence(
#[case] byte: u8,
#[case] presence: NodePresence,
#[case] data: NodeKind02,
) {
let typ = NodeType02::parse(byte).unwrap();
assert_eq!(typ, NodeType02::new(presence, data).shaped());
assert!(typ.shapes);
assert_eq!(typ.to_byte(), byte);
}
#[rstest]
#[case::id_is_a_features_own(0b0000_0000)]
#[case::long_id_is_a_features_own(0b0000_0001)]
#[case::shared_dict_introduces_columns(0b0000_1111)]
#[case::reserved_node_presence(0b0010_0101)]
#[case::reserved_node_presence_top(0b1111_0101)]
fn node_type_byte_rejects_what_a_node_cannot_hold(#[case] byte: u8) {
let err = NodeType02::parse(byte).unwrap_err();
assert!(matches!(err, MltError::ParsingColumnType(b) if b == byte));
}
#[rstest]
#[case::struct_root(0b0000_1100, Interior02::Struct)]
#[case::list_root(0b0001_1101, Interior02::List)]
#[case::map_root(0b0010_1110, Interior02::Map)]
fn a_nested_root_reads_as_the_interior_its_nibble_names(
#[case] byte: u8,
#[case] root: Interior02,
) {
let typ = ColumnType02::parse(byte, ALL_SHARED).unwrap();
let ColumnKind02::Nested(column) = typ.split() else {
panic!("expected a nested root")
};
assert_eq!(column.root, root);
assert_eq!(typ.to_byte(), byte);
}
fn varint(value: u64) -> Vec<u8> {
let mut buf = [0u8; 10];
let written = integer_encoding::VarInt::encode_var(value, &mut buf);
buf[..written].to_vec()
}
#[rstest]
#[case::no_m_values(false, 0, 0, &[0x00])]
#[case::three_columns(false, 3, 0, &[0x03])]
#[case::one_m_value(true, 0, 1, &[0x02])]
#[case::three_columns_two_m_values(true, 3, 2, &[0b0000_1101])]
#[case::one_byte_limits(true, 15, 7, &[0x7F])]
#[case::sixteen_columns_spill_over(true, 16, 1, &[0x82, 0x02])]
#[case::eight_m_values_spill_over(true, 0, 8, &[0x80, 0x01])]
fn column_counts_roundtrip(
#[case] m_values: bool,
#[case] columns: u32,
#[case] m_value_columns: u32,
#[case] bytes: &[u8],
) {
let counts = ColumnCounts {
columns,
m_values: m_value_columns,
};
assert_eq!(varint(counts.to_varint()), bytes);
assert_eq!(
ColumnCounts::parse(bytes, m_values).unwrap(),
(&[][..], counts)
);
}
#[rstest]
#[case::no_columns(&[0x00])]
#[case::columns_only(&[0x05])]
fn column_counts_reject_a_flagged_section_without_m_values(#[case] bytes: &[u8]) {
let err = ColumnCounts::parse(bytes, true).unwrap_err();
assert!(matches!(err, MltError::EmptyMValueSection));
}
#[rstest]
#[case::unassigned_geo_layout(0b0000_1110)]
#[case::unassigned_geo_layout_with_shared(0b0010_1111)]
fn layer_layout_byte_rejects_unassigned_geo_layout(#[case] byte: u8) {
let err = LayerLayout::parse(byte).unwrap_err();
assert!(
matches!(err, MltError::ParsingGeoLayout(b) if b == byte & LayerLayout::GEO_LAYOUT_MASK)
);
}
}