use crate::error::{GeoError, Result};
use std::io::{Cursor, Read};
#[derive(Debug, Clone, PartialEq)]
pub struct Coord {
pub x: f64,
pub y: f64,
pub z: Option<f64>,
pub m: Option<f64>,
}
impl Coord {
pub fn xy(x: f64, y: f64) -> Self {
Self {
x,
y,
z: None,
m: None,
}
}
pub fn xyz(x: f64, y: f64, z: f64) -> Self {
Self {
x,
y,
z: Some(z),
m: None,
}
}
pub fn has_z(&self) -> bool {
self.z.is_some()
}
pub fn has_m(&self) -> bool {
self.m.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Ring(pub Vec<Coord>);
impl Ring {
pub fn new(coords: Vec<Coord>) -> Self {
Self(coords)
}
pub fn coords(&self) -> &[Coord] {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn signed_area(&self) -> f64 {
let n = self.0.len();
if n < 3 {
return 0.0;
}
let mut a = 0.0f64;
for i in 0..n {
let j = (i + 1) % n;
a += self.0[i].x * self.0[j].y - self.0[j].x * self.0[i].y;
}
a * 0.5
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Geometry {
Point(Coord),
LineString(Vec<Coord>),
Polygon {
exterior: Ring,
interiors: Vec<Ring>,
},
MultiPoint(Vec<Coord>),
MultiLineString(Vec<Vec<Coord>>),
MultiPolygon(Vec<(Ring, Vec<Ring>)>),
GeometryCollection(Vec<Geometry>),
}
impl Geometry {
pub fn point(x: f64, y: f64) -> Self {
Self::Point(Coord::xy(x, y))
}
pub fn point_z(x: f64, y: f64, z: f64) -> Self {
Self::Point(Coord::xyz(x, y, z))
}
pub fn line_string(coords: Vec<Coord>) -> Self {
Self::LineString(coords)
}
pub fn polygon(exterior: Vec<Coord>, interiors: Vec<Vec<Coord>>) -> Self {
Self::Polygon {
exterior: Ring::new(exterior),
interiors: interiors.into_iter().map(Ring::new).collect(),
}
}
pub fn multi_point(pts: Vec<Coord>) -> Self {
Self::MultiPoint(pts)
}
pub fn multi_line_string(lines: Vec<Vec<Coord>>) -> Self {
Self::MultiLineString(lines)
}
pub fn multi_polygon(polys: Vec<(Vec<Coord>, Vec<Vec<Coord>>)>) -> Self {
Self::MultiPolygon(
polys
.into_iter()
.map(|(e, hs)| (Ring::new(e), hs.into_iter().map(Ring::new).collect()))
.collect(),
)
}
pub fn geom_type(&self) -> GeometryType {
match self {
Self::Point(_) => GeometryType::Point,
Self::LineString(_) => GeometryType::LineString,
Self::Polygon { .. } => GeometryType::Polygon,
Self::MultiPoint(_) => GeometryType::MultiPoint,
Self::MultiLineString(_) => GeometryType::MultiLineString,
Self::MultiPolygon(_) => GeometryType::MultiPolygon,
Self::GeometryCollection(_) => GeometryType::GeometryCollection,
}
}
pub fn has_z(&self) -> bool {
self.all_coords().iter().any(|c| c.has_z())
}
pub fn is_empty(&self) -> bool {
match self {
Self::Point(_) => false,
Self::LineString(v) => v.is_empty(),
Self::Polygon { exterior, .. } => exterior.is_empty(),
Self::MultiPoint(v) => v.is_empty(),
Self::MultiLineString(v) => v.is_empty(),
Self::MultiPolygon(v) => v.is_empty(),
Self::GeometryCollection(v) => v.is_empty(),
}
}
pub fn all_coords(&self) -> Vec<&Coord> {
match self {
Self::Point(c) => vec![c],
Self::LineString(cs) => cs.iter().collect(),
Self::Polygon {
exterior,
interiors,
} => exterior
.0
.iter()
.chain(interiors.iter().flat_map(|r| r.0.iter()))
.collect(),
Self::MultiPoint(cs) => cs.iter().collect(),
Self::MultiLineString(ls) => ls.iter().flat_map(|l| l.iter()).collect(),
Self::MultiPolygon(ps) => ps
.iter()
.flat_map(|(e, hs)| e.0.iter().chain(hs.iter().flat_map(|r| r.0.iter())))
.collect(),
Self::GeometryCollection(gs) => gs.iter().flat_map(|g| g.all_coords()).collect(),
}
}
pub fn bbox(&self) -> Option<BBox> {
let cs = self.all_coords();
if cs.is_empty() {
return None;
}
let mut b = BBox {
min_x: f64::INFINITY,
min_y: f64::INFINITY,
max_x: f64::NEG_INFINITY,
max_y: f64::NEG_INFINITY,
};
for c in cs {
b.min_x = b.min_x.min(c.x);
b.max_x = b.max_x.max(c.x);
b.min_y = b.min_y.min(c.y);
b.max_y = b.max_y.max(c.y);
}
Some(b)
}
pub fn to_wkt(&self) -> String {
let mut s = String::new();
wkt_write(&mut s, self);
s
}
pub fn to_wkb(&self) -> Vec<u8> {
let mut out = Vec::new();
wkb_write(&mut out, self);
out
}
pub fn from_wkb(data: &[u8]) -> Result<Self> {
wkb_read(&mut Cursor::new(data))
}
pub fn to_gpkg_wkb(&self, srs_id: i32) -> Vec<u8> {
let bb = self.bbox();
let env_flag: u8 = if bb.is_some() { 1 } else { 0 };
let mut out = Vec::new();
out.extend_from_slice(b"GP");
out.push(0x00); out.push((env_flag << 1) | 0x01); out.extend_from_slice(&srs_id.to_le_bytes());
if let Some(b) = bb {
out.extend_from_slice(&b.min_x.to_le_bytes());
out.extend_from_slice(&b.max_x.to_le_bytes());
out.extend_from_slice(&b.min_y.to_le_bytes());
out.extend_from_slice(&b.max_y.to_le_bytes());
}
wkb_write(&mut out, self);
out
}
pub fn from_gpkg_wkb(data: &[u8]) -> Result<(Self, i32)> {
if data.len() < 8 || &data[0..2] != b"GP" {
return Err(GeoError::InvalidWkb {
offset: 0,
msg: "missing GP magic".into(),
});
}
let flags = data[3];
let srs_id = i32::from_le_bytes(data[4..8].try_into().unwrap());
let env_type = (flags >> 1) & 0x07; let env_bytes: usize = match env_type {
0 => 0,
1 => 32,
2 | 3 => 48,
_ => 64,
};
let wkb_start = 8 + env_bytes;
let geom = Self::from_wkb(&data[wkb_start..])?;
Ok((geom, srs_id))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GeometryType {
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
GeometryCollection,
}
impl GeometryType {
pub fn as_str(self) -> &'static str {
match self {
Self::Point => "Point",
Self::LineString => "LineString",
Self::Polygon => "Polygon",
Self::MultiPoint => "MultiPoint",
Self::MultiLineString => "MultiLineString",
Self::MultiPolygon => "MultiPolygon",
Self::GeometryCollection => "GeometryCollection",
}
}
pub fn wkb_type(self) -> u32 {
match self {
Self::Point => 1,
Self::LineString => 2,
Self::Polygon => 3,
Self::MultiPoint => 4,
Self::MultiLineString => 5,
Self::MultiPolygon => 6,
Self::GeometryCollection => 7,
}
}
}
impl std::fmt::Display for GeometryType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BBox {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
}
impl BBox {
pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
Self {
min_x,
min_y,
max_x,
max_y,
}
}
pub fn width(&self) -> f64 {
self.max_x - self.min_x
}
pub fn height(&self) -> f64 {
self.max_y - self.min_y
}
pub fn center(&self) -> (f64, f64) {
(
(self.min_x + self.max_x) * 0.5,
(self.min_y + self.max_y) * 0.5,
)
}
pub fn expand_to(&mut self, other: &BBox) {
self.min_x = self.min_x.min(other.min_x);
self.min_y = self.min_y.min(other.min_y);
self.max_x = self.max_x.max(other.max_x);
self.max_y = self.max_y.max(other.max_y);
}
pub fn contains(&self, x: f64, y: f64) -> bool {
x >= self.min_x && x <= self.max_x && y >= self.min_y && y <= self.max_y
}
pub fn intersects(&self, o: &BBox) -> bool {
self.min_x <= o.max_x
&& self.max_x >= o.min_x
&& self.min_y <= o.max_y
&& self.max_y >= o.min_y
}
}
fn push_f64(out: &mut Vec<u8>, v: f64) {
out.extend_from_slice(&v.to_le_bytes());
}
fn push_u32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn wkb_write_coord(out: &mut Vec<u8>, c: &Coord, z: bool) {
push_f64(out, c.x);
push_f64(out, c.y);
if z {
push_f64(out, c.z.unwrap_or(0.0));
}
}
fn wkb_write_coords(out: &mut Vec<u8>, cs: &[Coord], z: bool) {
push_u32(out, cs.len() as u32);
for c in cs {
wkb_write_coord(out, c, z);
}
}
fn wkb_write_ring(out: &mut Vec<u8>, ring: &Ring, z: bool) {
let n = ring.0.len();
let closes = n > 0 && ring.0.first() != ring.0.last();
let count = n + if closes { 1 } else { 0 };
push_u32(out, count as u32);
for c in &ring.0 {
wkb_write_coord(out, c, z);
}
if closes && n > 0 {
wkb_write_coord(out, &ring.0[0], z);
}
}
pub(crate) fn wkb_write(out: &mut Vec<u8>, geom: &Geometry) {
let z = geom.has_z();
let base = geom.geom_type().wkb_type();
let wt = if z { base + 1000 } else { base };
out.push(1); push_u32(out, wt);
match geom {
Geometry::Point(c) => wkb_write_coord(out, c, z),
Geometry::LineString(cs) => wkb_write_coords(out, cs, z),
Geometry::Polygon {
exterior,
interiors,
} => {
push_u32(out, 1 + interiors.len() as u32);
wkb_write_ring(out, exterior, z);
for r in interiors {
wkb_write_ring(out, r, z);
}
}
Geometry::MultiPoint(cs) => {
push_u32(out, cs.len() as u32);
for c in cs {
wkb_write(out, &Geometry::Point(c.clone()));
}
}
Geometry::MultiLineString(ls) => {
push_u32(out, ls.len() as u32);
for l in ls {
wkb_write(out, &Geometry::LineString(l.clone()));
}
}
Geometry::MultiPolygon(ps) => {
push_u32(out, ps.len() as u32);
for (e, hs) in ps {
wkb_write(
out,
&Geometry::Polygon {
exterior: e.clone(),
interiors: hs.clone(),
},
);
}
}
Geometry::GeometryCollection(gs) => {
push_u32(out, gs.len() as u32);
for g in gs {
wkb_write(out, g);
}
}
}
}
fn read_exact<R: Read>(r: &mut R, buf: &mut [u8]) -> Result<()> {
r.read_exact(buf).map_err(GeoError::Io)
}
fn read_f64(r: &mut Cursor<&[u8]>, le: bool) -> Result<f64> {
let mut b = [0u8; 8];
read_exact(r, &mut b)?;
Ok(if le {
f64::from_le_bytes(b)
} else {
f64::from_be_bytes(b)
})
}
fn read_u32(r: &mut Cursor<&[u8]>, le: bool) -> Result<u32> {
let mut b = [0u8; 4];
read_exact(r, &mut b)?;
Ok(if le {
u32::from_le_bytes(b)
} else {
u32::from_be_bytes(b)
})
}
fn read_coord(r: &mut Cursor<&[u8]>, le: bool, z: bool, m: bool) -> Result<Coord> {
let x = read_f64(r, le)?;
let y = read_f64(r, le)?;
let zv = if z { Some(read_f64(r, le)?) } else { None };
let mv = if m { Some(read_f64(r, le)?) } else { None };
Ok(Coord { x, y, z: zv, m: mv })
}
fn read_coords(r: &mut Cursor<&[u8]>, le: bool, z: bool, m: bool) -> Result<Vec<Coord>> {
let n = read_u32(r, le)? as usize;
(0..n).map(|_| read_coord(r, le, z, m)).collect()
}
fn read_ring(r: &mut Cursor<&[u8]>, le: bool, z: bool, m: bool) -> Result<Ring> {
let mut cs = read_coords(r, le, z, m)?;
if cs.len() > 1 && cs.first() == cs.last() {
cs.pop();
}
Ok(Ring::new(cs))
}
fn wkb_read(r: &mut Cursor<&[u8]>) -> Result<Geometry> {
let mut bo = [0u8];
read_exact(r, &mut bo)?;
let le = bo[0] == 1;
let raw_type = read_u32(r, le)?;
let (base, z, m) = if raw_type & 0xFFFF_0000 != 0 {
let z = raw_type & 0x8000_0000 != 0;
let m = raw_type & 0x4000_0000 != 0;
(raw_type & 0x0000_FFFF, z, m)
} else if raw_type > 3000 {
(raw_type - 3000, true, true)
} else if raw_type > 2000 {
(raw_type - 2000, false, true)
} else if raw_type > 1000 {
(raw_type - 1000, true, false)
} else {
(raw_type, false, false)
};
match base {
1 => Ok(Geometry::Point(read_coord(r, le, z, m)?)),
2 => Ok(Geometry::LineString(read_coords(r, le, z, m)?)),
3 => {
let n = read_u32(r, le)? as usize;
if n == 0 {
return Ok(Geometry::Polygon {
exterior: Ring::default(),
interiors: vec![],
});
}
let exterior = read_ring(r, le, z, m)?;
let interiors = (1..n)
.map(|_| read_ring(r, le, z, m))
.collect::<Result<_>>()?;
Ok(Geometry::Polygon {
exterior,
interiors,
})
}
4 => {
let n = read_u32(r, le)? as usize;
let pts: Result<Vec<Coord>> = (0..n)
.map(|_| {
let g = wkb_read(r)?;
if let Geometry::Point(c) = g {
Ok(c)
} else {
Err(GeoError::InvalidWkb {
offset: 0,
msg: "MultiPoint child not Point".into(),
})
}
})
.collect();
Ok(Geometry::MultiPoint(pts?))
}
5 => {
let n = read_u32(r, le)? as usize;
let lines: Result<Vec<Vec<Coord>>> = (0..n)
.map(|_| {
let g = wkb_read(r)?;
if let Geometry::LineString(cs) = g {
Ok(cs)
} else {
Err(GeoError::InvalidWkb {
offset: 0,
msg: "MultiLineString child not LineString".into(),
})
}
})
.collect();
Ok(Geometry::MultiLineString(lines?))
}
6 => {
let n = read_u32(r, le)? as usize;
let polys: Result<Vec<(Ring, Vec<Ring>)>> = (0..n)
.map(|_| {
let g = wkb_read(r)?;
if let Geometry::Polygon {
exterior,
interiors,
} = g
{
Ok((exterior, interiors))
} else {
Err(GeoError::InvalidWkb {
offset: 0,
msg: "MultiPolygon child not Polygon".into(),
})
}
})
.collect();
Ok(Geometry::MultiPolygon(polys?))
}
7 => {
let n = read_u32(r, le)? as usize;
let gs: Result<Vec<Geometry>> = (0..n).map(|_| wkb_read(r)).collect();
Ok(Geometry::GeometryCollection(gs?))
}
other => Err(GeoError::UnsupportedWkbType(other)),
}
}
fn wkt_coord(s: &mut String, c: &Coord) {
s.push_str(&format!("{} {}", c.x, c.y));
if let Some(z) = c.z {
s.push_str(&format!(" {}", z));
}
}
fn wkt_coords(s: &mut String, cs: &[Coord]) {
for (i, c) in cs.iter().enumerate() {
if i > 0 {
s.push(',');
}
wkt_coord(s, c);
}
}
fn wkt_ring(s: &mut String, ring: &Ring) {
s.push('(');
wkt_coords(s, &ring.0);
if let Some(f) = ring.0.first() {
s.push(',');
wkt_coord(s, f);
}
s.push(')');
}
fn wkt_write(s: &mut String, g: &Geometry) {
match g {
Geometry::Point(c) => {
s.push_str("POINT(");
wkt_coord(s, c);
s.push(')');
}
Geometry::LineString(cs) => {
s.push_str("LINESTRING(");
wkt_coords(s, cs);
s.push(')');
}
Geometry::Polygon {
exterior,
interiors,
} => {
s.push_str("POLYGON(");
wkt_ring(s, exterior);
for r in interiors {
s.push(',');
wkt_ring(s, r);
}
s.push(')');
}
Geometry::MultiPoint(cs) => {
s.push_str("MULTIPOINT(");
for (i, c) in cs.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push('(');
wkt_coord(s, c);
s.push(')');
}
s.push(')');
}
Geometry::MultiLineString(ls) => {
s.push_str("MULTILINESTRING(");
for (i, l) in ls.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push('(');
wkt_coords(s, l);
s.push(')');
}
s.push(')');
}
Geometry::MultiPolygon(ps) => {
s.push_str("MULTIPOLYGON(");
for (i, (e, hs)) in ps.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push('(');
wkt_ring(s, e);
for h in hs {
s.push(',');
wkt_ring(s, h);
}
s.push(')');
}
s.push(')');
}
Geometry::GeometryCollection(gs) => {
s.push_str("GEOMETRYCOLLECTION(");
for (i, g) in gs.iter().enumerate() {
if i > 0 {
s.push(',');
}
wkt_write(s, g);
}
s.push(')');
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_wkb_roundtrip(g: Geometry) {
assert_eq!(Geometry::from_wkb(&g.to_wkb()).unwrap(), g);
}
#[test]
fn wkb_point() {
assert_wkb_roundtrip(Geometry::point(1.5, 2.5));
}
#[test]
fn wkb_point_z() {
assert_wkb_roundtrip(Geometry::point_z(1.0, 2.0, 3.0));
}
#[test]
fn wkb_linestring() {
assert_wkb_roundtrip(Geometry::line_string(vec![
Coord::xy(0., 0.),
Coord::xy(1., 1.),
]));
}
#[test]
fn wkb_polygon() {
assert_wkb_roundtrip(Geometry::polygon(
vec![
Coord::xy(0., 0.),
Coord::xy(1., 0.),
Coord::xy(1., 1.),
Coord::xy(0., 1.),
],
vec![],
));
}
#[test]
fn wkb_multipolygon() {
assert_wkb_roundtrip(Geometry::multi_polygon(vec![
(
vec![Coord::xy(0., 0.), Coord::xy(1., 0.), Coord::xy(0.5, 1.)],
vec![],
),
(
vec![Coord::xy(2., 0.), Coord::xy(3., 0.), Coord::xy(2.5, 1.)],
vec![],
),
]));
}
#[test]
fn gpkg_roundtrip() {
let g = Geometry::polygon(
vec![
Coord::xy(-1., -1.),
Coord::xy(1., -1.),
Coord::xy(1., 1.),
Coord::xy(-1., 1.),
],
vec![],
);
let (g2, srs) = Geometry::from_gpkg_wkb(&g.to_gpkg_wkb(4326)).unwrap();
assert_eq!(g, g2);
assert_eq!(srs, 4326);
}
#[test]
fn bbox_polygon() {
let g = Geometry::polygon(
vec![
Coord::xy(1., 2.),
Coord::xy(3., 2.),
Coord::xy(3., 4.),
Coord::xy(1., 4.),
],
vec![],
);
let b = g.bbox().unwrap();
assert_eq!((b.min_x, b.max_y), (1.0, 4.0));
}
#[test]
fn wkt_point() {
assert_eq!(Geometry::point(10.0, 20.0).to_wkt(), "POINT(10 20)");
}
}