use std::fs;
use std::io::Read;
use geo::{Coord, Geometry, GeometryCollection, Polygon};
use crate::core::MakeValidConfig;
use crate::validation::{validate, ValidationResult};
use crate::MakeValid;
pub mod binary;
pub mod wkb;
pub mod wkt;
pub use binary::{load_bin, load_bin_stream, write_bin};
pub use wkb::{
estimate_wkb_size, read_ewkb, read_wkb, read_wkb_concat, read_wkb_from, write_ewkb, write_wkb,
write_wkb_to, write_wkb_with_opts, Endianness, EwkbDims, EwkbGeometry, WkbError, WriteOptions,
};
pub use wkt::{infer_wkt_type, read_wkt, read_wkt_from, write_wkt, write_wkt_to, WktError};
fn extension(path: &str) -> &str {
std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
}
pub fn load(path: &str) -> Result<Vec<Geometry<f64>>, String> {
let ext = extension(path).to_lowercase();
match ext.as_str() {
"bin" => {
let polys = load_bin(path)?;
Ok(polys.into_iter().map(Geometry::Polygon).collect())
}
"wkb" | "wks" => {
let mut buf = Vec::new();
fs::File::open(path)
.map_err(|e| format!("cannot open {path}: {e}"))?
.read_to_end(&mut buf)
.map_err(|e| format!("cannot read {path}: {e}"))?;
let geom = read_wkb(&buf).map_err(|e| format!("WKB parse error: {e}"))?;
Ok(vec![geom])
}
"shp" => {
#[cfg(feature = "io-shp")]
return load_shp(path);
#[cfg(not(feature = "io-shp"))]
Err(
"'.shp' requires feature 'io-shp': cargo add geo-repair --features io-shp"
.to_string(),
)
}
"wkt" => load_wkt(path),
"csv" => {
#[cfg(feature = "io-csv")]
return load_csv(path);
#[cfg(not(feature = "io-csv"))]
Err(
"'.csv' requires feature 'io-csv': cargo add geo-repair --features io-csv"
.to_string(),
)
}
"gml" => {
#[cfg(feature = "io-gml")]
return load_gml(path);
#[cfg(not(feature = "io-gml"))]
Err(
"'.gml' requires feature 'io-gml': cargo add geo-repair --features io-gml"
.to_string(),
)
}
"gpkg" => {
#[cfg(feature = "io-gpkg")]
return load_gpkg(path);
#[cfg(not(feature = "io-gpkg"))]
Err(
"'.gpkg' requires feature 'io-gpkg': cargo add geo-repair --features io-gpkg"
.to_string(),
)
}
other => Err(format!("unsupported format '.{other}' for '{path}'")),
}
}
pub fn save(path: &str, geom: &Geometry<f64>) -> Result<(), String> {
let ext = extension(path).to_lowercase();
match ext.as_str() {
"bin" => {
let polys = extract_polygons(geom);
write_bin(path, &polys)
}
"wkb" | "wks" => {
let bytes = write_wkb(geom);
fs::write(path, &bytes).map_err(|e| format!("cannot write {path}: {e}"))
}
"wkt" => {
let text = write_wkt(geom);
fs::write(path, &text).map_err(|e| format!("cannot write {path}: {e}"))
}
other => Err(format!(
"output format '.{other}' not yet supported for '{path}'"
)),
}
}
fn extract_polygons(geom: &Geometry<f64>) -> Vec<Polygon<f64>> {
match geom {
Geometry::Polygon(p) => vec![p.clone()],
Geometry::MultiPolygon(mp) => mp.0.clone(),
Geometry::GeometryCollection(gc) => gc.0.iter().flat_map(extract_polygons).collect(),
_ => Vec::new(),
}
}
pub fn repair_file(input: &str, output: &str, config: &MakeValidConfig) -> Result<(), String> {
let geoms = load(input)?;
let mut results = Vec::with_capacity(geoms.len());
for g in &geoms {
results.push(g.make_valid_with_config(config));
}
let result = if results.len() == 1 {
results.into_iter().next().unwrap()
} else {
Geometry::GeometryCollection(GeometryCollection(results))
};
save(output, &result)
}
pub fn diagnose_file(path: &str) -> Result<Vec<ValidationResult>, String> {
let geoms = load(path)?;
Ok(geoms.iter().map(validate).collect())
}
#[cfg(feature = "io-shp")]
fn load_shp(_path: &str) -> Result<Vec<Geometry<f64>>, String> {
Err("Shapefile backend not yet implemented".into())
}
fn load_wkt(path: &str) -> Result<Vec<Geometry<f64>>, String> {
let text = fs::read_to_string(path).map_err(|e| format!("cannot read {path}: {e}"))?;
let geom = read_wkt(&text).map_err(|e| e.to_string())?;
Ok(vec![geom])
}
#[cfg(feature = "io-csv")]
fn load_csv(_path: &str) -> Result<Vec<Geometry<f64>>, String> {
Err("CSV backend not yet implemented".into())
}
#[cfg(feature = "io-gml")]
fn load_gml(_path: &str) -> Result<Vec<Geometry<f64>>, String> {
Err("GML backend not yet implemented".into())
}
#[cfg(feature = "io-gpkg")]
fn load_gpkg(_path: &str) -> Result<Vec<Geometry<f64>>, String> {
Err("GeoPackage backend not yet implemented".into())
}
pub fn signed_area(ring: &[Coord<f64>]) -> f64 {
let mut s = 0.0;
for w in ring.windows(2) {
s += w[0].x * w[1].y - w[1].x * w[0].y;
}
s / 2.0
}
pub fn polygon_area(p: &Polygon<f64>) -> f64 {
signed_area(&p.exterior().0).abs()
}
pub fn geo_area(g: &Geometry<f64>) -> f64 {
match g {
Geometry::Polygon(p) => polygon_area(p),
Geometry::MultiPolygon(mp) => mp.0.iter().map(polygon_area).sum(),
_ => 0.0,
}
}
pub fn count_sub_polys(g: &Geometry<f64>) -> usize {
match g {
Geometry::Polygon(_) => 1,
Geometry::MultiPolygon(mp) => mp.0.len(),
_ => 0,
}
}