rusqlite_gpkg/gpkg/
feature.rs

1use crate::Value;
2use crate::error::{GpkgError, Result};
3use rusqlite::types::Type;
4use std::collections::HashMap;
5use std::rc::Rc;
6use wkb::reader::Wkb;
7
8/// A single feature with geometry bytes and owned properties.
9pub struct GpkgFeature {
10    pub(super) id: i64,
11    pub(super) geometry: Option<Vec<u8>>,
12    pub(super) properties: Vec<Value>,
13    pub(super) property_index_by_name: Rc<HashMap<String, usize>>,
14}
15
16impl GpkgFeature {
17    /// Return the primary key value.
18    ///
19    /// Example:
20    /// ```no_run
21    /// use rusqlite_gpkg::Gpkg;
22    ///
23    /// let gpkg = Gpkg::open_read_only("data/example.gpkg")?;
24    /// let layer = gpkg.get_layer("points")?;
25    /// let features = layer.features()?;
26    /// let feature = features.first().expect("feature");
27    /// let _id = feature.id();
28    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
29    /// ```
30    pub fn id(&self) -> i64 {
31        self.id
32    }
33
34    /// Decode the geometry column into WKB.
35    ///
36    /// Example:
37    /// ```no_run
38    /// use rusqlite_gpkg::Gpkg;
39    ///
40    /// let gpkg = Gpkg::open_read_only("data/example.gpkg")?;
41    /// let layer = gpkg.get_layer("points")?;
42    /// let features = layer.features()?;
43    /// let feature = features.first().expect("feature");
44    /// let _geom = feature.geometry()?;
45    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
46    /// ```
47    pub fn geometry(&self) -> Result<Wkb<'_>> {
48        let bytes = self.geometry.as_ref().ok_or_else(|| {
49            GpkgError::Sql(rusqlite::Error::InvalidColumnType(
50                0,
51                "geometry".to_string(),
52                Type::Null,
53            ))
54        })?;
55        gpkg_geometry_to_wkb(bytes)
56    }
57
58    /// Read a property by name as an owned `Value`.
59    ///
60    /// Example:
61    /// ```no_run
62    /// use rusqlite_gpkg::Gpkg;
63    ///
64    /// let gpkg = Gpkg::open_read_only("data/example.gpkg")?;
65    /// let layer = gpkg.get_layer("points")?;
66    /// let features = layer.features()?;
67    /// let feature = features.first().expect("feature");
68    /// let value: String = feature
69    ///     .property("name")
70    ///     .ok_or("missing name")?
71    ///     .try_into()?;
72    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
73    /// ```
74    pub fn property(&self, name: &str) -> Option<Value> {
75        match self.property_index_by_name.get(name) {
76            Some(idx) => self.properties.get(*idx).cloned(),
77            None => None,
78        }
79    }
80
81    /// Return the ordered property values as stored in the feature.
82    pub fn properties(&self) -> &[Value] {
83        &self.properties
84    }
85
86    #[cfg(test)]
87    fn new<G, I>(id: i64, geometry: G, properties: I, property_names: &[&str]) -> Result<Self>
88    where
89        G: geo_traits::GeometryTrait<T = f64>,
90        I: IntoIterator<Item = Value>,
91    {
92        let mut buf = Vec::new();
93        wkb::writer::write_geometry(&mut buf, &geometry, &Default::default())?;
94        let mut property_index_by_name = HashMap::with_capacity(property_names.len());
95        for (idx, name) in property_names.iter().enumerate() {
96            property_index_by_name.insert((*name).to_string(), idx);
97        }
98        Ok(Self {
99            id,
100            geometry: Some(buf),
101            properties: properties.into_iter().collect(),
102            property_index_by_name: Rc::new(property_index_by_name),
103        })
104    }
105}
106
107/// Strip GeoPackage header and envelope bytes to access raw WKB.
108// cf. https://www.geopackage.org/spec140/index.html#gpb_format
109pub(crate) fn gpkg_geometry_to_wkb_bytes(b: &[u8]) -> Result<&[u8]> {
110    let flags = b[3];
111    let envelope_size: usize = match flags & 0b00001110 {
112        0b00000000 => 0,  // no envelope
113        0b00000010 => 32, // envelope is [minx, maxx, miny, maxy], 32 bytes
114        0b00000100 => 48, // envelope is [minx, maxx, miny, maxy, minz, maxz], 48 bytes
115        0b00000110 => 48, // envelope is [minx, maxx, miny, maxy, minm, maxm], 48 bytes
116        0b00001000 => 64, // envelope is [minx, maxx, miny, maxy, minz, maxz, minm, maxm], 64 bytes
117        _ => {
118            return Err(GpkgError::InvalidGpkgGeometryFlags(flags));
119        }
120    };
121    let offset = 8 + envelope_size;
122
123    Ok(&b[offset..])
124}
125
126pub(crate) fn gpkg_geometry_to_wkb<'a>(b: &'a [u8]) -> Result<Wkb<'a>> {
127    Ok(Wkb::try_new(gpkg_geometry_to_wkb_bytes(b)?)?)
128}
129
130// cf. https://www.geopackage.org/spec140/index.html#gpb_format
131pub(crate) fn wkb_to_gpkg_geometry<'a>(wkb: Wkb<'a>, srs_id: u32) -> Result<Vec<u8>> {
132    let mut geom = Vec::with_capacity(wkb.buf().len() + 8);
133    geom.extend_from_slice(&[
134        0x47u8, // magic
135        0x50u8, // magic
136        0x00u8, // version
137        0x01u8, // flags (little endian SRS ID, no envelope)
138    ]);
139    geom.extend_from_slice(&srs_id.to_le_bytes());
140    geom.extend_from_slice(wkb.buf());
141
142    Ok(geom)
143}
144
145#[cfg(test)]
146mod tests {
147    use super::{gpkg_geometry_to_wkb, wkb_to_gpkg_geometry};
148    use crate::Result;
149    use crate::Value;
150    use geo_types::Point;
151    use wkb::reader::Wkb;
152
153    #[test]
154    fn gpkg_geometry_roundtrip() -> Result<()> {
155        let point = Point::new(3.0, -1.0);
156        let mut buf = Vec::new();
157        wkb::writer::write_geometry(&mut buf, &point, &Default::default())?;
158        let wkb = Wkb::try_new(&buf)?;
159        let expected = wkb.buf().to_vec();
160        let gpkg_blob = wkb_to_gpkg_geometry(wkb, 4326)?;
161
162        let recovered = gpkg_geometry_to_wkb(&gpkg_blob)?;
163        assert_eq!(recovered.buf(), expected.as_slice());
164        Ok(())
165    }
166
167    #[test]
168    fn gpkg_geometry_rejects_invalid_flags() {
169        let mut blob = vec![0x47, 0x50, 0x00, 0x0A, 0, 0, 0, 0];
170        blob.extend_from_slice(&[0; 16]);
171        let result = gpkg_geometry_to_wkb(&blob);
172        assert!(matches!(
173            result,
174            Err(crate::error::GpkgError::InvalidGpkgGeometryFlags(_))
175        ));
176    }
177
178    #[test]
179    fn property_invalid_index_reports_error() -> Result<()> {
180        let feature =
181            super::GpkgFeature::new(1, Point::new(0.0, 0.0), vec![Value::Integer(1)], &["value"])?;
182        let value = feature.property("missing");
183        assert!(value.is_none());
184        Ok(())
185    }
186}