use crate::error::{Error, Result};
use pointy::{BBox, Float, Pt, Seg, Transform};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Command {
MoveTo = 1,
LineTo = 2,
ClosePath = 7,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct CommandInt {
id: Command,
count: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct ParamInt {
value: i32,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum GeomType {
#[default]
Point,
Linestring,
Polygon,
}
#[derive(Default)]
pub struct GeomEncoder<F>
where
F: Float,
{
geom_tp: GeomType,
xy_end: Option<Pt<F>>,
transform: Transform<F>,
bbox: BBox<F>,
x_min: i32,
x_max: i32,
y_min: i32,
y_max: i32,
pt0: Option<(i32, i32)>,
pt1: Option<(i32, i32)>,
cmd_offset: usize,
count: u32,
data: Vec<u32>,
}
pub struct GeomData {
geom_tp: GeomType,
data: Vec<u32>,
}
impl CommandInt {
fn new(id: Command, count: u32) -> Self {
debug_assert!(count <= 0x1FFF_FFFF);
CommandInt { id, count }
}
fn encode(&self) -> u32 {
((self.id as u32) & 0x7) | (self.count << 3)
}
fn decode(code: u32) -> Self {
let id = match code & 0x7 {
1 => Command::MoveTo,
2 => Command::LineTo,
7 => Command::ClosePath,
_ => panic!("Invalid code: {code}"),
};
let count = code >> 3;
CommandInt { id, count }
}
}
impl ParamInt {
fn new(value: i32) -> Self {
ParamInt { value }
}
fn encode(&self) -> u32 {
((self.value << 1) ^ (self.value >> 31)) as u32
}
}
impl<F> GeomEncoder<F>
where
F: Float,
{
pub fn new(geom_tp: GeomType) -> Self {
GeomEncoder {
geom_tp,
x_min: i32::MIN,
x_max: i32::MAX,
y_min: i32::MIN,
y_max: i32::MAX,
..Default::default()
}
}
fn adjust_minmax(mut self) -> Self {
if self.bbox != BBox::default() {
let p = self.transform * (self.bbox.x_min(), self.bbox.y_min());
let x0 = p.x.round().to_i32().unwrap_or(i32::MIN);
let y0 = p.y.round().to_i32().unwrap_or(i32::MIN);
let p = self.transform * (self.bbox.x_max(), self.bbox.y_max());
let x1 = p.x.round().to_i32().unwrap_or(i32::MAX);
let y1 = p.y.round().to_i32().unwrap_or(i32::MAX);
self.x_min = x0.min(x1);
self.y_min = y0.min(y1);
self.x_max = x0.max(x1);
self.y_max = y0.max(y1);
}
self
}
pub fn bbox(mut self, bbox: BBox<F>) -> Self {
self.bbox = bbox;
self.adjust_minmax()
}
pub fn transform(mut self, transform: Transform<F>) -> Self {
self.transform = transform;
self.adjust_minmax()
}
fn push_command(&mut self, cmd: Command) {
log::trace!("push_command: {cmd:?}");
self.cmd_offset = self.data.len();
self.data.push(CommandInt::new(cmd, 1).encode());
}
fn set_command_count(&mut self, count: u32) {
let off = self.cmd_offset;
let mut cmd = CommandInt::decode(self.data[off]);
cmd.count = count;
self.data[off] = cmd.encode();
}
fn push_point(&mut self, x: i32, y: i32) {
log::trace!("push_point: {x},{y}");
self.pt0 = self.pt1;
let (px, py) = self.pt0.unwrap_or((0, 0));
self.data.push(ParamInt::new(x.saturating_sub(px)).encode());
self.data.push(ParamInt::new(y.saturating_sub(py)).encode());
self.pt1 = Some((x, y));
self.count += 1;
}
fn pop_point(&mut self) {
log::trace!("pop_point");
self.data.pop();
self.data.pop();
self.pt1 = self.pt0;
self.count -= 1;
}
pub fn point(mut self, x: F, y: F) -> Result<Self> {
self.add_point(x, y)?;
Ok(self)
}
pub fn add_point(&mut self, x: F, y: F) -> Result<()> {
self.add_boundary_points(x, y)?;
self.add_tile_point(x, y)
}
fn add_boundary_points(&mut self, x: F, y: F) -> Result<()> {
if let Some(pxy) = self.xy_end {
let xy = Pt::from((x, y));
let seg = Seg::new(pxy, xy);
if let Some(seg) = seg.clip(self.bbox) {
if seg.p0 != pxy {
self.add_tile_point(seg.p0.x, seg.p0.y)?;
}
if seg.p1 != xy {
self.add_tile_point(seg.p1.x, seg.p1.y)?;
}
}
}
match self.geom_tp {
GeomType::Linestring | GeomType::Polygon => {
self.xy_end = Some(Pt::from((x, y)));
}
_ => (),
}
Ok(())
}
fn add_tile_point(&mut self, x: F, y: F) -> Result<()> {
let pt = self.make_point(x, y)?;
if let Some((px, py)) = self.pt1
&& pt.0 == px
&& pt.1 == py
{
if self.count == 0 {
self.count += 1;
} else {
log::trace!("redundant point: {px},{py}");
}
return Ok(());
}
match self.geom_tp {
GeomType::Point => {
if self.count == 0 {
self.push_command(Command::MoveTo);
}
}
GeomType::Linestring => match self.count {
0 => self.push_command(Command::MoveTo),
1 => self.push_command(Command::LineTo),
_ => (),
},
GeomType::Polygon => {
match self.count {
0 => self.push_command(Command::MoveTo),
1 => self.push_command(Command::LineTo),
_ => (),
}
if self.count >= 2 && self.should_simplify_point(pt.0, pt.1) {
self.pop_point();
}
}
}
self.push_point(pt.0, pt.1);
Ok(())
}
fn make_point(&self, x: F, y: F) -> Result<(i32, i32)> {
let p = self.transform * (x, y);
let mut x = p.x.round().to_i32().ok_or(Error::InvalidValue())?;
let mut y = p.y.round().to_i32().ok_or(Error::InvalidValue())?;
x = x.clamp(self.x_min, self.x_max);
y = y.clamp(self.y_min, self.y_max);
Ok((x, y))
}
fn should_simplify_point(&self, x: i32, y: i32) -> bool {
if let (Some((p0x, p0y)), Some((p1x, p1y))) = (self.pt0, self.pt1) {
if p0x == p1x && p1x == x {
return (p0y < p1y && p1y < y) || (p0y > p1y && p1y > y);
}
if p0y == p1y && p1y == y {
return (p0x < p1x && p1x < x) || (p0x > p1x && p1x > x);
}
}
false
}
pub fn complete_geom(&mut self) -> Result<()> {
match self.geom_tp {
GeomType::Point => {
self.set_command_count(self.count);
return Ok(());
}
GeomType::Linestring => {
if self.count > 1 {
self.set_command_count(self.count - 1);
}
}
GeomType::Polygon => {
if self.count > 1 {
self.set_command_count(self.count - 1);
self.push_command(Command::ClosePath);
}
}
}
self.count = 0;
self.xy_end = None;
self.pt0 = None;
Ok(())
}
pub fn complete(mut self) -> Result<Self> {
self.complete_geom()?;
Ok(self)
}
pub fn encode(mut self) -> Result<GeomData> {
self = self.complete()?;
Ok(GeomData::new(self.geom_tp, self.data))
}
}
impl GeomData {
fn new(geom_tp: GeomType, data: Vec<u32>) -> Self {
GeomData { geom_tp, data }
}
pub(crate) fn geom_type(&self) -> GeomType {
self.geom_tp
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub(crate) fn into_vec(self) -> Vec<u32> {
self.data
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_point() {
let v = GeomEncoder::new(GeomType::Point)
.point(25.0, 17.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(v, vec!(9, 50, 34));
}
#[test]
fn test_multipoint() {
let v = GeomEncoder::new(GeomType::Point)
.point(5.0, 7.0)
.unwrap()
.point(3.0, 2.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(v, vec!(17, 10, 14, 3, 9));
}
#[test]
fn test_linestring() {
let v = GeomEncoder::new(GeomType::Linestring)
.point(2.0, 2.0)
.unwrap()
.point(2.0, 10.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(v, vec!(9, 4, 4, 18, 0, 16, 16, 0));
}
#[test]
fn test_multilinestring() {
let v = GeomEncoder::new(GeomType::Linestring)
.point(2.0, 2.0)
.unwrap()
.point(2.0, 10.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.complete()
.unwrap()
.point(1.0, 1.0)
.unwrap()
.point(3.0, 5.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(v, vec!(9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8));
}
#[test]
fn test_multilinestring_with_redundant_points() {
let v = GeomEncoder::new(GeomType::Linestring)
.point(2.0, 2.0)
.unwrap()
.point(2.0, 2.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.complete()
.unwrap()
.point(10.0, 10.0)
.unwrap()
.point(13.0, 15.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.complete()
.unwrap()
.point(2.0, 2.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(
v,
vec!(9, 4, 4, 10, 16, 16, 18, 6, 10, 5, 9, 9, 15, 15, 10, 16, 16)
);
}
#[test]
fn test_polygon() {
let v = GeomEncoder::new(GeomType::Polygon)
.point(3.0, 6.0)
.unwrap()
.point(8.0, 12.0)
.unwrap()
.point(20.0, 34.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(v, vec!(9, 6, 12, 18, 10, 12, 24, 44, 15));
}
#[test]
fn test_multipolygon() {
let v = GeomEncoder::new(GeomType::Polygon)
.point(0.0, 0.0)
.unwrap()
.point(10.0, 0.0)
.unwrap()
.point(10.0, 10.0)
.unwrap()
.point(0.0, 10.0)
.unwrap()
.complete()
.unwrap()
.point(11.0, 11.0)
.unwrap()
.point(20.0, 11.0)
.unwrap()
.point(20.0, 20.0)
.unwrap()
.point(11.0, 20.0)
.unwrap()
.complete()
.unwrap()
.point(13.0, 13.0)
.unwrap()
.point(13.0, 17.0)
.unwrap()
.point(17.0, 17.0)
.unwrap()
.point(17.0, 13.0)
.unwrap()
.encode()
.unwrap()
.into_vec();
assert_eq!(
v,
vec!(
9, 0, 0, 26, 20, 0, 0, 20, 19, 0, 15, 9, 22, 2, 26, 18, 0, 0,
18, 17, 0, 15, 9, 4, 13, 26, 0, 8, 8, 0, 0, 7, 15
)
);
}
}