#![expect(
clippy::cast_sign_loss,
reason = "ShapeId/EdgeId (i32) encoded as u64 — values always non-negative at encoding time"
)]
#![expect(
clippy::cast_possible_truncation,
reason = "u64 varint -> i32/usize — values fit by format spec"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "u64 -> i32 for decoded EdgeId/ShapeId — bounded by format"
)]
use std::io::{self, Cursor, Read, Write};
use crate::s2::cell_id::CellId;
use crate::s2::encoded_s2cell_id_vector;
use crate::s2::encoded_s2point_vector::CodingHint;
use crate::s2::encoded_string_vector::{self, StringVectorBuilder};
use crate::s2::encoding::{S2Decode, read_uvarint, write_uvarint};
use crate::s2::lax_polygon::LaxPolygon;
use crate::s2::lax_polyline::LaxPolyline;
use crate::s2::point_vector::PointVector;
use crate::s2::polygon::Polygon;
use crate::s2::polyline::Polyline;
use crate::s2::shape::Shape;
use crate::s2::shape::ShapeId;
use crate::s2::shape_index::{ClippedShape, ShapeIndex, ShapeIndexCell};
const SHAPE_INDEX_VERSION: u64 = 2;
const MAX_PREALLOC: usize = 1 << 16;
const NO_TYPE_TAG: u32 = 0;
const POLYGON_TYPE_TAG: u32 = 1;
const POLYLINE_TYPE_TAG: u32 = 2;
const POINT_VECTOR_TYPE_TAG: u32 = 3;
const LAX_POLYLINE_TYPE_TAG: u32 = 4;
const LAX_POLYGON_TYPE_TAG: u32 = 5;
fn shape_type_tag(shape: &dyn Shape) -> u32 {
shape.type_tag()
}
fn encode_tagged_shape(shape: &dyn Shape, hint: CodingHint, w: &mut dyn Write) -> io::Result<()> {
let tag = shape_type_tag(shape);
if tag == NO_TYPE_TAG {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"shape has no type tag, cannot encode (dimension={})",
shape.dimension()
),
));
}
write_uvarint(w, u64::from(tag))?;
shape.encode_tagged(w, hint)
}
fn decode_tagged_shape(data: &[u8]) -> io::Result<Box<dyn Shape>> {
let mut r = Cursor::new(data);
let tag = read_uvarint(&mut r)? as u32;
match tag {
POLYGON_TYPE_TAG => {
let polygon = Polygon::decode(&mut r)?;
Ok(Box::new(polygon))
}
POLYLINE_TYPE_TAG => {
let polyline = Polyline::decode(&mut r)?;
Ok(Box::new(polyline))
}
POINT_VECTOR_TYPE_TAG => {
let pv = PointVector::decode(&mut r)?;
Ok(Box::new(pv))
}
LAX_POLYLINE_TYPE_TAG => {
let lp = LaxPolyline::decode(&mut r)?;
Ok(Box::new(lp))
}
LAX_POLYGON_TYPE_TAG => {
let lp = LaxPolygon::decode(&mut r)?;
Ok(Box::new(lp))
}
_ => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unsupported shape type tag {tag}"),
)),
}
}
fn encode_cell(cell: &ShapeIndexCell, num_shape_ids: usize, w: &mut dyn Write) -> io::Result<()> {
if num_shape_ids == 1 {
debug_assert_eq!(cell.shapes.len(), 1);
let clipped = &cell.shapes[0];
debug_assert_eq!(clipped.shape_id, 0);
let n = clipped.num_edges();
let cc = u64::from(clipped.contains_center);
if (2..=17).contains(&n) && clipped.edges[n - 1] - clipped.edges[0] == (n as i32 - 1) {
let edge_id = clipped.edges[0] as u64;
let val = (edge_id << 6) | ((n as u64 - 2) << 2) | (cc << 1);
write_uvarint(w, val)?;
} else if n == 1 {
let edge_id = clipped.edges[0] as u64;
write_uvarint(w, (edge_id << 3) | (cc << 2) | 1)?;
} else {
write_uvarint(w, (n as u64) << 3 | (cc << 2) | 3)?;
encode_edges(clipped, w)?;
}
} else {
if cell.shapes.len() > 1 {
write_uvarint(w, ((cell.shapes.len() as u64) << 3) | 3)?;
}
let mut shape_id_base = 0i32;
for clipped in &cell.shapes {
debug_assert!(clipped.shape_id.0 >= shape_id_base);
let shape_delta = (clipped.shape_id.0 - shape_id_base) as u64;
shape_id_base = clipped.shape_id.0 + 1;
let n = clipped.num_edges();
let cc = u64::from(clipped.contains_center);
if (1..=16).contains(&n) && clipped.edges[n - 1] - clipped.edges[0] == (n as i32 - 1) {
let edge_id = clipped.edges[0] as u64;
write_uvarint(w, (edge_id << 2) | (cc << 1))?;
write_uvarint(w, (shape_delta << 4) | (n as u64 - 1))?;
} else if n == 0 {
write_uvarint(w, (shape_delta << 4) | (cc << 3) | 7)?;
} else {
write_uvarint(w, ((n as u64 - 1) << 3) | (cc << 2) | 1)?;
write_uvarint(w, shape_delta)?;
encode_edges(clipped, w)?;
}
}
}
Ok(())
}
fn encode_edges(clipped: &ClippedShape, w: &mut dyn Write) -> io::Result<()> {
let mut prev = 0i32;
for &edge in &clipped.edges {
write_uvarint(w, (edge - prev) as u64)?;
prev = edge + 1;
}
Ok(())
}
fn decode_cell(data: &[u8], num_shape_ids: usize) -> io::Result<ShapeIndexCell> {
let mut r = Cursor::new(data);
let mut cell = ShapeIndexCell::default();
if num_shape_ids == 1 {
let header = read_uvarint(&mut r)?;
let mut clipped = ClippedShape {
shape_id: ShapeId(0),
contains_center: false,
edges: Vec::new(),
};
if (header & 1) == 0 {
let num_edges = ((header >> 2) & 15) as usize + 2;
clipped.contains_center = (header & 2) != 0;
let edge_id = (header >> 6) as i32;
clipped.edges = (edge_id..edge_id + num_edges as i32).collect();
} else if (header & 2) == 0 {
clipped.contains_center = (header & 4) != 0;
let edge_id = (header >> 3) as i32;
clipped.edges = vec![edge_id];
} else {
clipped.contains_center = (header & 4) != 0;
let num_edges = (header >> 3) as usize;
clipped.edges = decode_edges(num_edges, &mut r)?;
}
cell.shapes.push(clipped);
} else {
let first_byte = if data.is_empty() {
return Err(io::Error::new(io::ErrorKind::InvalidData, "empty cell"));
} else {
data[0]
};
let num_clipped = if (first_byte & 7) == 3 {
let header = read_uvarint(&mut r)?;
let n = (header >> 3) as usize;
if n <= 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"multi-shape header with num_clipped <= 1",
));
}
n
} else {
1
};
let mut shape_id_base = 0i32;
for _ in 0..num_clipped {
let header = read_uvarint(&mut r)?;
let mut clipped = ClippedShape {
shape_id: ShapeId(0),
contains_center: false,
edges: Vec::new(),
};
if (header & 1) == 0 {
let edge_id = (header >> 2) as i32;
clipped.contains_center = (header & 2) != 0;
let next_val = read_uvarint(&mut r)?;
let num_edges = ((next_val & 15) + 1) as usize;
let shape_delta = (next_val >> 4) as i32;
clipped.shape_id = ShapeId(add_i32(shape_id_base, shape_delta)?);
clipped.edges = (edge_id..add_i32(edge_id, num_edges as i32)?).collect();
} else if (header & 7) == 7 {
clipped.contains_center = (header & 8) != 0;
let shape_delta = (header >> 4) as i32;
clipped.shape_id = ShapeId(add_i32(shape_id_base, shape_delta)?);
} else if (header & 3) == 1 {
let num_edges = ((header >> 3) + 1) as usize;
clipped.contains_center = (header & 4) != 0;
let shape_delta = read_uvarint(&mut r)? as i32;
clipped.shape_id = ShapeId(add_i32(shape_id_base, shape_delta)?);
clipped.edges = decode_edges(num_edges, &mut r)?;
} else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid cell encoding tag: {header:#x}"),
));
}
shape_id_base = add_i32(clipped.shape_id.0, 1)?;
cell.shapes.push(clipped);
}
}
Ok(cell)
}
fn add_i32(a: i32, b: i32) -> io::Result<i32> {
a.checked_add(b).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"integer overflow in cell decode",
)
})
}
fn decode_edges(num_edges: usize, r: &mut dyn Read) -> io::Result<Vec<i32>> {
let mut edges = Vec::with_capacity(num_edges.min(MAX_PREALLOC));
let mut prev = 0i32;
for _ in 0..num_edges {
let delta = read_uvarint(r)? as i32;
let edge = add_i32(prev, delta)?;
edges.push(edge);
prev = add_i32(edge, 1)?;
}
Ok(edges)
}
impl ShapeIndex {
pub fn encode_to_writer(&self, w: &mut dyn Write) -> io::Result<()> {
self.encode_with_hint(w, CodingHint::Fast)
}
pub fn encode_with_hint(&self, w: &mut dyn Write, hint: CodingHint) -> io::Result<()> {
let max_edges = self.max_edges_per_cell() as u64;
write_uvarint(w, (max_edges << 2) | SHAPE_INDEX_VERSION)?;
let mut shape_vec = StringVectorBuilder::new();
for shape_opt in self.shapes_iter() {
let mut shape_buf = Vec::new();
if let Some(shape) = shape_opt {
encode_tagged_shape(shape.as_ref(), hint, &mut shape_buf)?;
}
shape_vec.add(shape_buf);
}
shape_vec.encode(w)?;
let mut cell_ids = Vec::new();
let mut encoded_cells = StringVectorBuilder::new();
let num_shape_ids = self.num_shape_ids();
for it in self.cell_iter() {
cell_ids.push(it.cell_id());
if let Some(cell) = it.index_cell() {
let mut cell_buf = Vec::new();
encode_cell(cell, num_shape_ids, &mut cell_buf)?;
encoded_cells.add(cell_buf);
}
}
encoded_s2cell_id_vector::encode_s2cell_id_vector(&cell_ids, w)?;
encoded_cells.encode(w)?;
Ok(())
}
pub fn decode_from_reader(r: &mut dyn Read) -> io::Result<Self> {
let max_edges_version = read_uvarint(r)?;
let version = max_edges_version & 3;
if version != SHAPE_INDEX_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unsupported ShapeIndex version {version}"),
));
}
let max_edges_per_cell = (max_edges_version >> 2) as usize;
let shape_data = encoded_string_vector::decode_string_vector(r)?;
let num_shapes = shape_data.len();
let mut shapes: Vec<Option<Box<dyn Shape>>> = Vec::with_capacity(num_shapes);
for data in &shape_data {
if data.is_empty() {
shapes.push(None);
} else {
shapes.push(Some(decode_tagged_shape(data)?));
}
}
let cell_ids = encoded_s2cell_id_vector::decode_s2cell_id_vector(r)?;
let mut prev: Option<CellId> = None;
for &cid in &cell_ids {
if !cid.is_valid() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"shape index contains an invalid cell id",
));
}
if prev.is_some_and(|p| cid <= p) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"shape index cell ids must be strictly increasing",
));
}
prev = Some(cid);
}
let cell_data = encoded_string_vector::decode_string_vector(r)?;
if cell_ids.len() != cell_data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"cell_ids and cell_data length mismatch",
));
}
let mut index = ShapeIndex::new();
index.set_max_edges_per_cell(max_edges_per_cell);
for shape in shapes {
index.add_option(shape);
}
for (i, cid) in cell_ids.iter().enumerate() {
let cell = decode_cell(&cell_data[i], num_shapes)?;
for clipped in &cell.shapes {
let shape_id = clipped.shape_id.0;
if shape_id < 0 || shape_id as usize >= num_shapes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"shape index cell references an out-of-range shape id",
));
}
let num_edges = index.shape(clipped.shape_id).map_or(0, Shape::num_edges);
if clipped
.edges
.iter()
.any(|&e| e < 0 || (e as usize) >= num_edges)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"shape index cell references an out-of-range edge id",
));
}
}
index.insert_cell(*cid, cell);
}
index.mark_built();
Ok(index)
}
fn cell_iter(&self) -> CellIter<'_> {
CellIter { inner: self.iter() }
}
fn shapes_iter(&self) -> &[Option<Box<dyn Shape>>] {
self.shapes_slice()
}
}
struct CellIter<'a> {
inner: crate::s2::shape_index::ShapeIndexIterator<'a>,
}
impl<'a> Iterator for CellIter<'a> {
type Item = CellIterItem<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.inner.done() {
return None;
}
let item = CellIterItem {
cell_id: self.inner.cell_id(),
cell: self.inner.index_cell(),
};
self.inner.next();
Some(item)
}
}
struct CellIterItem<'a> {
cell_id: CellId,
cell: Option<&'a ShapeIndexCell>,
}
impl<'a> CellIterItem<'a> {
fn cell_id(&self) -> CellId {
self.cell_id
}
fn index_cell(&self) -> Option<&'a ShapeIndexCell> {
self.cell
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::s2::LatLng;
use crate::s2::encoding::{S2Decode, S2Encode};
fn roundtrip_index(index: &ShapeIndex) -> ShapeIndex {
let mut buf = Vec::new();
index.encode_to_writer(&mut buf).unwrap();
let mut cursor = Cursor::new(buf);
ShapeIndex::decode_from_reader(&mut cursor).unwrap()
}
fn assert_indices_equal(a: &ShapeIndex, b: &ShapeIndex) {
assert_eq!(a.num_shape_ids(), b.num_shape_ids());
let mut it_a = a.iter();
let mut it_b = b.iter();
loop {
if it_a.done() && it_b.done() {
break;
}
assert!(!it_a.done(), "a has fewer cells");
assert!(!it_b.done(), "b has fewer cells");
assert_eq!(it_a.cell_id(), it_b.cell_id());
let cell_a = it_a.index_cell().unwrap();
let cell_b = it_b.index_cell().unwrap();
assert_eq!(cell_a.shapes.len(), cell_b.shapes.len());
for (ca, cb) in cell_a.shapes.iter().zip(cell_b.shapes.iter()) {
assert_eq!(ca.shape_id, cb.shape_id);
assert_eq!(ca.contains_center, cb.contains_center);
assert_eq!(ca.edges, cb.edges);
}
it_a.next();
it_b.next();
}
}
#[test]
fn test_empty_index() {
let mut index = ShapeIndex::new();
index.build();
let decoded = roundtrip_index(&index);
assert_eq!(decoded.num_shape_ids(), 0);
}
#[test]
fn test_single_point_vector() {
let mut index = ShapeIndex::new();
let pv = PointVector::new(vec![
LatLng::from_degrees(0.0, 0.0).to_point(),
LatLng::from_degrees(1.0, 1.0).to_point(),
]);
index.add(Box::new(pv));
index.build();
let decoded = roundtrip_index(&index);
assert_indices_equal(&index, &decoded);
}
#[test]
fn test_single_lax_polyline() {
let mut index = ShapeIndex::new();
let lp = LaxPolyline::new(vec![
LatLng::from_degrees(0.0, 0.0).to_point(),
LatLng::from_degrees(1.0, 0.0).to_point(),
LatLng::from_degrees(2.0, 0.0).to_point(),
]);
index.add(Box::new(lp));
index.build();
let decoded = roundtrip_index(&index);
assert_indices_equal(&index, &decoded);
}
#[test]
fn test_single_lax_polygon() {
let mut index = ShapeIndex::new();
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(0.0, 1.0).to_point();
let p2 = LatLng::from_degrees(1.0, 0.0).to_point();
let lp = LaxPolygon::from_loops(&[&[p0, p1, p2]]);
index.add(Box::new(lp));
index.build();
let decoded = roundtrip_index(&index);
assert_indices_equal(&index, &decoded);
}
#[test]
fn test_multiple_shapes() {
let mut index = ShapeIndex::new();
let pv = PointVector::new(vec![LatLng::from_degrees(10.0, 10.0).to_point()]);
let lp = LaxPolyline::new(vec![
LatLng::from_degrees(0.0, 0.0).to_point(),
LatLng::from_degrees(1.0, 1.0).to_point(),
]);
index.add(Box::new(pv));
index.add(Box::new(lp));
index.build();
let decoded = roundtrip_index(&index);
assert_indices_equal(&index, &decoded);
}
#[test]
fn test_lax_polyline_roundtrip() {
let lp = LaxPolyline::new(vec![
LatLng::from_degrees(0.0, 0.0).to_point(),
LatLng::from_degrees(1.0, 0.0).to_point(),
]);
let mut buf = Vec::new();
lp.encode(&mut buf).unwrap();
let decoded = LaxPolyline::decode(&mut buf.as_slice()).unwrap();
assert_eq!(lp.num_vertices(), decoded.num_vertices());
for i in 0..lp.num_vertices() {
assert_eq!(lp.vertex(i), decoded.vertex(i));
}
}
#[test]
fn test_lax_polygon_roundtrip() {
let p0 = LatLng::from_degrees(0.0, 0.0).to_point();
let p1 = LatLng::from_degrees(0.0, 1.0).to_point();
let p2 = LatLng::from_degrees(1.0, 0.0).to_point();
let p3 = LatLng::from_degrees(10.0, 10.0).to_point();
let p4 = LatLng::from_degrees(10.0, 11.0).to_point();
let p5 = LatLng::from_degrees(11.0, 10.0).to_point();
let lp = LaxPolygon::from_loops(&[&[p0, p1, p2], &[p3, p4, p5]]);
let mut buf = Vec::new();
lp.encode(&mut buf).unwrap();
let decoded = LaxPolygon::decode(&mut buf.as_slice()).unwrap();
assert_eq!(lp.num_loops(), decoded.num_loops());
for i in 0..lp.num_loops() {
assert_eq!(lp.num_loop_vertices(i), decoded.num_loop_vertices(i));
for j in 0..lp.num_loop_vertices(i) {
assert_eq!(lp.loop_vertex(i, j), decoded.loop_vertex(i, j));
}
}
}
#[test]
fn test_lax_polygon_empty() {
let lp = LaxPolygon::default();
let mut buf = Vec::new();
lp.encode(&mut buf).unwrap();
let decoded = LaxPolygon::decode(&mut buf.as_slice()).unwrap();
assert_eq!(decoded.num_loops(), 0);
}
#[test]
fn test_point_vector_roundtrip() {
let pv = PointVector::new(vec![
LatLng::from_degrees(37.0, -122.0).to_point(),
LatLng::from_degrees(38.0, -121.0).to_point(),
LatLng::from_degrees(39.0, -120.0).to_point(),
]);
let mut buf = Vec::new();
pv.encode(&mut buf).unwrap();
let decoded = PointVector::decode(&mut buf.as_slice()).unwrap();
assert_eq!(pv.len(), decoded.len());
for i in 0..pv.len() {
assert_eq!(pv.point(i), decoded.point(i));
}
}
#[test]
fn test_fast_encode_tagged_shapes_mixed() {
let index =
crate::s2::text_format::make_index("0:0 | 0:1 # 1:1, 1:2, 1:3 # 2:2; 2:3, 2:4, 3:3");
let decoded = roundtrip_index(&index);
assert_indices_equal(&index, &decoded);
assert_eq!(index.num_shape_ids(), decoded.num_shape_ids());
}
#[test]
fn test_fast_encode_shape_polygon() {
let polygon = crate::s2::text_format::make_polygon("0:0, 0:1, 1:0");
let mut buf = Vec::new();
polygon.encode(&mut buf).unwrap();
let decoded = Polygon::decode(&mut buf.as_slice()).unwrap();
assert!(polygon.boundary_equals(&decoded));
}
#[test]
fn test_null_shape_encoding() {
let mut index = ShapeIndex::new();
let pv = PointVector::new(vec![LatLng::from_degrees(0.0, 0.0).to_point()]);
index.add(Box::new(pv));
index.build();
let mut buf = Vec::new();
index.encode_to_writer(&mut buf).unwrap();
let decoded = ShapeIndex::decode_from_reader(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.num_shape_ids(), 1);
assert!(decoded.shape(0).is_some());
}
#[test]
fn test_encode_decode_preserves_max_edges_per_cell() {
let mut index = ShapeIndex::new();
index.set_max_edges_per_cell(42);
let pv = PointVector::new(vec![LatLng::from_degrees(0.0, 0.0).to_point()]);
index.add(Box::new(pv));
index.build();
let mut buf = Vec::new();
index.encode_to_writer(&mut buf).unwrap();
let decoded = ShapeIndex::decode_from_reader(&mut Cursor::new(&buf)).unwrap();
assert_eq!(decoded.max_edges_per_cell(), 42);
}
#[test]
fn test_compact_encoding() {
let mut index = ShapeIndex::new();
let lp = LaxPolyline::new(vec![
LatLng::from_degrees(0.0, 0.0).to_point(),
LatLng::from_degrees(1.0, 0.0).to_point(),
]);
index.add(Box::new(lp));
index.build();
let mut buf_fast = Vec::new();
index
.encode_with_hint(&mut buf_fast, CodingHint::Fast)
.unwrap();
let mut buf_compact = Vec::new();
index
.encode_with_hint(&mut buf_compact, CodingHint::Compact)
.unwrap();
assert!(buf_compact.len() <= buf_fast.len());
let decoded_fast = ShapeIndex::decode_from_reader(&mut Cursor::new(&buf_fast)).unwrap();
let decoded_compact =
ShapeIndex::decode_from_reader(&mut Cursor::new(&buf_compact)).unwrap();
assert_indices_equal(&index, &decoded_fast);
assert_indices_equal(&index, &decoded_compact);
}
}