use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use crate::io::wkb::{read_wkb, write_wkb};
use crate::validation::GeoValidation;
use crate::{MakeValid, MakeValidConfig, PolyMethod};
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn make_config(method: Option<&str>) -> MakeValidConfig {
let pm = match method.unwrap_or("auto").to_lowercase().as_str() {
"arrange" => PolyMethod::Arrange,
"structure" => PolyMethod::Structure,
_ => PolyMethod::Auto,
};
MakeValidConfig {
poly_method: pm,
..Default::default()
}
}
fn parse_wkb(wkb: &[u8]) -> PyResult<geo::Geometry<f64>> {
read_wkb(wkb).map_err(|e| PyValueError::new_err(format!("WKB parse error: {e}")))
}
fn repair_one(geom: geo::Geometry<f64>, config: &MakeValidConfig) -> geo::Geometry<f64> {
geom.make_valid_with_config(config)
}
#[pymodule]
#[pyo3(name = "geo_repair")]
fn geo_repair_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add("__version__", VERSION)?;
m.add_function(wrap_pyfunction!(repair_wkb, m)?)?;
m.add_function(wrap_pyfunction!(repair_wkb_batch, m)?)?;
m.add_function(wrap_pyfunction!(repair_validate_wkb_batch, m)?)?;
#[cfg(feature = "parallel")]
{
m.add_function(wrap_pyfunction!(par_repair_wkb_batch, m)?)?;
}
m.add_function(wrap_pyfunction!(is_valid_wkb, m)?)?;
m.add_function(wrap_pyfunction!(is_valid_wkb_batch, m)?)?;
m.add_function(wrap_pyfunction!(validate_wkb, m)?)?;
m.add_function(wrap_pyfunction!(validate_wkb_batch, m)?)?;
m.add_function(wrap_pyfunction!(validate_and_fix_wkb, m)?)?;
m.add_function(wrap_pyfunction!(validate_and_fix_wkb_batch, m)?)?;
Ok(())
}
#[pyfunction]
#[pyo3(signature = (wkb, method = None))]
fn repair_wkb(wkb: Vec<u8>, method: Option<&str>) -> PyResult<Vec<u8>> {
let config = make_config(method);
let geom = parse_wkb(&wkb)?;
let fixed = repair_one(geom, &config);
Ok(write_wkb(&fixed))
}
#[pyfunction]
#[pyo3(signature = (wkbs, method = None))]
fn repair_wkb_batch(wkbs: Vec<Vec<u8>>, method: Option<&str>) -> PyResult<Vec<Vec<u8>>> {
let config = make_config(method);
let mut results = Vec::with_capacity(wkbs.len());
for wkb in wkbs {
let r = match parse_wkb(&wkb) {
Ok(geom) => {
let fixed = repair_one(geom, &config);
write_wkb(&fixed)
}
Err(_) => wkb.to_vec(),
};
results.push(r);
}
Ok(results)
}
#[cfg(feature = "parallel")]
#[pyfunction]
#[pyo3(signature = (wkbs, method = None))]
fn par_repair_wkb_batch(wkbs: Vec<Vec<u8>>, method: Option<&str>) -> PyResult<Vec<Vec<u8>>> {
use rayon::prelude::*;
let config = make_config(method);
let results: Vec<Vec<u8>> = wkbs
.par_iter()
.map(|wkb| match parse_wkb(wkb) {
Ok(geom) => {
let fixed = repair_one(geom, &config);
write_wkb(&fixed)
}
Err(_) => wkb.to_vec(),
})
.collect();
Ok(results)
}
#[pyfunction]
fn is_valid_wkb(wkb: Vec<u8>) -> PyResult<bool> {
let geom = parse_wkb(&wkb)?;
Ok(geom.is_valid())
}
#[pyfunction]
fn validate_wkb(wkb: Vec<u8>) -> PyResult<(bool, Vec<String>)> {
let geom = parse_wkb(&wkb)?;
let valid = geom.is_valid();
let errors: Vec<String> = geom
.validate()
.errors
.iter()
.map(|e| format!("{e}"))
.collect();
Ok((valid, errors))
}
#[pyfunction]
#[pyo3(signature = (wkb, method = None))]
fn validate_and_fix_wkb(
wkb: Vec<u8>,
method: Option<&str>,
) -> PyResult<(bool, Vec<String>, Vec<u8>)> {
let config = make_config(method);
let geom = parse_wkb(&wkb)?;
let valid = geom.is_valid();
let errors: Vec<String> = geom
.validate()
.errors
.iter()
.map(|e| format!("{e}"))
.collect();
let fixed = repair_one(geom, &config);
Ok((valid, errors, write_wkb(&fixed)))
}
#[pyfunction]
fn is_valid_wkb_batch(wkbs: Vec<Vec<u8>>) -> PyResult<Vec<bool>> {
let mut results = Vec::with_capacity(wkbs.len());
for wkb in wkbs {
results.push(match parse_wkb(&wkb) {
Ok(geom) => geom.is_valid(),
Err(_) => false,
});
}
Ok(results)
}
#[pyfunction]
fn validate_wkb_batch(wkbs: Vec<Vec<u8>>) -> PyResult<Vec<(bool, Vec<String>)>> {
let mut results = Vec::with_capacity(wkbs.len());
for wkb in wkbs {
let r = match parse_wkb(&wkb) {
Ok(geom) => {
let valid = geom.is_valid();
let errors: Vec<String> = geom
.validate()
.errors
.iter()
.map(|e| format!("{e}"))
.collect();
(valid, errors)
}
Err(e) => (false, vec![format!("{e}")]),
};
results.push(r);
}
Ok(results)
}
#[pyfunction]
#[pyo3(signature = (wkbs, method = None))]
fn validate_and_fix_wkb_batch(
wkbs: Vec<Vec<u8>>,
method: Option<&str>,
) -> PyResult<Vec<(bool, Vec<String>, Vec<u8>)>> {
let config = make_config(method);
let mut results = Vec::with_capacity(wkbs.len());
for wkb in wkbs {
let r = match parse_wkb(&wkb) {
Ok(geom) => {
let valid = geom.is_valid();
let errors: Vec<String> = geom
.validate()
.errors
.iter()
.map(|e| format!("{e}"))
.collect();
let fixed = repair_one(geom, &config);
(valid, errors, write_wkb(&fixed))
}
Err(e) => (false, vec![format!("{e}")], wkb.to_vec()),
};
results.push(r);
}
Ok(results)
}
#[pyfunction]
#[pyo3(signature = (wkbs, method = None))]
fn repair_validate_wkb_batch(
wkbs: Vec<Vec<u8>>,
method: Option<&str>,
) -> PyResult<Vec<(Vec<u8>, bool, Vec<String>)>> {
let config = make_config(method);
let mut results = Vec::with_capacity(wkbs.len());
for wkb in wkbs {
let r = match parse_wkb(&wkb) {
Ok(geom) => {
let valid = geom.is_valid();
let errors: Vec<String> = geom
.validate()
.errors
.iter()
.map(|e| format!("{e}"))
.collect();
let fixed = repair_one(geom, &config);
(write_wkb(&fixed), valid, errors)
}
Err(e) => (wkb.to_vec(), false, vec![format!("{e}")]),
};
results.push(r);
}
Ok(results)
}