#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FieldType {
Integer,
Real,
Text,
Blob,
Boolean,
Date,
DateTime,
Null,
}
impl FieldType {
pub fn from_sql_type(type_str: &str) -> Self {
match type_str.to_ascii_uppercase().trim() {
"INTEGER" | "INT" | "TINYINT" | "SMALLINT" | "MEDIUMINT" | "BIGINT"
| "UNSIGNED BIG INT" | "INT2" | "INT8" => Self::Integer,
"REAL" | "DOUBLE" | "DOUBLE PRECISION" | "FLOAT" | "NUMERIC" | "DECIMAL" => Self::Real,
"BLOB" => Self::Blob,
"BOOLEAN" | "BOOL" => Self::Boolean,
"DATE" => Self::Date,
"DATETIME" | "TIMESTAMP" => Self::DateTime,
"NULL" => Self::Null,
_ => Self::Text,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Integer => "INTEGER",
Self::Real => "REAL",
Self::Text => "TEXT",
Self::Blob => "BLOB",
Self::Boolean => "BOOLEAN",
Self::Date => "DATE",
Self::DateTime => "DATETIME",
Self::Null => "NULL",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldValue {
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
Boolean(bool),
Null,
}
impl FieldValue {
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(v) => Some(*v),
_ => None,
}
}
pub fn as_real(&self) -> Option<f64> {
match self {
Self::Real(v) => Some(*v),
_ => None,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(s) => Some(s.as_str()),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Boolean(b) => Some(*b),
_ => None,
}
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn field_type(&self) -> FieldType {
match self {
Self::Integer(_) => FieldType::Integer,
Self::Real(_) => FieldType::Real,
Self::Text(_) => FieldType::Text,
Self::Blob(_) => FieldType::Blob,
Self::Boolean(_) => FieldType::Boolean,
Self::Null => FieldType::Null,
}
}
pub(crate) fn to_json(&self) -> String {
match self {
Self::Integer(v) => v.to_string(),
Self::Real(v) => {
if v.is_finite() {
format!("{v}")
} else {
"null".into()
}
}
Self::Text(s) => json_string_escape(s),
Self::Blob(b) => {
let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
json_string_escape(&format!("0x{hex}"))
}
Self::Boolean(b) => if *b { "true" } else { "false" }.into(),
Self::Null => "null".into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldDefinition {
pub name: String,
pub field_type: FieldType,
pub not_null: bool,
pub primary_key: bool,
pub default_value: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoordDim {
XY,
XYZ,
XYM,
XYZM,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Point4D {
pub x: f64,
pub y: f64,
pub z: Option<f64>,
pub m: Option<f64>,
}
impl Point4D {
pub fn to_xy(&self) -> (f64, f64) {
(self.x, self.y)
}
pub fn to_xyz(&self) -> (f64, f64, f64) {
(self.x, self.y, self.z.unwrap_or(0.0))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GpkgGeometry {
Point {
x: f64,
y: f64,
},
LineString {
coords: Vec<(f64, f64)>,
},
Polygon {
rings: Vec<Vec<(f64, f64)>>,
},
MultiPoint {
points: Vec<(f64, f64)>,
},
MultiLineString {
lines: Vec<Vec<(f64, f64)>>,
},
MultiPolygon {
polygons: Vec<Vec<Vec<(f64, f64)>>>,
},
GeometryCollection(Vec<GpkgGeometry>),
PointZ {
x: f64,
y: f64,
z: f64,
},
LineStringZ {
coords: Vec<(f64, f64, f64)>,
},
PolygonZ {
rings: Vec<Vec<(f64, f64, f64)>>,
},
MultiPointZ {
points: Vec<(f64, f64, f64)>,
},
MultiLineStringZ {
lines: Vec<Vec<(f64, f64, f64)>>,
},
MultiPolygonZ {
polygons: Vec<Vec<Vec<(f64, f64, f64)>>>,
},
GeometryCollectionZ(Vec<GpkgGeometry>),
PointM {
x: f64,
y: f64,
m: f64,
},
LineStringM {
coords: Vec<(f64, f64, f64)>,
},
PolygonM {
rings: Vec<Vec<(f64, f64, f64)>>,
},
MultiPointM {
points: Vec<(f64, f64, f64)>,
},
MultiLineStringM {
lines: Vec<Vec<(f64, f64, f64)>>,
},
MultiPolygonM {
polygons: Vec<Vec<Vec<(f64, f64, f64)>>>,
},
GeometryCollectionM(Vec<GpkgGeometry>),
PointZM(Point4D),
LineStringZM {
coords: Vec<Point4D>,
},
PolygonZM {
rings: Vec<Vec<Point4D>>,
},
MultiPointZM {
points: Vec<Point4D>,
},
MultiLineStringZM {
lines: Vec<Vec<Point4D>>,
},
MultiPolygonZM {
polygons: Vec<Vec<Vec<Point4D>>>,
},
GeometryCollectionZM(Vec<GpkgGeometry>),
Empty,
}
impl GpkgGeometry {
pub fn geometry_type(&self) -> &'static str {
match self {
Self::Point { .. } => "Point",
Self::LineString { .. } => "LineString",
Self::Polygon { .. } => "Polygon",
Self::MultiPoint { .. } => "MultiPoint",
Self::MultiLineString { .. } => "MultiLineString",
Self::MultiPolygon { .. } => "MultiPolygon",
Self::GeometryCollection(_) => "GeometryCollection",
Self::PointZ { .. } => "PointZ",
Self::LineStringZ { .. } => "LineStringZ",
Self::PolygonZ { .. } => "PolygonZ",
Self::MultiPointZ { .. } => "MultiPointZ",
Self::MultiLineStringZ { .. } => "MultiLineStringZ",
Self::MultiPolygonZ { .. } => "MultiPolygonZ",
Self::GeometryCollectionZ(_) => "GeometryCollectionZ",
Self::PointM { .. } => "PointM",
Self::LineStringM { .. } => "LineStringM",
Self::PolygonM { .. } => "PolygonM",
Self::MultiPointM { .. } => "MultiPointM",
Self::MultiLineStringM { .. } => "MultiLineStringM",
Self::MultiPolygonM { .. } => "MultiPolygonM",
Self::GeometryCollectionM(_) => "GeometryCollectionM",
Self::PointZM(_) => "PointZM",
Self::LineStringZM { .. } => "LineStringZM",
Self::PolygonZM { .. } => "PolygonZM",
Self::MultiPointZM { .. } => "MultiPointZM",
Self::MultiLineStringZM { .. } => "MultiLineStringZM",
Self::MultiPolygonZM { .. } => "MultiPolygonZM",
Self::GeometryCollectionZM(_) => "GeometryCollectionZM",
Self::Empty => "Empty",
}
}
pub fn point_count(&self) -> usize {
match self {
Self::Point { .. } | Self::PointZ { .. } | Self::PointM { .. } | Self::PointZM(_) => 1,
Self::LineString { coords } => coords.len(),
Self::LineStringZ { coords } => coords.len(),
Self::LineStringM { coords } => coords.len(),
Self::LineStringZM { coords } => coords.len(),
Self::Polygon { rings } => rings.iter().map(|r| r.len()).sum(),
Self::PolygonZ { rings } => rings.iter().map(|r| r.len()).sum(),
Self::PolygonM { rings } => rings.iter().map(|r| r.len()).sum(),
Self::PolygonZM { rings } => rings.iter().map(|r| r.len()).sum(),
Self::MultiPoint { points } => points.len(),
Self::MultiPointZ { points } => points.len(),
Self::MultiPointM { points } => points.len(),
Self::MultiPointZM { points } => points.len(),
Self::MultiLineString { lines } => lines.iter().map(|l| l.len()).sum(),
Self::MultiLineStringZ { lines } => lines.iter().map(|l| l.len()).sum(),
Self::MultiLineStringM { lines } => lines.iter().map(|l| l.len()).sum(),
Self::MultiLineStringZM { lines } => lines.iter().map(|l| l.len()).sum(),
Self::MultiPolygon { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter())
.map(|ring| ring.len())
.sum(),
Self::MultiPolygonZ { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter())
.map(|ring| ring.len())
.sum(),
Self::MultiPolygonM { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter())
.map(|ring| ring.len())
.sum(),
Self::MultiPolygonZM { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter())
.map(|ring| ring.len())
.sum(),
Self::GeometryCollection(geoms)
| Self::GeometryCollectionZ(geoms)
| Self::GeometryCollectionM(geoms)
| Self::GeometryCollectionZM(geoms) => geoms.iter().map(|g| g.point_count()).sum(),
Self::Empty => 0,
}
}
pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
let coords: Vec<(f64, f64)> = self.collect_coords();
if coords.is_empty() {
return None;
}
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for (x, y) in &coords {
if *x < min_x {
min_x = *x;
}
if *y < min_y {
min_y = *y;
}
if *x > max_x {
max_x = *x;
}
if *y > max_y {
max_y = *y;
}
}
if min_x.is_finite() {
Some((min_x, min_y, max_x, max_y))
} else {
None
}
}
fn collect_coords(&self) -> Vec<(f64, f64)> {
match self {
Self::Point { x, y } => vec![(*x, *y)],
Self::PointZ { x, y, .. } => vec![(*x, *y)],
Self::PointM { x, y, .. } => vec![(*x, *y)],
Self::PointZM(p) => vec![(p.x, p.y)],
Self::LineString { coords } => coords.clone(),
Self::LineStringZ { coords } => coords.iter().map(|(x, y, _)| (*x, *y)).collect(),
Self::LineStringM { coords } => coords.iter().map(|(x, y, _)| (*x, *y)).collect(),
Self::LineStringZM { coords } => coords.iter().map(|p| (p.x, p.y)).collect(),
Self::Polygon { rings } => rings.iter().flatten().copied().collect(),
Self::PolygonZ { rings } => rings.iter().flatten().map(|(x, y, _)| (*x, *y)).collect(),
Self::PolygonM { rings } => rings.iter().flatten().map(|(x, y, _)| (*x, *y)).collect(),
Self::PolygonZM { rings } => rings.iter().flatten().map(|p| (p.x, p.y)).collect(),
Self::MultiPoint { points } => points.clone(),
Self::MultiPointZ { points } => points.iter().map(|(x, y, _)| (*x, *y)).collect(),
Self::MultiPointM { points } => points.iter().map(|(x, y, _)| (*x, *y)).collect(),
Self::MultiPointZM { points } => points.iter().map(|p| (p.x, p.y)).collect(),
Self::MultiLineString { lines } => lines.iter().flatten().copied().collect(),
Self::MultiLineStringZ { lines } => {
lines.iter().flatten().map(|(x, y, _)| (*x, *y)).collect()
}
Self::MultiLineStringM { lines } => {
lines.iter().flatten().map(|(x, y, _)| (*x, *y)).collect()
}
Self::MultiLineStringZM { lines } => {
lines.iter().flatten().map(|p| (p.x, p.y)).collect()
}
Self::MultiPolygon { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter().flatten())
.copied()
.collect(),
Self::MultiPolygonZ { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter().flatten())
.map(|(x, y, _)| (*x, *y))
.collect(),
Self::MultiPolygonM { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter().flatten())
.map(|(x, y, _)| (*x, *y))
.collect(),
Self::MultiPolygonZM { polygons } => polygons
.iter()
.flat_map(|poly| poly.iter().flatten())
.map(|p| (p.x, p.y))
.collect(),
Self::GeometryCollection(geoms)
| Self::GeometryCollectionZ(geoms)
| Self::GeometryCollectionM(geoms)
| Self::GeometryCollectionZM(geoms) => {
geoms.iter().flat_map(|g| g.collect_coords()).collect()
}
Self::Empty => vec![],
}
}
pub(crate) fn to_geojson_geometry(&self) -> String {
match self {
Self::Point { x, y } => {
format!(r#"{{"type":"Point","coordinates":[{x},{y}]}}"#)
}
Self::PointZ { x, y, z } => {
format!(r#"{{"type":"Point","coordinates":[{x},{y},{z}]}}"#)
}
Self::LineString { coords } => {
let pts = coords_to_json_array(coords);
format!(r#"{{"type":"LineString","coordinates":{pts}}}"#)
}
Self::LineStringZ { coords } => {
let pts = coords_z_to_json_array(coords);
format!(r#"{{"type":"LineString","coordinates":{pts}}}"#)
}
Self::Polygon { rings } => {
let rings_json = rings
.iter()
.map(|r| coords_to_json_array(r))
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"Polygon","coordinates":[{rings_json}]}}"#)
}
Self::PolygonZ { rings } => {
let rings_json = rings
.iter()
.map(|r| coords_z_to_json_array(r))
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"Polygon","coordinates":[{rings_json}]}}"#)
}
Self::MultiPoint { points } => {
let pts = coords_to_json_array(points);
format!(r#"{{"type":"MultiPoint","coordinates":{pts}}}"#)
}
Self::MultiPointZ { points } => {
let pts = coords_z_to_json_array(points);
format!(r#"{{"type":"MultiPoint","coordinates":{pts}}}"#)
}
Self::MultiLineString { lines } => {
let lines_json = lines
.iter()
.map(|l| coords_to_json_array(l))
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiLineString","coordinates":[{lines_json}]}}"#)
}
Self::MultiLineStringZ { lines } => {
let lines_json = lines
.iter()
.map(|l| coords_z_to_json_array(l))
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiLineString","coordinates":[{lines_json}]}}"#)
}
Self::MultiPolygon { polygons } => {
let polys_json = polygons
.iter()
.map(|poly| {
let rings_json = poly
.iter()
.map(|r| coords_to_json_array(r))
.collect::<Vec<_>>()
.join(",");
format!("[{rings_json}]")
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiPolygon","coordinates":[{polys_json}]}}"#)
}
Self::MultiPolygonZ { polygons } => {
let polys_json = polygons
.iter()
.map(|poly| {
let rings_json = poly
.iter()
.map(|r| coords_z_to_json_array(r))
.collect::<Vec<_>>()
.join(",");
format!("[{rings_json}]")
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiPolygon","coordinates":[{polys_json}]}}"#)
}
Self::GeometryCollection(geoms)
| Self::GeometryCollectionZ(geoms)
| Self::GeometryCollectionM(geoms)
| Self::GeometryCollectionZM(geoms) => {
let geom_json = geoms
.iter()
.map(|g| g.to_geojson_geometry())
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"GeometryCollection","geometries":[{geom_json}]}}"#)
}
Self::PointM { x, y, .. } => {
format!(r#"{{"type":"Point","coordinates":[{x},{y}]}}"#)
}
Self::LineStringM { coords } => {
let pts = coords_to_json_array(
&coords.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>(),
);
format!(r#"{{"type":"LineString","coordinates":{pts}}}"#)
}
Self::PolygonM { rings } => {
let rings_json = rings
.iter()
.map(|r| {
coords_to_json_array(
&r.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"Polygon","coordinates":[{rings_json}]}}"#)
}
Self::MultiPointM { points } => {
let pts = coords_to_json_array(
&points.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>(),
);
format!(r#"{{"type":"MultiPoint","coordinates":{pts}}}"#)
}
Self::MultiLineStringM { lines } => {
let lines_json = lines
.iter()
.map(|l| {
coords_to_json_array(
&l.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiLineString","coordinates":[{lines_json}]}}"#)
}
Self::MultiPolygonM { polygons } => {
let polys_json = polygons
.iter()
.map(|poly| {
let rings_json = poly
.iter()
.map(|r| {
coords_to_json_array(
&r.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>()
.join(",");
format!("[{rings_json}]")
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiPolygon","coordinates":[{polys_json}]}}"#)
}
Self::PointZM(p) => {
let (x, y, z) = p.to_xyz();
format!(r#"{{"type":"Point","coordinates":[{x},{y},{z}]}}"#)
}
Self::LineStringZM { coords } => {
let pts =
coords_z_to_json_array(&coords.iter().map(|p| p.to_xyz()).collect::<Vec<_>>());
format!(r#"{{"type":"LineString","coordinates":{pts}}}"#)
}
Self::PolygonZM { rings } => {
let rings_json = rings
.iter()
.map(|r| {
coords_z_to_json_array(&r.iter().map(|p| p.to_xyz()).collect::<Vec<_>>())
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"Polygon","coordinates":[{rings_json}]}}"#)
}
Self::MultiPointZM { points } => {
let pts =
coords_z_to_json_array(&points.iter().map(|p| p.to_xyz()).collect::<Vec<_>>());
format!(r#"{{"type":"MultiPoint","coordinates":{pts}}}"#)
}
Self::MultiLineStringZM { lines } => {
let lines_json = lines
.iter()
.map(|l| {
coords_z_to_json_array(&l.iter().map(|p| p.to_xyz()).collect::<Vec<_>>())
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiLineString","coordinates":[{lines_json}]}}"#)
}
Self::MultiPolygonZM { polygons } => {
let polys_json = polygons
.iter()
.map(|poly| {
let rings_json = poly
.iter()
.map(|r| {
coords_z_to_json_array(
&r.iter().map(|p| p.to_xyz()).collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>()
.join(",");
format!("[{rings_json}]")
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"MultiPolygon","coordinates":[{polys_json}]}}"#)
}
Self::Empty => "null".into(),
}
}
}
pub(crate) fn json_string_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out.push('"');
out
}
pub(crate) fn coords_to_json_array(coords: &[(f64, f64)]) -> String {
let inner: String = coords
.iter()
.map(|(x, y)| format!("[{x},{y}]"))
.collect::<Vec<_>>()
.join(",");
format!("[{inner}]")
}
pub(crate) fn coords_z_to_json_array(coords: &[(f64, f64, f64)]) -> String {
let inner: String = coords
.iter()
.map(|(x, y, z)| format!("[{x},{y},{z}]"))
.collect::<Vec<_>>()
.join(",");
format!("[{inner}]")
}