rusqlite_gpkg/gpkg/
feature.rs1use crate::Value;
2use crate::error::{GpkgError, Result};
3use rusqlite::types::Type;
4use std::collections::HashMap;
5use std::rc::Rc;
6use wkb::reader::Wkb;
7
8pub 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 pub fn id(&self) -> i64 {
31 self.id
32 }
33
34 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 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 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
107pub(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, 0b00000010 => 32, 0b00000100 => 48, 0b00000110 => 48, 0b00001000 => 64, _ => {
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
130pub(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, 0x50u8, 0x00u8, 0x01u8, ]);
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}