use napi::bindgen_prelude::*;
use napi::tokio;
use napi_derive::napi;
use std::path::Path;
use crate::buffer::BufferWrapper;
use crate::error::NodeError;
use crate::raster::Dataset;
use crate::vector::FeatureCollection;
#[allow(dead_code)]
#[napi]
pub async fn open_raster_async(path: String) -> Result<Dataset> {
tokio::task::spawn_blocking(move || Dataset::open(path))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn save_raster_async(dataset: &Dataset, path: String) -> Result<()> {
let ds_clone = dataset.clone();
tokio::task::spawn_blocking(move || ds_clone.save(path))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn read_geojson_async(path: String) -> Result<FeatureCollection> {
tokio::task::spawn_blocking(move || {
let content = std::fs::read_to_string(&path).map_err(|e| NodeError {
code: "IO_ERROR".to_string(),
message: format!("Failed to read file: {}", e),
})?;
FeatureCollection::from_geojson(content)
})
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn write_geojson_async(path: String, collection: &FeatureCollection) -> Result<()> {
let content = collection.to_geojson()?;
tokio::task::spawn_blocking(move || {
std::fs::write(&path, content).map_err(|e| {
NodeError {
code: "IO_ERROR".to_string(),
message: format!("Failed to write file: {}", e),
}
.into()
})
})
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn resample_async(
buffer: &BufferWrapper,
new_width: u32,
new_height: u32,
method: crate::algorithms::ResamplingMethod,
) -> Result<BufferWrapper> {
let buffer_clone = buffer.clone();
tokio::task::spawn_blocking(move || {
crate::algorithms::resample(&buffer_clone, new_width, new_height, method)
})
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn hillshade_async(
dem: &BufferWrapper,
azimuth: f64,
altitude: f64,
z_factor: f64,
) -> Result<BufferWrapper> {
let dem_clone = dem.clone();
tokio::task::spawn_blocking(move || {
crate::algorithms::hillshade(&dem_clone, azimuth, altitude, z_factor)
})
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn slope_async(
dem: &BufferWrapper,
z_factor: f64,
as_percent: bool,
) -> Result<BufferWrapper> {
let dem_clone = dem.clone();
tokio::task::spawn_blocking(move || crate::algorithms::slope(&dem_clone, z_factor, as_percent))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn aspect_async(dem: &BufferWrapper) -> Result<BufferWrapper> {
let dem_clone = dem.clone();
tokio::task::spawn_blocking(move || crate::algorithms::aspect(&dem_clone))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn zonal_stats_async(
raster: &BufferWrapper,
zones: &BufferWrapper,
) -> Result<Vec<crate::algorithms::ZonalStatistics>> {
let raster_clone = raster.clone();
let zones_clone = zones.clone();
tokio::task::spawn_blocking(move || crate::algorithms::zonal_stats(&raster_clone, &zones_clone))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn buffer_async(
geometry: &crate::vector::GeometryWrapper,
distance: f64,
segments: u32,
) -> Result<crate::vector::GeometryWrapper> {
let geom_clone = geometry.clone();
tokio::task::spawn_blocking(move || crate::algorithms::buffer(&geom_clone, distance, segments))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn area_async(geometry: &crate::vector::GeometryWrapper, method: String) -> Result<f64> {
let geom_clone = geometry.clone();
tokio::task::spawn_blocking(move || crate::algorithms::area(&geom_clone, method))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn simplify_async(
geometry: &crate::vector::GeometryWrapper,
tolerance: f64,
method: String,
) -> Result<crate::vector::GeometryWrapper> {
let geom_clone = geometry.clone();
tokio::task::spawn_blocking(move || crate::algorithms::simplify(&geom_clone, tolerance, method))
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[allow(dead_code)]
#[napi]
pub async fn batch_process_rasters(
paths: Vec<String>,
output_dir: String,
operation: String,
) -> Result<Vec<String>> {
let mut tasks = Vec::new();
for path in paths {
let output_path = format!(
"{}/processed_{}",
output_dir,
Path::new(&path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("output.tif")
);
let op = operation.clone();
let task = tokio::task::spawn_blocking(move || -> Result<String> {
let dataset = Dataset::open(path)?;
let processed = match op.as_str() {
"identity" => dataset,
_ => {
return Err(NodeError {
code: "INVALID_OPERATION".to_string(),
message: format!("Unknown operation: {}", op),
}
.into());
}
};
processed.save(output_path.clone())?;
Ok(output_path)
});
tasks.push(task);
}
let mut results = Vec::new();
for task in tasks {
let result = task.await.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})??;
results.push(result);
}
Ok(results)
}
#[allow(dead_code)]
#[napi(ts_args_type = "callback: (progress: number) => void")]
pub fn set_progress_callback(_callback: Function<'_, Unknown<'_>>) -> Result<()> {
Ok(())
}
#[napi]
pub struct CancellationToken {
cancelled: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
#[napi]
impl CancellationToken {
#[napi(constructor)]
pub fn new() -> Self {
Self {
cancelled: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
#[napi]
pub fn cancel(&self) {
self.cancelled
.store(true, std::sync::atomic::Ordering::SeqCst);
}
#[napi]
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::SeqCst)
}
#[napi]
pub fn reset(&self) {
self.cancelled
.store(false, std::sync::atomic::Ordering::SeqCst);
}
}
#[allow(dead_code)]
#[napi(object)]
pub struct ParallelConfig {
pub num_threads: u32,
pub chunk_size: u32,
pub report_progress: bool,
}
impl Default for ParallelConfig {
fn default() -> Self {
Self {
num_threads: 0,
chunk_size: 1000,
report_progress: false,
}
}
}
#[allow(dead_code)]
#[napi]
pub async fn process_raster_parallel(
dataset: &Dataset,
operation: String,
config: Option<ParallelConfig>,
) -> Result<Dataset> {
let _cfg = config.unwrap_or_default();
let ds_clone = dataset.clone();
tokio::task::spawn_blocking(move || -> Result<Dataset> {
match operation.as_str() {
"identity" => Ok(ds_clone),
_ => Err(NodeError {
code: "INVALID_OPERATION".to_string(),
message: format!("Unknown operation: {}", operation),
}
.into()),
}
})
.await
.map_err(|e| NodeError {
code: "TASK_ERROR".to_string(),
message: format!("Task execution failed: {}", e),
})?
}
#[napi]
pub struct RasterStream {
dataset: Dataset,
current_row: u32,
chunk_height: u32,
}
#[napi]
impl RasterStream {
#[napi(constructor)]
pub fn new(dataset: &Dataset, chunk_height: u32) -> Self {
Self {
dataset: dataset.clone(),
current_row: 0,
chunk_height,
}
}
#[napi]
pub async unsafe fn read_next_chunk(&mut self) -> Result<Option<BufferWrapper>> {
if self.current_row >= self.dataset.height() {
return Ok(None);
}
let height = self
.chunk_height
.min(self.dataset.height() - self.current_row);
let chunk =
self.dataset
.read_window(0, 0, self.current_row, self.dataset.width(), height)?;
self.current_row += height;
Ok(Some(chunk))
}
#[napi]
pub unsafe fn reset(&mut self) {
self.current_row = 0;
}
#[napi]
pub fn progress(&self) -> f64 {
if self.dataset.height() == 0 {
1.0
} else {
self.current_row as f64 / self.dataset.height() as f64
}
}
}