use geo::{BoundingRect, Coord, Geometry, LineString};
use geozero::{CoordDimensions, ToGeo, ToWkb, wkb::SpatiaLiteWkb};
use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
pub struct admin_geometry(pub Geometry<f64>);
impl admin_geometry {
pub fn geometry(&self) -> &Geometry<f64> {
&self.0
}
pub fn into_geometry(self) -> Geometry<f64> {
self.0
}
}
impl From<Geometry<f64>> for admin_geometry {
fn from(geometry: Geometry<f64>) -> Self {
Self(geometry)
}
}
impl ToSql for admin_geometry {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
let bbox = self.0.bounding_rect().ok_or_else(|| {
rusqlite::Error::ToSqlConversionFailure(Box::<dyn std::error::Error + Send + Sync>::from(
"admin_geometry has no bounding rect",
))
})?;
let envelope = vec![bbox.min().x, bbox.min().y, bbox.max().x, bbox.max().y];
let blob = self
.0
.to_spatialite_wkb(CoordDimensions::default(), Some(4326), envelope)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?;
Ok(ToSqlOutput::from(blob))
}
}
impl FromSql for admin_geometry {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let blob = value.as_blob()?;
let empty = || {
admin_geometry(Geometry::GeometryCollection(geo::GeometryCollection(
vec![],
)))
};
if blob.len() <= 40 {
eprintln!(
"warn: admin_geometry: blob too short ({} bytes); returning empty sentinel",
blob.len()
);
return Ok(empty());
}
match SpatiaLiteWkb(blob).to_geo() {
Ok(geometry) => Ok(admin_geometry(geometry)),
Err(e) => {
let preview: Vec<String> = blob.iter().take(16).map(|b| format!("{:02x}", b)).collect();
eprintln!(
"warn: admin_geometry: WKB parse failed ({} bytes, head=[{}]): {:?}",
blob.len(),
preview.join(" "),
e
);
Ok(empty())
}
}
}
}
pub fn mbr_center(blob: &[u8]) -> Option<(f64, f64)> {
if blob.len() < 38 || blob[0] != 0x00 {
return None;
}
let little_endian = blob[1] == 0x01;
let read = |offset: usize| -> f64 {
let mut buf = [0u8; 8];
buf.copy_from_slice(&blob[offset..offset + 8]);
if little_endian {
f64::from_le_bytes(buf)
} else {
f64::from_be_bytes(buf)
}
};
let min_x = read(6);
let min_y = read(14);
let max_x = read(22);
let max_y = read(30);
Some(((min_x + max_x) / 2.0, (min_y + max_y) / 2.0))
}
pub fn approx_eq(a: Coord<f64>, b: Coord<f64>) -> bool {
(a.x - b.x).abs() < 1e-9 && (a.y - b.y).abs() < 1e-9
}
pub fn assemble_rings(ways: &[LineString<f64>]) -> Vec<LineString<f64>> {
let mut remaining: Vec<(LineString<f64>, bool)> =
ways.iter().map(|w| (w.clone(), false)).collect();
let mut rings: Vec<LineString<f64>> = Vec::new();
while let Some(start_idx) = remaining.iter().position(|(_, used)| !used) {
let mut coords: Vec<Coord<f64>> = remaining[start_idx].0.0.clone();
remaining[start_idx].1 = true;
while let Some(next) = take_continuation(&mut remaining, *coords.last().unwrap()) {
coords.extend_from_slice(&next[1..]);
}
rings.push(LineString(coords));
}
rings
}
fn take_continuation(
remaining: &mut [(LineString<f64>, bool)],
tail: Coord<f64>,
) -> Option<Vec<Coord<f64>>> {
for (ls, used) in remaining.iter_mut() {
if *used {
continue;
}
let pts = &ls.0;
if approx_eq(pts[0], tail) {
*used = true;
return Some(pts.clone());
}
if approx_eq(pts[pts.len() - 1], tail) {
*used = true;
let mut rev = pts.clone();
rev.reverse();
return Some(rev);
}
}
None
}
#[derive(Clone, Copy)]
pub struct bounding_box {
pub min_lat: f64,
pub max_lat: f64,
pub min_lon: f64,
pub max_lon: f64,
}
#[cfg(test)]
#[path = "geometry.test.rs"]
mod tests;