terrana 0.1.1

Zero-config spatial API server — point it at a CSV, Parquet, or GeoJSON file and get a REST API with spatial and geometry queries.
use crate::error::AppError;
use crate::server::AppState;
use axum::http::header;
use axum::response::{IntoResponse, Response};
use serde_json::{json, Value};

pub fn to_geojson_response(rows: &[Value], state: &AppState) -> Result<Response, AppError> {
    let snap = state.snapshot();
    let lat_col = &snap.schema.lat_col;
    let lon_col = &snap.schema.lon_col;

    let features: Vec<Value> = rows
        .iter()
        .filter_map(|row| {
            let obj = row.as_object()?;
            let lat = obj.get(lat_col).and_then(|v| v.as_f64())?;
            let lon = obj.get(lon_col).and_then(|v| v.as_f64())?;

            let mut properties = serde_json::Map::new();
            for (k, v) in obj {
                if k != lat_col && k != lon_col {
                    properties.insert(k.clone(), v.clone());
                }
            }

            Some(json!({
                "type": "Feature",
                "geometry": {
                    "type": "Point",
                    "coordinates": [lon, lat]
                },
                "properties": properties,
            }))
        })
        .collect();

    let fc = json!({
        "type": "FeatureCollection",
        "features": features,
    });

    let body = serde_json::to_string(&fc)
        .map_err(|e| AppError::Internal(anyhow::anyhow!("GeoJSON serialization error: {}", e)))?;

    Ok(([(header::CONTENT_TYPE, "application/geo+json")], body).into_response())
}