use crate::error::GpkgError;
use crate::gpkg::GeoPackage;
#[derive(Debug, Clone, PartialEq)]
pub struct GeometryColumnDef {
pub table_name: String,
pub column_name: String,
pub geometry_type_name: String,
pub srs_id: i32,
pub has_z: bool,
pub has_m: bool,
}
impl GeometryColumnDef {
pub fn from_raw(
table_name: impl Into<String>,
column_name: impl Into<String>,
geometry_type_name: impl Into<String>,
srs_id: i32,
z_flag: u8,
m_flag: u8,
) -> Self {
Self {
table_name: table_name.into(),
column_name: column_name.into(),
geometry_type_name: geometry_type_name.into(),
srs_id,
has_z: z_flag > 0,
has_m: m_flag > 0,
}
}
pub fn z_flag(&self) -> u8 {
if self.has_z { 2 } else { 0 }
}
pub fn m_flag(&self) -> u8 {
if self.has_m { 2 } else { 0 }
}
}
#[derive(Debug, Clone)]
pub struct MultiGeomColumnSet {
pub table_name: String,
pub columns: Vec<GeometryColumnDef>,
}
impl MultiGeomColumnSet {
pub fn new(table_name: impl Into<String>) -> Self {
Self {
table_name: table_name.into(),
columns: Vec::new(),
}
}
pub fn primary(&self) -> Option<&GeometryColumnDef> {
self.columns.first()
}
pub fn find_by_name(&self, column_name: &str) -> Option<&GeometryColumnDef> {
self.columns.iter().find(|c| c.column_name == column_name)
}
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub fn has_multiple(&self) -> bool {
self.columns.len() > 1
}
}
pub fn load_geometry_columns_for_table(
reader: &GeoPackage,
table_name: &str,
) -> Result<Option<MultiGeomColumnSet>, GpkgError> {
let all_rows = match reader.scan_table_by_name("gpkg_geometry_columns")? {
Some(r) => r,
None => return Ok(None),
};
let mut column_set = MultiGeomColumnSet::new(table_name);
for (_rowid, values) in &all_rows {
if values.len() < 6 {
continue; }
let row_table_name = cell_to_string(&values[0]);
if row_table_name != table_name {
continue;
}
let def = decode_geometry_column_row(values);
column_set.columns.push(def);
}
if column_set.columns.is_empty() {
Ok(None)
} else {
Ok(Some(column_set))
}
}
pub fn load_all_geometry_columns(
reader: &GeoPackage,
) -> Result<Vec<MultiGeomColumnSet>, GpkgError> {
let all_rows = match reader.scan_table_by_name("gpkg_geometry_columns")? {
Some(r) => r,
None => return Ok(Vec::new()),
};
let mut table_order: Vec<String> = Vec::new();
let mut table_map: std::collections::HashMap<String, MultiGeomColumnSet> =
std::collections::HashMap::new();
for (_rowid, values) in &all_rows {
if values.len() < 6 {
continue;
}
let row_table_name = cell_to_string(&values[0]);
let def = decode_geometry_column_row(values);
if let Some(set) = table_map.get_mut(&row_table_name) {
set.columns.push(def);
} else {
table_order.push(row_table_name.clone());
let mut set = MultiGeomColumnSet::new(&row_table_name);
set.columns.push(def);
table_map.insert(row_table_name, set);
}
}
Ok(table_order
.into_iter()
.filter_map(|name| table_map.remove(&name))
.collect())
}
pub fn has_multiple_geometry_columns(
reader: &GeoPackage,
table_name: &str,
) -> Result<bool, GpkgError> {
match load_geometry_columns_for_table(reader, table_name)? {
Some(set) => Ok(set.has_multiple()),
None => Ok(false),
}
}
fn decode_geometry_column_row(values: &[crate::btree::CellValue]) -> GeometryColumnDef {
let table_name = cell_to_string(&values[0]);
let column_name = cell_to_string(&values[1]);
let geometry_type_name = cell_to_string(&values[2]);
let srs_id = cell_to_i32(&values[3]);
let z_flag = cell_to_u8(&values[4]);
let m_flag = cell_to_u8(&values[5]);
GeometryColumnDef::from_raw(
table_name,
column_name,
geometry_type_name,
srs_id,
z_flag,
m_flag,
)
}
fn cell_to_string(v: &crate::btree::CellValue) -> String {
use crate::btree::CellValue;
match v {
CellValue::Text(s) => s.clone(),
CellValue::Integer(i) => i.to_string(),
CellValue::Float(f) => f.to_string(),
CellValue::Blob(b) => String::from_utf8_lossy(b).into_owned(),
CellValue::Null => String::new(),
}
}
fn cell_to_i32(v: &crate::btree::CellValue) -> i32 {
use crate::btree::CellValue;
match v {
CellValue::Integer(i) => {
if *i > i32::MAX as i64 {
i32::MAX
} else if *i < i32::MIN as i64 {
i32::MIN
} else {
*i as i32
}
}
CellValue::Float(f) => *f as i32,
_ => 0,
}
}
fn cell_to_u8(v: &crate::btree::CellValue) -> u8 {
use crate::btree::CellValue;
match v {
CellValue::Integer(i) => {
if *i < 0 {
0
} else if *i > u8::MAX as i64 {
u8::MAX
} else {
*i as u8
}
}
CellValue::Float(f) => {
let i = *f as i64;
if i < 0 {
0
} else if i > u8::MAX as i64 {
u8::MAX
} else {
i as u8
}
}
_ => 0,
}
}