james-willis commented on code in PR #1027: URL: https://github.com/apache/sedona-db/pull/1027#discussion_r3590178325
########## rust/sedona-raster-functions/src/sampling.rs: ########## @@ -0,0 +1,229 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Point-sampling primitives shared by the raster value functions +//! (`RS_Value`, `RS_Values`). +//! +//! These helpers turn a geometry coordinate into a pixel read against a band's +//! [`NdBuffer`](sedona_raster::traits::NdBuffer): reprojecting into the raster's +//! CRS, mapping a world coordinate to a `(col, row)` index, and decoding the one +//! pixel there. They are geometry-shape agnostic — `RS_Value` drives them with a +//! single Point, `RS_Values` with each sub-point of a MultiPoint — so the pixel +//! math and CRS handling live in exactly one place. + +use arrow_array::ArrayRef; +use arrow_schema::DataType; +use datafusion_common::{exec_datafusion_err, exec_err, Result}; +use datafusion_expr::ColumnarValue; +use sedona_raster::affine_transformation::AffineMatrix; +use sedona_raster::traits::{nodata_bytes_to_f64_lossless, NdBuffer}; +use sedona_schema::crs::CrsRef; +use sedona_schema::datatypes::SedonaType; + +use crate::crs_utils::crs_transform_wkb; + +/// Materialise an integer argument as an owned `Int32` [`ArrayRef`] for the +/// batch. Callers keep the returned `ArrayRef` alive and borrow a typed +/// `&Int32Array` view from it (via `as_int32_array`) rather than cloning the +/// typed array. +pub(crate) fn int32_array_arg(arg: &ColumnarValue, num_iterations: usize) -> Result<ArrayRef> { + arg.clone() + .cast_to(&DataType::Int32, None)? + .into_array(num_iterations) +} + +/// Advance the optional band-number iterator one row, yielding the 1-based band +/// to sample. A missing band argument defaults to band 1; a NULL band element +/// returns `None`, which the caller propagates to a NULL result. Band 0 and +/// negative values map to 0 so [`Bands::band`](sedona_raster::traits::Bands::band) +/// rejects them as not 1-based rather than being silently coerced. +pub(crate) fn next_band( + band_iter: &mut Option<arrow_array::iterator::ArrayIter<&arrow_array::Int32Array>>, +) -> Option<usize> { + match band_iter.as_mut() { + None => Some(1), + Some(iter) => iter.next().flatten().map(|b| b.max(0) as usize), + } +} + +/// Resolve the 1-based band to sample when no band argument was given: band 1 +/// for a single-band raster, otherwise an error. Sampling an unspecified band of +/// a multiband raster is ambiguous, so the caller must name the band rather than +/// silently getting band 1 (matches `RS_SetBandNoDataValue`'s 2-argument form). +/// `func` names the calling UDF for the error message. +pub(crate) fn default_band(func: &str, num_bands: usize) -> Result<usize> { + if num_bands == 1 { + Ok(1) + } else { + exec_err!( + "{func}: raster has {num_bands} bands; specify which band to sample (the \ + 2-argument form is only allowed for a single-band raster)" + ) + } +} + +/// Reproject `wkb` from its CRS into the raster CRS, returning the transformed +/// WKB only when a reprojection actually happened (so the caller can sample the +/// original bytes otherwise — no allocation in the common case). +/// +/// Errors if exactly one of the geometry / raster carries a CRS: sampling across +/// a known and an unknown CRS would silently mislocate the geometry. +/// +/// Unlike the spatial predicates (`RS_Intersects` et al.), which fall back to a +/// WGS84 pivot when a direct transform between two CRSes fails, a failed +/// transform here is propagated as an error. Sampling has to land the geometry +/// in the raster's own CRS — that is the only space its affine/pixel grid is +/// defined in — so there is no neutral CRS to fall back to: a WGS84 pivot would +/// silently sample the wrong pixel rather than compare geometries in a shared +/// space. +/// +/// `func` names the calling UDF for the error messages. +pub(crate) fn reproject_wkb( + func: &str, + wkb: &[u8], + geom_crs: CrsRef<'_>, + raster_crs: CrsRef<'_>, + engine: &dyn sedona_geometry::transform::CrsEngine, +) -> Result<Option<Vec<u8>>> { Review Comment: done -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
