use crate::Value;
use crate::error::{GpkgError, Result};
use std::collections::HashMap;
use std::rc::Rc;
use wkb::reader::Wkb;
pub struct GpkgFeature {
pub(super) id: i64,
pub(super) geometry: Option<Vec<u8>>,
pub(super) properties: Vec<Value>,
pub(super) property_index_by_name: Rc<HashMap<String, usize>>,
}
impl GpkgFeature {
pub fn id(&self) -> i64 {
self.id
}
pub fn geometry(&self) -> Result<Wkb<'_>> {
let bytes = self.geometry.as_ref().ok_or(GpkgError::NullGeometryValue)?;
gpkg_geometry_to_wkb(bytes)
}
pub fn property(&self, name: &str) -> Option<Value> {
match self.property_index_by_name.get(name) {
Some(idx) => self.properties.get(*idx).cloned(),
None => None,
}
}
pub fn properties(&self) -> &[Value] {
&self.properties
}
#[cfg(test)]
fn new<G, I>(id: i64, geometry: G, properties: I, property_names: &[&str]) -> Result<Self>
where
G: geo_traits::GeometryTrait<T = f64>,
I: IntoIterator<Item = Value>,
{
let mut buf = Vec::new();
wkb::writer::write_geometry(&mut buf, &geometry, &Default::default())?;
let mut property_index_by_name = HashMap::with_capacity(property_names.len());
for (idx, name) in property_names.iter().enumerate() {
property_index_by_name.insert((*name).to_string(), idx);
}
Ok(Self {
id,
geometry: Some(buf),
properties: properties.into_iter().collect(),
property_index_by_name: Rc::new(property_index_by_name),
})
}
}
pub(crate) fn gpkg_geometry_to_wkb_bytes(b: &[u8]) -> Result<&[u8]> {
if b.len() < 8 {
return Err(GpkgError::InvalidGpkgGeometryLength {
len: b.len(),
minimum: 8,
});
}
let flags = b[3];
let envelope_size: usize = match flags & 0b00001110 {
0b00000000 => 0, 0b00000010 => 32, 0b00000100 => 48, 0b00000110 => 48, 0b00001000 => 64, _ => {
return Err(GpkgError::InvalidGpkgGeometryFlags(flags));
}
};
let offset = 8 + envelope_size;
if b.len() < offset {
return Err(GpkgError::InvalidGpkgGeometryEnvelope {
len: b.len(),
required: offset,
});
}
Ok(&b[offset..])
}
pub(crate) fn gpkg_geometry_to_wkb<'a>(b: &'a [u8]) -> Result<Wkb<'a>> {
Ok(Wkb::try_new(gpkg_geometry_to_wkb_bytes(b)?)?)
}
pub(crate) fn wkb_to_gpkg_geometry<'a>(wkb: Wkb<'a>, srs_id: u32) -> Result<Vec<u8>> {
let mut geom = Vec::with_capacity(wkb.buf().len() + 8);
geom.extend_from_slice(&[
0x47u8, 0x50u8, 0x00u8, 0x01u8, ]);
geom.extend_from_slice(&srs_id.to_le_bytes());
geom.extend_from_slice(wkb.buf());
Ok(geom)
}
#[cfg(test)]
mod tests {
use super::{gpkg_geometry_to_wkb, wkb_to_gpkg_geometry};
use crate::Result;
use crate::Value;
use geo_types::Point;
use wkb::reader::Wkb;
#[test]
fn gpkg_geometry_roundtrip() -> Result<()> {
let point = Point::new(3.0, -1.0);
let mut buf = Vec::new();
wkb::writer::write_geometry(&mut buf, &point, &Default::default())?;
let wkb = Wkb::try_new(&buf)?;
let expected = wkb.buf().to_vec();
let gpkg_blob = wkb_to_gpkg_geometry(wkb, 4326)?;
let recovered = gpkg_geometry_to_wkb(&gpkg_blob)?;
assert_eq!(recovered.buf(), expected.as_slice());
Ok(())
}
#[test]
fn gpkg_geometry_rejects_invalid_flags() {
let mut blob = vec![0x47, 0x50, 0x00, 0x0A, 0, 0, 0, 0];
blob.extend_from_slice(&[0; 16]);
let result = gpkg_geometry_to_wkb(&blob);
assert!(matches!(
result,
Err(crate::error::GpkgError::InvalidGpkgGeometryFlags(_))
));
}
#[test]
fn gpkg_geometry_rejects_too_short_header() {
let blob = vec![0x47, 0x50, 0x00, 0x01, 0, 0, 0];
let result = gpkg_geometry_to_wkb(&blob);
assert!(matches!(
result,
Err(crate::error::GpkgError::InvalidGpkgGeometryLength { len: 7, minimum: 8 })
));
}
#[test]
fn gpkg_geometry_rejects_too_short_envelope_payload() {
let mut blob = vec![0x47, 0x50, 0x00, 0x08, 0, 0, 0, 0];
blob.extend_from_slice(&[0; 32]);
let result = gpkg_geometry_to_wkb(&blob);
assert!(matches!(
result,
Err(crate::error::GpkgError::InvalidGpkgGeometryEnvelope {
len: 40,
required: 72
})
));
}
#[test]
fn property_invalid_index_reports_error() -> Result<()> {
let feature =
super::GpkgFeature::new(1, Point::new(0.0, 0.0), vec![Value::Integer(1)], &["value"])?;
let value = feature.property("missing");
assert!(value.is_none());
Ok(())
}
}