use crate::error::{GeoError, Result};
use crate::geometry::{BBox, Geometry, GeometryType};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
Integer, Float, Text, Boolean, Blob, Date, DateTime, Json, }
impl FieldType {
pub fn as_str(self) -> &'static str {
match self {
Self::Integer => "Integer",
Self::Float => "Float",
Self::Text => "Text",
Self::Boolean => "Boolean",
Self::Blob => "Blob",
Self::Date => "Date",
Self::DateTime => "DateTime",
Self::Json => "Json",
}
}
}
impl std::fmt::Display for FieldType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct FieldDef {
pub name: String,
pub field_type: FieldType,
pub nullable: bool,
pub width: usize,
pub precision: usize,
}
impl FieldDef {
pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
Self {
name: name.into(),
field_type,
nullable: true,
width: 0,
precision: 0,
}
}
pub fn not_null(mut self) -> Self {
self.nullable = false;
self
}
pub fn width(mut self, w: usize) -> Self {
self.width = w;
self
}
pub fn precision(mut self, p: usize) -> Self {
self.precision = p;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldValue {
Integer(i64),
Float(f64),
Text(String),
Boolean(bool),
Blob(Vec<u8>),
Date(String),
DateTime(String),
Null,
}
impl FieldValue {
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::Integer(v) => Some(*v),
Self::Float(v) => Some(*v as i64),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Float(v) => Some(*v),
Self::Integer(v) => Some(*v as f64),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Text(s) | Self::Date(s) | Self::DateTime(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn as_blob(&self) -> Option<&[u8]> {
match self {
Self::Blob(b) => Some(b.as_slice()),
_ => None,
}
}
pub fn widen_type(a: FieldType, b: FieldType) -> FieldType {
if a == b {
return a;
}
if matches!(
(a, b),
(FieldType::Integer, FieldType::Float) | (FieldType::Float, FieldType::Integer)
) {
return FieldType::Float;
}
FieldType::Text
}
}
impl From<i64> for FieldValue {
fn from(v: i64) -> Self {
Self::Integer(v)
}
}
impl From<i32> for FieldValue {
fn from(v: i32) -> Self {
Self::Integer(v as i64)
}
}
impl From<f64> for FieldValue {
fn from(v: f64) -> Self {
Self::Float(v)
}
}
impl From<f32> for FieldValue {
fn from(v: f32) -> Self {
Self::Float(v as f64)
}
}
impl From<bool> for FieldValue {
fn from(v: bool) -> Self {
Self::Boolean(v)
}
}
impl From<String> for FieldValue {
fn from(v: String) -> Self {
Self::Text(v)
}
}
impl From<&str> for FieldValue {
fn from(v: &str) -> Self {
Self::Text(v.to_owned())
}
}
impl std::fmt::Display for FieldValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Integer(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v}"),
Self::Text(v) => write!(f, "{v}"),
Self::Boolean(v) => write!(f, "{v}"),
Self::Blob(v) => write!(f, "<blob {} bytes>", v.len()),
Self::Date(v) => write!(f, "{v}"),
Self::DateTime(v) => write!(f, "{v}"),
Self::Null => write!(f, "NULL"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Schema {
fields: Vec<FieldDef>,
index: HashMap<String, usize>,
}
impl Schema {
pub fn new() -> Self {
Self::default()
}
pub fn add_field(&mut self, def: FieldDef) {
if self.index.contains_key(&def.name) {
return;
}
let i = self.fields.len();
self.index.insert(def.name.clone(), i);
self.fields.push(def);
}
pub fn upsert_field(&mut self, def: FieldDef) -> usize {
if let Some(&i) = self.index.get(&def.name) {
self.fields[i] = def;
i
} else {
let i = self.fields.len();
self.index.insert(def.name.clone(), i);
self.fields.push(def);
i
}
}
pub fn fields(&self) -> &[FieldDef] {
&self.fields
}
pub fn len(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn field_index(&self, name: &str) -> Option<usize> {
self.index.get(name).copied()
}
pub fn field(&self, name: &str) -> Option<&FieldDef> {
self.index.get(name).map(|&i| &self.fields[i])
}
}
#[derive(Debug, Clone)]
pub struct Feature {
pub fid: u64,
pub geometry: Option<Geometry>,
pub attributes: Vec<FieldValue>,
}
impl Feature {
pub fn new() -> Self {
Self {
fid: 0,
geometry: None,
attributes: Vec::new(),
}
}
pub fn with_geometry(fid: u64, geom: Geometry, n_fields: usize) -> Self {
Self {
fid,
geometry: Some(geom),
attributes: vec![FieldValue::Null; n_fields],
}
}
pub fn get_by_index(&self, idx: usize) -> Option<&FieldValue> {
self.attributes.get(idx)
}
pub fn set_by_index(&mut self, idx: usize, val: FieldValue) {
if idx >= self.attributes.len() {
self.attributes.resize(idx + 1, FieldValue::Null);
}
self.attributes[idx] = val;
}
pub fn get(&self, schema: &Schema, name: &str) -> Result<&FieldValue> {
let idx = schema
.field_index(name)
.ok_or_else(|| GeoError::FieldNotFound(name.to_owned()))?;
Ok(self.attributes.get(idx).unwrap_or(&FieldValue::Null))
}
pub fn set(&mut self, schema: &Schema, name: &str, val: FieldValue) -> Result<()> {
let idx = schema
.field_index(name)
.ok_or_else(|| GeoError::FieldNotFound(name.to_owned()))?;
self.set_by_index(idx, val);
Ok(())
}
pub fn attrs_map<'a>(&'a self, schema: &'a Schema) -> HashMap<&'a str, &'a FieldValue> {
schema
.fields()
.iter()
.enumerate()
.filter_map(|(i, fd)| self.attributes.get(i).map(|v| (fd.name.as_str(), v)))
.collect()
}
}
impl Default for Feature {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct Crs {
pub epsg: Option<u32>,
pub wkt: Option<String>,
}
impl Crs {
pub fn new() -> Self {
Self::default()
}
pub fn with_epsg(mut self, epsg: u32) -> Self {
self.epsg = Some(epsg);
self
}
pub fn with_wkt(mut self, wkt: impl Into<String>) -> Self {
self.wkt = Some(wkt.into());
self
}
}
#[derive(Debug, Clone)]
pub struct Layer {
pub name: String,
pub geom_type: Option<GeometryType>,
pub crs: Option<Crs>,
pub schema: Schema,
pub features: Vec<Feature>,
pub extent: Option<BBox>,
}
impl Layer {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
geom_type: None,
crs: None,
schema: Schema::new(),
features: Vec::new(),
extent: None,
}
}
pub fn with_geom_type(mut self, gt: GeometryType) -> Self {
self.geom_type = Some(gt);
self
}
pub fn with_epsg(self, epsg: u32) -> Self {
self.with_crs_epsg(epsg)
}
pub fn with_crs(mut self, crs: Crs) -> Self {
self.crs = Some(crs);
self
}
pub fn with_crs_epsg(mut self, epsg: u32) -> Self {
self.ensure_crs_mut().epsg = Some(epsg);
self
}
pub fn with_crs_wkt(mut self, wkt: impl Into<String>) -> Self {
self.ensure_crs_mut().wkt = Some(wkt.into());
self
}
pub fn crs_epsg(&self) -> Option<u32> {
self.crs.as_ref().and_then(|c| c.epsg)
}
pub fn crs_wkt(&self) -> Option<&str> {
self.crs.as_ref().and_then(|c| c.wkt.as_deref())
}
pub fn set_crs_epsg(&mut self, epsg: Option<u32>) {
if let Some(epsg_code) = epsg {
self.ensure_crs_mut().epsg = Some(epsg_code);
} else if let Some(crs) = self.crs.as_mut() {
crs.epsg = None;
if crs.wkt.is_none() {
self.crs = None;
}
}
}
pub fn set_crs_wkt(&mut self, wkt: Option<String>) {
if let Some(wkt_text) = wkt {
self.ensure_crs_mut().wkt = Some(wkt_text);
} else if let Some(crs) = self.crs.as_mut() {
crs.wkt = None;
if crs.epsg.is_none() {
self.crs = None;
}
}
}
pub fn assign_crs_epsg(&mut self, epsg: u32) {
self.crs = Some(Crs {
epsg: Some(epsg),
wkt: None,
});
}
pub fn assign_crs_wkt(&mut self, wkt: &str) {
self.crs = Some(Crs {
epsg: None,
wkt: Some(wkt.to_string()),
});
}
pub fn reproject_to_epsg(&self, dst_epsg: u32) -> Result<Self> {
crate::reproject::layer_to_epsg(self, dst_epsg)
}
pub fn reproject_to_epsg_with_options(
&self,
dst_epsg: u32,
options: &crate::reproject::VectorReprojectOptions,
) -> Result<Self> {
crate::reproject::layer_to_epsg_with_options(self, dst_epsg, options)
}
pub fn reproject_from_to_epsg(&self, src_epsg: u32, dst_epsg: u32) -> Result<Self> {
crate::reproject::layer_from_to_epsg(self, src_epsg, dst_epsg)
}
pub fn reproject_from_to_epsg_with_options(
&self,
src_epsg: u32,
dst_epsg: u32,
options: &crate::reproject::VectorReprojectOptions,
) -> Result<Self> {
crate::reproject::layer_from_to_epsg_with_options(self, src_epsg, dst_epsg, options)
}
fn ensure_crs_mut(&mut self) -> &mut Crs {
self.crs.get_or_insert_with(Crs::new)
}
pub fn add_field(&mut self, def: FieldDef) {
self.schema.add_field(def);
}
pub fn push(&mut self, f: Feature) {
self.features.push(f);
}
pub fn len(&self) -> usize {
self.features.len()
}
pub fn is_empty(&self) -> bool {
self.features.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Feature> {
self.features.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Feature> {
self.features.iter_mut()
}
pub fn add_feature(
&mut self,
geom: Option<Geometry>,
attrs: &[(&str, FieldValue)],
) -> Result<()> {
let n = self.schema.len();
let fid = self.features.len() as u64;
let mut f = Feature {
fid,
geometry: geom,
attributes: vec![FieldValue::Null; n],
};
for (name, val) in attrs {
f.set(&self.schema, name, val.clone())?;
}
self.features.push(f);
Ok(())
}
pub fn bbox(&mut self) -> Option<BBox> {
if self.extent.is_some() {
return self.extent.clone();
}
let mut bb: Option<BBox> = None;
for f in &self.features {
if let Some(g) = &f.geometry {
if let Some(fb) = g.bbox() {
bb = Some(match bb {
None => fb,
Some(mut e) => {
e.expand_to(&fb);
e
}
});
}
}
}
self.extent = bb.clone();
bb
}
pub fn features_in_bbox(&self, query: &BBox) -> Vec<&Feature> {
self.features
.iter()
.filter(|f| {
f.geometry
.as_ref()
.and_then(|g| g.bbox())
.map_or(false, |b| b.intersects(query))
})
.collect()
}
}
impl std::ops::Index<usize> for Layer {
type Output = Feature;
fn index(&self, i: usize) -> &Feature {
&self.features[i]
}
}
impl std::ops::IndexMut<usize> for Layer {
fn index_mut(&mut self, i: usize) -> &mut Feature {
&mut self.features[i]
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_layer() -> Layer {
let mut l = Layer::new("test")
.with_geom_type(GeometryType::Point)
.with_epsg(4326);
l.add_field(FieldDef::new("name", FieldType::Text));
l.add_field(FieldDef::new("value", FieldType::Float));
l.add_feature(
Some(Geometry::point(10.0, 20.0)),
&[("name", "alpha".into()), ("value", 3.14f64.into())],
)
.unwrap();
l.add_feature(
Some(Geometry::point(11.0, 21.0)),
&[("name", "beta".into()), ("value", 2.72f64.into())],
)
.unwrap();
l
}
use crate::geometry::Geometry;
#[test]
fn layer_len() {
assert_eq!(make_layer().len(), 2);
}
#[test]
fn get_field_by_name() {
let l = make_layer();
let v = l[0].get(&l.schema, "name").unwrap();
assert_eq!(v, &FieldValue::Text("alpha".into()));
}
#[test]
fn get_field_by_index() {
let l = make_layer();
assert!(l[0].get_by_index(1).unwrap().as_f64().unwrap() - 3.14 < 1e-9);
}
#[test]
fn bbox() {
let mut l = make_layer();
let b = l.bbox().unwrap();
assert_eq!(b.min_x, 10.0);
assert_eq!(b.max_x, 11.0);
}
#[test]
fn widen_type_int_float() {
assert_eq!(
FieldValue::widen_type(FieldType::Integer, FieldType::Float),
FieldType::Float
);
}
#[test]
fn widen_type_mismatch() {
assert_eq!(
FieldValue::widen_type(FieldType::Integer, FieldType::Text),
FieldType::Text
);
}
}