use crate::value::Value;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone)]
pub(crate) struct GeoPoly {
pub xy: Vec<f32>,
}
impl GeoPoly {
fn n_vertex(&self) -> usize {
self.xy.len() / 2
}
fn x(&self, i: usize) -> f32 {
self.xy[i * 2]
}
fn y(&self, i: usize) -> f32 {
self.xy[i * 2 + 1]
}
pub(crate) fn to_blob(&self) -> Vec<u8> {
let n = self.n_vertex();
let mut out = Vec::with_capacity(4 + 8 * n);
out.push(0x01);
out.push(((n >> 16) & 0xff) as u8);
out.push(((n >> 8) & 0xff) as u8);
out.push((n & 0xff) as u8);
for &c in &self.xy {
out.extend_from_slice(&c.to_le_bytes());
}
out
}
pub(crate) fn to_json(&self) -> String {
let n = self.n_vertex();
let mut s = String::from("[");
for i in 0..n {
s.push('[');
s.push_str(&fmt_bang_g(self.x(i)));
s.push(',');
s.push_str(&fmt_bang_g(self.y(i)));
s.push_str("],");
}
s.push('[');
s.push_str(&fmt_bang_g(self.x(0)));
s.push(',');
s.push_str(&fmt_bang_g(self.y(0)));
s.push_str("]]");
s
}
pub(crate) fn to_svg(&self, extra: &[Option<String>]) -> String {
let n = self.n_vertex();
let mut s = String::from("<polyline points=");
let mut sep = '\'';
for i in 0..n {
s.push(sep);
s.push_str(&fmt_g(self.x(i)));
s.push(',');
s.push_str(&fmt_g(self.y(i)));
sep = ' ';
}
s.push(' ');
s.push_str(&fmt_g(self.x(0)));
s.push(',');
s.push_str(&fmt_g(self.y(0)));
s.push('\'');
for z in extra.iter().flatten() {
if !z.is_empty() {
s.push(' ');
s.push_str(z);
}
}
s.push_str("></polyline>");
s
}
pub(crate) fn area(&self) -> f64 {
let n = self.n_vertex();
let mut area = 0.0f64;
for i in 0..n - 1 {
let term = (self.x(i) - self.x(i + 1)) * (self.y(i) + self.y(i + 1));
area += term as f64 * 0.5;
}
let term = (self.x(n - 1) - self.x(0)) * (self.y(n - 1) + self.y(0));
area += term as f64 * 0.5;
area
}
pub(crate) fn ccw(&self) -> GeoPoly {
let mut p = self.clone();
if p.area() < 0.0 {
let n = p.n_vertex();
let (mut ii, mut jj) = (1usize, n - 1);
while ii < jj {
p.xy.swap(ii * 2, jj * 2);
p.xy.swap(ii * 2 + 1, jj * 2 + 1);
ii += 1;
jj -= 1;
}
}
p
}
pub(crate) fn bbox(&self) -> GeoPoly {
let (mnx, mxx, mny, mxy) = self.bbox_coords();
GeoPoly::from_bbox(mnx, mxx, mny, mxy)
}
pub(crate) fn bbox_coords(&self) -> (f32, f32, f32, f32) {
let n = self.n_vertex();
let mut mnx = self.x(0);
let mut mxx = self.x(0);
let mut mny = self.y(0);
let mut mxy = self.y(0);
for ii in 1..n {
let r = self.x(ii) as f64;
if r < mnx as f64 {
mnx = r as f32;
} else if r > mxx as f64 {
mxx = r as f32;
}
let r = self.y(ii) as f64;
if r < mny as f64 {
mny = r as f32;
} else if r > mxy as f64 {
mxy = r as f32;
}
}
(mnx, mxx, mny, mxy)
}
pub(crate) fn from_bbox(mnx: f32, mxx: f32, mny: f32, mxy: f32) -> GeoPoly {
GeoPoly {
xy: alloc::vec![mnx, mny, mxx, mny, mxx, mxy, mnx, mxy],
}
}
pub(crate) fn xform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> GeoPoly {
let n = self.n_vertex();
let mut out = GeoPoly {
xy: self.xy.clone(),
};
for ii in 0..n {
let x0 = self.x(ii) as f64;
let y0 = self.y(ii) as f64;
out.xy[ii * 2] = (a * x0 + b * y0 + e) as f32;
out.xy[ii * 2 + 1] = (c * x0 + d * y0 + f) as f32;
}
out
}
pub(crate) fn contains_point(&self, x0: f64, y0: f64) -> i64 {
let n = self.n_vertex();
let mut v = 0;
let mut cnt = 0i64;
let mut ii = 0usize;
while ii < n - 1 {
v = point_beneath_line(
x0,
y0,
self.x(ii) as f64,
self.y(ii) as f64,
self.x(ii + 1) as f64,
self.y(ii + 1) as f64,
);
if v == 2 {
break;
}
cnt += v;
ii += 1;
}
if v != 2 {
v = point_beneath_line(
x0,
y0,
self.x(ii) as f64,
self.y(ii) as f64,
self.x(0) as f64,
self.y(0) as f64,
);
}
if v == 2 {
1
} else if ((v + cnt) & 1) == 0 {
0
} else {
2
}
}
}
pub(crate) fn regular(x: f64, y: f64, r: f64, n: i64) -> Option<GeoPoly> {
if n < 3 || r <= 0.0 {
return None;
}
let n = if n > 1000 { 1000 } else { n } as usize;
let mut xy = Vec::with_capacity(2 * n);
for i in 0..n {
let r_angle = 2.0 * GEOPOLY_PI * (i as f64) / (n as f64);
let px = x - r * geopoly_sine(r_angle - 0.5 * GEOPOLY_PI);
let py = y + r * geopoly_sine(r_angle);
xy.push(px as f32);
xy.push(py as f32);
}
Some(GeoPoly { xy })
}
#[allow(clippy::approx_constant, clippy::excessive_precision)]
const GEOPOLY_PI: f64 = 3.1415926535897932385;
fn geopoly_sine(mut r: f64) -> f64 {
if r >= 1.5 * GEOPOLY_PI {
r -= 2.0 * GEOPOLY_PI;
}
if r >= 0.5 * GEOPOLY_PI {
-geopoly_sine(r - GEOPOLY_PI)
} else {
let r2 = r * r;
let r3 = r2 * r;
let r5 = r3 * r2;
0.9996949 * r - 0.1656700 * r3 + 0.0075134 * r5
}
}
fn point_beneath_line(x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> i64 {
if x0 == x1 && y0 == y1 {
return 2;
}
if x1 < x2 {
if x0 <= x1 || x0 > x2 {
return 0;
}
} else if x1 > x2 {
if x0 <= x2 || x0 > x1 {
return 0;
}
} else {
if x0 != x1 {
return 0;
}
if y0 < y1 && y0 < y2 {
return 0;
}
if y0 > y1 && y0 > y2 {
return 0;
}
return 2;
}
let y = y1 + (y2 - y1) * (x0 - x1) / (x2 - x1);
if y0 == y {
2
} else if y0 < y {
1
} else {
0
}
}
fn fmt_bang_g(c: f32) -> String {
match crate::exec::datetime::printf(&[Value::Text(String::from("%!g")), Value::Real(c as f64)])
{
Value::Text(s) => s,
_ => String::new(),
}
}
fn fmt_g(c: f32) -> String {
match crate::exec::datetime::printf(&[Value::Text(String::from("%g")), Value::Real(c as f64)]) {
Value::Text(s) => s,
_ => String::new(),
}
}
pub(crate) fn parse_value(v: &Value) -> Option<GeoPoly> {
match v {
Value::Blob(b) => parse_blob(b),
Value::Text(s) => parse_json(s.as_bytes()),
_ => None,
}
}
pub(crate) enum BBoxStep {
Poly(GeoPoly),
ZeroBox,
Skip,
}
pub(crate) fn bbox_step(v: &Value) -> BBoxStep {
match v {
Value::Blob(b) => match parse_blob(b) {
Some(p) => BBoxStep::Poly(p),
None => BBoxStep::Skip,
},
Value::Text(s) => {
let bytes = s.as_bytes();
match parse_json(bytes) {
Some(p) => BBoxStep::Poly(p),
None => {
let mut i = 0;
while i < bytes.len() && is_geo_space(bytes[i]) {
i += 1;
}
if bytes.get(i) == Some(&b'[') {
BBoxStep::Skip
} else {
BBoxStep::ZeroBox
}
}
}
}
_ => BBoxStep::Skip,
}
}
fn parse_blob(a: &[u8]) -> Option<GeoPoly> {
if a.len() < 4 + 6 * 4 {
return None;
}
let n_vertex = ((a[1] as usize) << 16) + ((a[2] as usize) << 8) + a[3] as usize;
if (a[0] != 0 && a[0] != 1) || (n_vertex * 2 * 4 + 4) != a.len() {
return None;
}
let little_endian = a[0] == 1;
let mut xy = Vec::with_capacity(n_vertex * 2);
for i in 0..n_vertex * 2 {
let off = 4 + i * 4;
let bytes = [a[off], a[off + 1], a[off + 2], a[off + 3]];
let f = if little_endian {
f32::from_le_bytes(bytes)
} else {
f32::from_be_bytes(bytes)
};
xy.push(f);
}
Some(GeoPoly { xy })
}
fn parse_json(z: &[u8]) -> Option<GeoPoly> {
let mut p = Parser { z, pos: 0 };
let mut verts: Vec<f32> = Vec::new(); if p.skip_space() != b'[' {
return None;
}
p.pos += 1;
while p.skip_space() == b'[' {
let mut ii = 0;
p.pos += 1;
let mut vx = 0.0f32;
let mut vy = 0.0f32;
let mut have_vertex = false;
loop {
let mut val = 0.0f32;
if !p.parse_number(&mut val) {
break;
}
if ii == 0 {
vx = val;
} else if ii == 1 {
vy = val;
}
ii += 1;
if ii == 2 {
verts.push(vx);
verts.push(vy);
have_vertex = true;
}
let c = p.skip_space();
p.pos += 1;
if c == b',' {
continue;
}
if c == b']' && ii >= 2 {
break;
}
let _ = have_vertex;
return None;
}
if p.skip_space() == b',' {
p.pos += 1;
continue;
}
break;
}
let n_vertex = verts.len() / 2;
if p.skip_space() == b']'
&& n_vertex >= 4
&& verts[0] == verts[(n_vertex - 1) * 2]
&& verts[1] == verts[(n_vertex - 1) * 2 + 1]
{
p.pos += 1;
if p.skip_space() != 0 {
return None;
}
verts.truncate((n_vertex - 1) * 2);
Some(GeoPoly { xy: verts })
} else {
None
}
}
struct Parser<'a> {
z: &'a [u8],
pos: usize,
}
impl Parser<'_> {
fn at(&self, i: usize) -> u8 {
self.z.get(i).copied().unwrap_or(0)
}
fn skip_space(&mut self) -> u8 {
while is_geo_space(self.at(self.pos)) {
self.pos += 1;
}
self.at(self.pos)
}
fn parse_number(&mut self, out: &mut f32) -> bool {
let c = self.skip_space();
let start = self.pos;
let z = |j: isize| -> u8 {
let idx = start as isize + j;
if idx < 0 {
0
} else {
self.at(idx as usize)
}
};
let mut j = 0isize;
let mut seen_dp = false;
let mut seen_e = false;
let mut c = c;
if c == b'-' {
j = 1;
c = z(j);
}
if c == b'0' && z(j + 1).is_ascii_digit() {
return false;
}
loop {
c = z(j);
if c.is_ascii_digit() {
j += 1;
continue;
}
if c == b'.' {
if z(j - 1) == b'-' {
return false;
}
if seen_dp {
return false;
}
seen_dp = true;
j += 1;
continue;
}
if c == b'e' || c == b'E' {
if z(j - 1) < b'0' {
return false;
}
if seen_e {
return false;
}
seen_dp = true;
seen_e = true;
let mut cc = z(j + 1);
if cc == b'+' || cc == b'-' {
j += 1;
cc = z(j + 1);
}
if !cc.is_ascii_digit() {
return false;
}
j += 1;
continue;
}
break;
}
if z(j - 1) < b'0' {
return false;
}
let j = j as usize;
let tok = &self.z[start..start + j];
let Ok(s) = core::str::from_utf8(tok) else {
return false;
};
let Ok(r) = s.parse::<f64>() else {
return false;
};
*out = r as f32;
self.pos = start + j;
true
}
}
fn is_geo_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | b'\r')
}
pub(crate) fn overlap(p1: &GeoPoly, p2: &GeoPoly) -> i64 {
let mut segs: Vec<Segment> = Vec::new();
let mut events: Vec<Event> = Vec::new();
add_segments(&mut segs, &mut events, p1, 1);
add_segments(&mut segs, &mut events, p2, 2);
let order = sort_events_by_x(&events);
let mut active: Vec<usize> = Vec::new();
let mut a_overlap = [0u8; 4];
let mut need_sort = false;
let mut r_x = if let Some(&first) = order.first() {
if events[first].x == 0.0 {
-1.0
} else {
0.0
}
} else {
0.0
};
let mut ei = 0usize;
while ei < order.len() {
let ev = events[order[ei]];
if ev.x != r_x {
r_x = ev.x;
if need_sort {
sort_active_by_y_c(&mut active, &segs);
need_sort = false;
}
let mut prev: Option<usize> = None;
let mut i_mask = 0usize;
for &si in &active {
if let Some(pi) = prev {
if segs[pi].y != segs[si].y {
a_overlap[i_mask] = 1;
}
}
i_mask ^= segs[si].side as usize;
prev = Some(si);
}
prev = None;
i_mask = 0;
for &si in &active {
let y = segs[si].c * r_x + segs[si].b;
segs[si].y = y;
if let Some(pi) = prev {
if segs[pi].y > segs[si].y && segs[pi].side != segs[si].side {
return 1;
} else if segs[pi].y != segs[si].y {
a_overlap[i_mask] = 1;
}
}
i_mask ^= segs[si].side as usize;
prev = Some(si);
}
}
if ev.etype == 0 {
let si = ev.seg;
segs[si].y = segs[si].y0 as f64;
active.insert(0, si);
need_sort = true;
} else {
if let Some(pos) = active.iter().position(|&s| s == ev.seg) {
active.remove(pos);
}
}
ei += 1;
}
if a_overlap[3] == 0 {
0
} else if a_overlap[1] != 0 && a_overlap[2] == 0 {
3
} else if a_overlap[1] == 0 && a_overlap[2] != 0 {
2
} else if a_overlap[1] == 0 && a_overlap[2] == 0 {
4
} else {
1
}
}
pub(crate) fn within(p1: &GeoPoly, p2: &GeoPoly) -> i64 {
let x = overlap(p1, p2);
if x == 2 {
1
} else if x == 4 {
2
} else {
0
}
}
#[derive(Clone, Copy)]
struct Event {
x: f64,
etype: u8, seg: usize,
}
struct Segment {
c: f64, b: f64, y: f64, y0: f32, side: u8,
}
fn add_segments(segs: &mut Vec<Segment>, events: &mut Vec<Event>, poly: &GeoPoly, side: u8) {
let n = poly.n_vertex();
for i in 0..n - 1 {
add_one_segment(
segs,
events,
poly.x(i),
poly.y(i),
poly.x(i + 1),
poly.y(i + 1),
side,
);
}
add_one_segment(
segs,
events,
poly.x(n - 1),
poly.y(n - 1),
poly.x(0),
poly.y(0),
side,
);
}
fn add_one_segment(
segs: &mut Vec<Segment>,
events: &mut Vec<Event>,
mut x0: f32,
mut y0: f32,
mut x1: f32,
mut y1: f32,
side: u8,
) {
if x0 == x1 {
return; }
if x0 > x1 {
core::mem::swap(&mut x0, &mut x1);
core::mem::swap(&mut y0, &mut y1);
}
let c = ((y1 - y0) / (x1 - x0)) as f64;
let b = y1 as f64 - x1 as f64 * c;
let si = segs.len();
segs.push(Segment {
c,
b,
y: 0.0,
y0,
side,
});
events.push(Event {
x: x0 as f64,
etype: 0,
seg: si,
});
events.push(Event {
x: x1 as f64,
etype: 1,
seg: si,
});
}
fn sort_active_by_y_c(active: &mut Vec<usize>, segs: &[Segment]) {
let merge = |left: &[usize], right: &[usize]| -> Vec<usize> {
let mut out = Vec::with_capacity(left.len() + right.len());
let (mut i, mut j) = (0usize, 0usize);
while i < left.len() && j < right.len() {
let a = &segs[left[i]];
let b = &segs[right[j]];
let mut r = b.y - a.y;
if r == 0.0 {
r = b.c - a.c;
}
if r < 0.0 {
out.push(right[j]);
j += 1;
} else {
out.push(left[i]);
i += 1;
}
}
out.extend_from_slice(&left[i..]);
out.extend_from_slice(&right[j..]);
out
};
let mut buckets: Vec<Option<Vec<usize>>> = Vec::new();
for &si in active.iter() {
let mut run = alloc::vec![si];
let mut i = 0;
while i < buckets.len() {
if let Some(b) = buckets[i].take() {
run = merge(&b, &run);
i += 1;
} else {
break;
}
}
if i < buckets.len() {
buckets[i] = Some(run);
} else {
buckets.push(Some(run));
}
}
let mut result: Vec<usize> = Vec::new();
for b in buckets.into_iter().flatten() {
result = merge(&b, &result);
}
*active = result;
}
fn sort_events_by_x(events: &[Event]) -> Vec<usize> {
let merge = |left: &[usize], right: &[usize]| -> Vec<usize> {
let mut out = Vec::with_capacity(left.len() + right.len());
let (mut i, mut j) = (0usize, 0usize);
while i < left.len() && j < right.len() {
if events[right[j]].x <= events[left[i]].x {
out.push(right[j]);
j += 1;
} else {
out.push(left[i]);
i += 1;
}
}
out.extend_from_slice(&left[i..]);
out.extend_from_slice(&right[j..]);
out
};
let mut buckets: Vec<Option<Vec<usize>>> = Vec::new();
for si in 0..events.len() {
let mut run = alloc::vec![si];
let mut i = 0;
while i < buckets.len() {
if let Some(b) = buckets[i].take() {
run = merge(&b, &run);
i += 1;
} else {
break;
}
}
if i < buckets.len() {
buckets[i] = Some(run);
} else {
buckets.push(Some(run));
}
}
let mut result: Vec<usize> = Vec::new();
for b in buckets.into_iter().flatten() {
result = merge(&b, &result);
}
result
}