const GEOM_COMMAND_MOVE_TO: u32 = 1;
const GEOM_COMMAND_LINE_TO: u32 = 2;
const GEOM_COMMAND_CLOSE_PATH: u32 = 7;
const GEOM_COMMAND_MOVE_TO_WITH_COUNT1: u32 = 1 << 3 | GEOM_COMMAND_MOVE_TO;
const GEOM_COMMAND_CLOSE_PATH_WITH_COUNT1: u32 = 1 << 3 | GEOM_COMMAND_CLOSE_PATH;
pub struct GeometryEncoder {
buf: Vec<u32>,
prev_x: i32,
prev_y: i32,
}
impl GeometryEncoder {
#[inline]
pub fn new() -> Self {
Self {
buf: Vec::new(),
prev_x: 0,
prev_y: 0,
}
}
#[inline]
pub fn into_vec(self) -> Vec<u32> {
self.buf
}
pub fn add_points(&mut self, iterable: impl IntoIterator<Item = [i32; 2]>) {
let mut iter = iterable.into_iter();
let Some([first_x, first_y]) = iter.next() else {
return;
};
let dx = first_x - self.prev_x;
let dy = first_y - self.prev_y;
(self.prev_x, self.prev_y) = (first_x, first_y);
let moveto_cmd_pos = self.buf.len();
self.buf
.extend([GEOM_COMMAND_MOVE_TO_WITH_COUNT1, zigzag(dx), zigzag(dy)]);
let mut count = 1;
for [x, y] in iter {
let dx = x - self.prev_x;
let dy = y - self.prev_y;
(self.prev_x, self.prev_y) = (x, y);
if dx != 0 || dy != 0 {
self.buf.extend([zigzag(dx), zigzag(dy)]);
count += 1;
}
}
self.buf[moveto_cmd_pos] = GEOM_COMMAND_MOVE_TO | count << 3;
}
pub fn add_linestring(&mut self, iterable: impl IntoIterator<Item = [i32; 2]>) {
self.add_path(iterable, false)
}
pub fn add_ring(&mut self, iterable: impl IntoIterator<Item = [i32; 2]>) {
self.add_path(iterable, true)
}
fn add_path(&mut self, iterable: impl IntoIterator<Item = [i32; 2]>, close: bool) {
let mut iter = iterable.into_iter();
let Some([first_x, first_y]) = iter.next() else {
return;
};
let dx = first_x - self.prev_x;
let dy = first_y - self.prev_y;
(self.prev_x, self.prev_y) = (first_x, first_y);
self.buf
.extend([GEOM_COMMAND_MOVE_TO_WITH_COUNT1, zigzag(dx), zigzag(dy)]);
let lineto_cmd_pos = self.buf.len();
self.buf.push(GEOM_COMMAND_LINE_TO); let mut count = 0;
for [x, y] in iter {
let dx = x - self.prev_x;
let dy = y - self.prev_y;
(self.prev_x, self.prev_y) = (x, y);
if dx != 0 || dy != 0 {
self.buf.extend([zigzag(dx), zigzag(dy)]);
count += 1;
}
}
if count == 0 {
self.buf.extend([0, 0]);
count += 1;
}
self.buf[lineto_cmd_pos] = GEOM_COMMAND_LINE_TO | count << 3;
if close {
self.buf.push(GEOM_COMMAND_CLOSE_PATH_WITH_COUNT1);
}
}
}
impl Default for GeometryEncoder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DecodedGeometry {
Points(Vec<[i32; 2]>),
LineStrings(Vec<Vec<[i32; 2]>>),
Polygons(Vec<Vec<Vec<[i32; 2]>>>),
}
pub type Geometry = DecodedGeometry;
pub struct GeometryDecoder<'a> {
buf: &'a [u32],
pos: usize,
cursor_x: i32,
cursor_y: i32,
}
impl<'a> GeometryDecoder<'a> {
pub fn new(buf: &'a [u32]) -> Self {
Self {
buf,
pos: 0,
cursor_x: 0,
cursor_y: 0,
}
}
pub fn decode_points(&mut self) -> Result<Vec<[i32; 2]>, String> {
let mut points = Vec::new();
while self.pos < self.buf.len() {
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
let count = (cmd_int >> 3) as usize;
match cmd {
GEOM_COMMAND_MOVE_TO => {
for _ in 0..count {
let coord = self.read_coord()?;
points.push(coord);
}
}
_ => {
return Err(format!("Unexpected command {} in point geometry", cmd));
}
}
}
Ok(points)
}
pub fn decode_linestrings(&mut self) -> Result<Vec<Vec<[i32; 2]>>, String> {
let mut linestrings = Vec::new();
while self.pos < self.buf.len() {
let mut linestring = Vec::new();
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
let count = (cmd_int >> 3) as usize;
if cmd != GEOM_COMMAND_MOVE_TO {
return Err(format!("Expected MoveTo command, got {}", cmd));
}
if count != 1 {
return Err(format!(
"MoveTo count must be 1 for linestrings, got {}",
count
));
}
let coord = self.read_coord()?;
linestring.push(coord);
if self.pos >= self.buf.len() {
return Err("Unexpected end of buffer after MoveTo".to_string());
}
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
let count = (cmd_int >> 3) as usize;
if cmd != GEOM_COMMAND_LINE_TO {
return Err(format!("Expected LineTo command, got {}", cmd));
}
for _ in 0..count {
let coord = self.read_coord()?;
linestring.push(coord);
}
linestrings.push(linestring);
}
Ok(linestrings)
}
pub fn decode_polygons(&mut self) -> Result<Vec<Vec<Vec<[i32; 2]>>>, String> {
let mut polygons: Vec<Vec<Vec<[i32; 2]>>> = Vec::new();
let mut current_polygon: Vec<Vec<[i32; 2]>> = Vec::new();
while self.pos < self.buf.len() {
let mut ring = Vec::new();
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
let count = (cmd_int >> 3) as usize;
if cmd != GEOM_COMMAND_MOVE_TO {
return Err(format!("Expected MoveTo command, got {}", cmd));
}
if count != 1 {
return Err(format!(
"MoveTo count must be 1 for polygons, got {}",
count
));
}
let coord = self.read_coord()?;
ring.push(coord);
if self.pos >= self.buf.len() {
return Err("Unexpected end of buffer after MoveTo".to_string());
}
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
let count = (cmd_int >> 3) as usize;
if cmd != GEOM_COMMAND_LINE_TO {
return Err(format!("Expected LineTo command, got {}", cmd));
}
for _ in 0..count {
let coord = self.read_coord()?;
ring.push(coord);
}
if self.pos >= self.buf.len() {
return Err("Unexpected end of buffer after LineTo".to_string());
}
let cmd_int = self.buf[self.pos];
self.pos += 1;
let cmd = cmd_int & 0x7;
if cmd != GEOM_COMMAND_CLOSE_PATH {
return Err(format!("Expected ClosePath command, got {}", cmd));
}
let signed_area = calculate_signed_area(&ring);
let is_exterior = signed_area > 0.0;
if is_exterior && !current_polygon.is_empty() {
polygons.push(current_polygon);
current_polygon = Vec::new();
}
current_polygon.push(ring);
}
if !current_polygon.is_empty() {
polygons.push(current_polygon);
}
Ok(polygons)
}
fn read_coord(&mut self) -> Result<[i32; 2], String> {
if self.pos + 1 >= self.buf.len() {
return Err("Unexpected end of buffer while reading coordinates".to_string());
}
let dx = unzigzag(self.buf[self.pos]);
let dy = unzigzag(self.buf[self.pos + 1]);
self.pos += 2;
self.cursor_x += dx;
self.cursor_y += dy;
Ok([self.cursor_x, self.cursor_y])
}
}
fn calculate_signed_area(ring: &[[i32; 2]]) -> f64 {
if ring.len() < 3 {
return 0.0;
}
let mut area = 0i64;
for i in 0..ring.len() {
let j = (i + 1) % ring.len();
area += ring[i][0] as i64 * ring[j][1] as i64;
area -= ring[j][0] as i64 * ring[i][1] as i64;
}
area as f64 / 2.0
}
#[inline]
fn zigzag(v: i32) -> u32 {
((v << 1) ^ (v >> 31)) as u32
}
#[inline]
fn unzigzag(v: u32) -> i32 {
((v >> 1) as i32) ^ (-((v & 1) as i32))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zigzag() {
assert_eq!(zigzag(0), 0);
assert_eq!(zigzag(-1), 1);
assert_eq!(zigzag(1), 2);
assert_eq!(zigzag(-2), 3);
assert_eq!(zigzag(2), 4);
assert_eq!(zigzag(4096), 8192);
assert_eq!(zigzag(-4096), 8191);
}
#[test]
fn test_linestring_with_two_vertices() {
let mut encoder = GeometryEncoder::new();
encoder.add_linestring([[0, 0], [10, 10]]);
let geometry = encoder.into_vec();
assert_eq!(geometry.len(), 6);
assert_eq!(geometry[0], GEOM_COMMAND_MOVE_TO_WITH_COUNT1); assert_eq!(geometry[1], zigzag(0)); assert_eq!(geometry[2], zigzag(0)); assert_eq!(geometry[3], GEOM_COMMAND_LINE_TO | (1 << 3)); assert_eq!(geometry[4], zigzag(10)); assert_eq!(geometry[5], zigzag(10)); }
#[test]
fn test_linestring_with_duplicate_points_filtered() {
let mut encoder = GeometryEncoder::new();
encoder.add_linestring([[0, 0], [0, 0], [0, 0]]);
let geometry = encoder.into_vec();
assert_eq!(geometry.len(), 6);
assert_eq!(geometry[0], GEOM_COMMAND_MOVE_TO_WITH_COUNT1);
assert_eq!(geometry[3], GEOM_COMMAND_LINE_TO | (1 << 3)); assert_eq!(geometry[4], 0); assert_eq!(geometry[5], 0); }
#[test]
fn test_unzigzag() {
assert_eq!(unzigzag(0), 0);
assert_eq!(unzigzag(1), -1);
assert_eq!(unzigzag(2), 1);
assert_eq!(unzigzag(3), -2);
assert_eq!(unzigzag(4), 2);
assert_eq!(unzigzag(8192), 4096);
assert_eq!(unzigzag(8191), -4096);
}
#[test]
fn test_zigzag_roundtrip() {
for v in [-4096, -100, -1, 0, 1, 100, 4096] {
assert_eq!(unzigzag(zigzag(v)), v);
}
}
#[test]
fn test_decode_points() {
let mut encoder = GeometryEncoder::new();
let points = [[10, 20], [30, 40], [50, 60]];
encoder.add_points(points);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_points().unwrap();
assert_eq!(decoded, points);
}
#[test]
fn test_decode_single_linestring() {
let mut encoder = GeometryEncoder::new();
let linestring = [[0, 0], [10, 10], [20, 20]];
encoder.add_linestring(linestring);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_linestrings().unwrap();
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0], linestring);
}
#[test]
fn test_decode_multiple_linestrings() {
let mut encoder = GeometryEncoder::new();
let ls1 = [[0, 0], [10, 10]];
let ls2 = [[100, 100], [110, 110], [120, 120]];
encoder.add_linestring(ls1);
encoder.add_linestring(ls2);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_linestrings().unwrap();
assert_eq!(decoded.len(), 2);
assert_eq!(decoded[0], ls1);
assert_eq!(decoded[1], ls2);
}
#[test]
fn test_decode_polygon_single_ring() {
let mut encoder = GeometryEncoder::new();
let ring = [[0, 0], [100, 0], [100, 100], [0, 100]];
encoder.add_ring(ring);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_polygons().unwrap();
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0].len(), 1);
assert_eq!(decoded[0][0], ring);
}
#[test]
fn test_decode_polygon_with_holes() {
let mut encoder = GeometryEncoder::new();
let exterior = [[0, 0], [100, 0], [100, 100], [0, 100]]; let hole1 = [[10, 10], [10, 20], [20, 20], [20, 10]]; let hole2 = [[30, 30], [30, 40], [40, 40], [40, 30]]; encoder.add_ring(exterior);
encoder.add_ring(hole1);
encoder.add_ring(hole2);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_polygons().unwrap();
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0].len(), 3);
assert_eq!(decoded[0][0], exterior);
assert_eq!(decoded[0][1], hole1);
assert_eq!(decoded[0][2], hole2);
}
#[test]
fn test_decode_multiple_polygons() {
let mut encoder = GeometryEncoder::new();
let poly1_ring1 = [[0, 0], [50, 0], [50, 50], [0, 50]]; let poly1_ring2 = [[10, 10], [10, 20], [20, 20], [20, 10]]; encoder.add_ring(poly1_ring1);
encoder.add_ring(poly1_ring2);
let poly2_ring1 = [[100, 100], [150, 100], [150, 150], [100, 150]]; encoder.add_ring(poly2_ring1);
let geometry = encoder.into_vec();
let mut decoder = GeometryDecoder::new(&geometry);
let decoded = decoder.decode_polygons().unwrap();
assert_eq!(decoded.len(), 2);
assert_eq!(decoded[0].len(), 2); assert_eq!(decoded[0][0], poly1_ring1);
assert_eq!(decoded[0][1], poly1_ring2);
assert_eq!(decoded[1].len(), 1); assert_eq!(decoded[1][0], poly2_ring1);
}
}