Skip to content

Commit c7a2668

Browse files
author
kontinuation
committed
Fix some obvious problems
1 parent 3ddacd2 commit c7a2668

4 files changed

Lines changed: 138 additions & 94 deletions

File tree

c/sedona-gdal/src/gdal.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
//! at every call site.
2424
2525
use crate::config;
26+
use crate::cpl::CslStringList;
2627
use crate::dataset::Dataset;
2728
use crate::driver::{Driver, DriverManager};
2829
use crate::errors::Result;
@@ -177,7 +178,7 @@ impl Gdal {
177178
&self,
178179
path: &str,
179180
recurse_depth: i32,
180-
options: Option<&crate::cpl::CslStringList>,
181+
options: Option<&CslStringList>,
181182
) -> Result<crate::vsi::VsiDir> {
182183
crate::vsi::open_dir(self.api, path, recurse_depth, options)
183184
}

c/sedona-gdal/src/vsi.rs

Lines changed: 121 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use std::ffi::CString;
2525
use std::ops::Deref;
2626

27+
use crate::cpl::CslStringList;
2728
use crate::errors::{GdalError, Result};
2829
use crate::gdal_api::{call_gdal_api, GdalApi};
2930

@@ -147,6 +148,36 @@ pub fn unlink_mem_file(api: &'static GdalApi, file_name: &str) -> Result<()> {
147148
Ok(())
148149
}
149150

151+
/// Return the directory separator for the specified path.
152+
///
153+
/// Default is forward slash. The only exception currently is the Windows
154+
/// file system which returns backslash, unless the specified path is of the
155+
/// form "{drive_letter}:/{rest_of_the_path}".
156+
///
157+
/// This function replicates the `VSIGetDirectorySeparator` function of GDAL 3.9+.
158+
/// We do not call the GDAL function directly since we want to be compatible with older
159+
/// GDAL versions.
160+
pub fn directory_separator_for_path(path: &str) -> &'static str {
161+
if path.starts_with("http://") || path.starts_with("https://") {
162+
"/"
163+
} else {
164+
#[cfg(windows)]
165+
{
166+
// Return forward slash for paths of the form
167+
// "{drive_letter}:/{rest_of_the_path}", and backslash otherwise.
168+
if path.len() >= 3 && path.as_bytes()[1] == b':' && path.as_bytes()[2] == b'/' {
169+
"/"
170+
} else {
171+
"\\"
172+
}
173+
}
174+
#[cfg(not(windows))]
175+
{
176+
"/"
177+
}
178+
}
179+
}
180+
150181
#[cfg(test)]
151182
pub(crate) fn with_memfile<T>(
152183
api: &'static GdalApi,
@@ -208,6 +239,80 @@ pub fn get_vsi_mem_file_bytes_owned(api: &'static GdalApi, file_name: &str) -> R
208239
Ok(buffer.as_ref().to_vec())
209240
}
210241

242+
pub struct VsiDirEntry {
243+
pub name: String,
244+
pub mode: Option<i32>,
245+
pub size: Option<crate::gdal_dyn_bindgen::vsi_l_offset>,
246+
pub mtime: Option<crate::gdal_dyn_bindgen::GIntBig>,
247+
}
248+
249+
pub struct VsiDir {
250+
api: &'static crate::gdal_api::GdalApi,
251+
handle: *mut crate::gdal_dyn_bindgen::VSIDIR,
252+
}
253+
254+
impl VsiDir {
255+
pub fn next_entry(&mut self) -> Option<VsiDirEntry> {
256+
let entry = unsafe { (self.api.inner.VSIGetNextDirEntry?)(self.handle) };
257+
if entry.is_null() {
258+
return None;
259+
}
260+
let entry = unsafe { &*entry };
261+
262+
let name = if entry.pszName.is_null() {
263+
String::new()
264+
} else {
265+
unsafe { std::ffi::CStr::from_ptr(entry.pszName) }
266+
.to_string_lossy()
267+
.into_owned()
268+
};
269+
270+
Some(VsiDirEntry {
271+
name,
272+
mode: (entry.bModeKnown != 0).then_some(entry.nMode),
273+
size: (entry.bSizeKnown != 0).then_some(entry.nSize),
274+
mtime: (entry.bMTimeKnown != 0).then_some(entry.nMTime),
275+
})
276+
}
277+
}
278+
279+
impl Iterator for VsiDir {
280+
type Item = VsiDirEntry;
281+
282+
fn next(&mut self) -> Option<Self::Item> {
283+
self.next_entry()
284+
}
285+
}
286+
287+
impl Drop for VsiDir {
288+
fn drop(&mut self) {
289+
if !self.handle.is_null() {
290+
if let Some(close) = self.api.inner.VSICloseDir {
291+
unsafe { close(self.handle) };
292+
}
293+
self.handle = std::ptr::null_mut();
294+
}
295+
}
296+
}
297+
298+
pub fn open_dir(
299+
api: &'static crate::gdal_api::GdalApi,
300+
path: &str,
301+
recurse_depth: i32,
302+
options: Option<&CslStringList>,
303+
) -> crate::errors::Result<VsiDir> {
304+
let c_path = std::ffi::CString::new(path)?;
305+
let options_ptr: *const *const std::os::raw::c_char = options
306+
.map(|opts| opts.as_ptr() as *const *const std::os::raw::c_char)
307+
.unwrap_or(std::ptr::null());
308+
let handle =
309+
unsafe { call_gdal_api!(api, VSIOpenDir, c_path.as_ptr(), recurse_depth, options_ptr) };
310+
if handle.is_null() {
311+
return Err(api.last_null_pointer_err("VSIOpenDir"));
312+
}
313+
Ok(VsiDir { api, handle })
314+
}
315+
211316
#[cfg(all(test, feature = "gdal-sys"))]
212317
mod tests {
213318
use super::*;
@@ -300,78 +405,24 @@ mod tests {
300405
})
301406
.unwrap();
302407
}
303-
}
304-
305-
pub struct VsiDirEntry {
306-
pub name: String,
307-
pub mode: Option<i32>,
308-
pub size: Option<crate::gdal_dyn_bindgen::vsi_l_offset>,
309-
pub mtime: Option<crate::gdal_dyn_bindgen::GIntBig>,
310-
}
311-
312-
pub struct VsiDir {
313-
api: &'static crate::gdal_api::GdalApi,
314-
handle: *mut crate::gdal_dyn_bindgen::VSIDIR,
315-
}
316408

317-
impl VsiDir {
318-
pub fn next_entry(&mut self) -> Option<VsiDirEntry> {
319-
let entry = unsafe { (self.api.inner.VSIGetNextDirEntry?)(self.handle) };
320-
if entry.is_null() {
321-
return None;
409+
#[test]
410+
fn test_directory_separator_for_path() {
411+
#[cfg(windows)]
412+
{
413+
assert_eq!(directory_separator_for_path("/vsis3/bucket/prefix"), r"\");
414+
assert_eq!(directory_separator_for_path("https://host/data.tif"), "/");
415+
assert_eq!(directory_separator_for_path(r"C:\data\dir"), r"\");
416+
assert_eq!(directory_separator_for_path(r"C:/data/dir"), "/");
417+
assert_eq!(directory_separator_for_path("/tmp/data"), r"\");
322418
}
323-
let entry = unsafe { &*entry };
324-
325-
let name = if entry.pszName.is_null() {
326-
String::new()
327-
} else {
328-
unsafe { std::ffi::CStr::from_ptr(entry.pszName) }
329-
.to_string_lossy()
330-
.into_owned()
331-
};
332-
333-
Some(VsiDirEntry {
334-
name,
335-
mode: (entry.bModeKnown != 0).then_some(entry.nMode),
336-
size: (entry.bSizeKnown != 0).then_some(entry.nSize),
337-
mtime: (entry.bMTimeKnown != 0).then_some(entry.nMTime),
338-
})
339-
}
340-
}
341-
342-
impl Iterator for VsiDir {
343-
type Item = VsiDirEntry;
344-
345-
fn next(&mut self) -> Option<Self::Item> {
346-
self.next_entry()
347-
}
348-
}
349-
350-
impl Drop for VsiDir {
351-
fn drop(&mut self) {
352-
if !self.handle.is_null() {
353-
if let Some(close) = self.api.inner.VSICloseDir {
354-
unsafe { close(self.handle) };
355-
}
356-
self.handle = std::ptr::null_mut();
419+
#[cfg(not(windows))]
420+
{
421+
assert_eq!(directory_separator_for_path("/vsis3/bucket/prefix"), "/");
422+
assert_eq!(directory_separator_for_path("https://host/data.tif"), "/");
423+
assert_eq!(directory_separator_for_path(r"C:\data\dir"), "/");
424+
assert_eq!(directory_separator_for_path(r"C:/data/dir"), "/");
425+
assert_eq!(directory_separator_for_path("/tmp/data"), "/");
357426
}
358427
}
359428
}
360-
361-
pub fn open_dir(
362-
api: &'static crate::gdal_api::GdalApi,
363-
path: &str,
364-
recurse_depth: i32,
365-
options: Option<&crate::cpl::CslStringList>,
366-
) -> crate::errors::Result<VsiDir> {
367-
let c_path = std::ffi::CString::new(path)?;
368-
let options_ptr: *const *const std::os::raw::c_char = options
369-
.map(|opts| opts.as_ptr() as *const *const std::os::raw::c_char)
370-
.unwrap_or(std::ptr::null());
371-
let handle =
372-
unsafe { (api.inner.VSIOpenDir.unwrap())(c_path.as_ptr(), recurse_depth, options_ptr) };
373-
if handle.is_null() {
374-
return Err(api.last_null_pointer_err("VSIOpenDir"));
375-
}
376-
Ok(VsiDir { api, handle })
377-
}

rust/sedona-geoparquet/src/statistics_accumulator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ impl GeoStatsAccumulator for GeographyGeoStatsAccumulator {
255255
#[cfg(test)]
256256
mod test {
257257
use super::*;
258+
#[cfg(feature = "s2geography")]
258259
use parquet::geospatial::bounding_box::BoundingBox;
259260
use sedona_schema::datatypes::{WKB_GEOGRAPHY, WKB_VIEW_GEOGRAPHY};
260261
use sedona_testing::create::create_scalar;

rust/sedona-raster-gdal/src/rs_geotiff_tiles.rs

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use std::sync::Arc;
2424
use arrow_array::{builder::StringBuilder, builder::UInt32Builder, ArrayRef, RecordBatch};
2525
use arrow_schema::{DataType, Field, Schema, SchemaRef};
2626
use async_trait::async_trait;
27-
use datafusion::catalog::TableFunctionImpl;
27+
use datafusion::catalog::{Session, TableFunctionImpl, TableProvider};
2828
use datafusion::execution::context::TaskContext;
2929
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
3030
use datafusion::physical_plan::expressions::Column;
@@ -45,6 +45,7 @@ use datafusion_common_runtime::SpawnedTask;
4545
use futures::{StreamExt, TryStreamExt};
4646
use sedona_gdal::gdal_dyn_bindgen::{VSI_S_IFMT, VSI_S_IFREG};
4747
use sedona_gdal::spatial_ref::SpatialRef;
48+
use sedona_gdal::vsi::directory_separator_for_path;
4849
use sedona_raster::builder::RasterBuilder;
4950
use sedona_raster::traits::{BandMetadata, RasterMetadata};
5051
use sedona_schema::raster::StorageType;
@@ -62,7 +63,7 @@ pub fn rs_geotiff_tiles_udtf() -> Arc<dyn TableFunctionImpl> {
6263
pub struct RsGeoTiffTilesFunction {}
6364

6465
impl TableFunctionImpl for RsGeoTiffTilesFunction {
65-
fn call(&self, exprs: &[Expr]) -> Result<Arc<dyn datafusion::catalog::TableProvider>> {
66+
fn call(&self, exprs: &[Expr]) -> Result<Arc<dyn TableProvider>> {
6667
if exprs.is_empty() || exprs.len() > 2 {
6768
return plan_err!(
6869
"rs_geotiff_tiles() expected 1 or 2 arguments (path[, recursive]) but got {}",
@@ -124,7 +125,7 @@ impl GeoTiffTilesProvider {
124125
}
125126

126127
#[async_trait]
127-
impl datafusion::catalog::TableProvider for GeoTiffTilesProvider {
128+
impl TableProvider for GeoTiffTilesProvider {
128129
fn as_any(&self) -> &dyn Any {
129130
self
130131
}
@@ -134,12 +135,12 @@ impl datafusion::catalog::TableProvider for GeoTiffTilesProvider {
134135
}
135136

136137
fn table_type(&self) -> TableType {
137-
TableType::View
138+
TableType::Base
138139
}
139140

140141
async fn scan(
141142
&self,
142-
_state: &dyn datafusion::catalog::Session,
143+
_state: &dyn Session,
143144
projection: Option<&Vec<usize>>,
144145
_filters: &[Expr],
145146
_limit: Option<usize>,
@@ -178,6 +179,7 @@ impl GeoTiffTilesExec {
178179
fn new(dir: String, recursive: bool, schema: SchemaRef) -> Self {
179180
let properties = PlanProperties::new(
180181
EquivalenceProperties::new(schema.clone()),
182+
// TODO: allow split paths to load into multiple partitions to enable parallelism.
181183
Partitioning::UnknownPartitioning(1),
182184
EmissionType::Incremental,
183185
Boundedness::Bounded,
@@ -232,9 +234,13 @@ impl ExecutionPlan for GeoTiffTilesExec {
232234

233235
fn execute(
234236
&self,
235-
_partition: usize,
237+
partition: usize,
236238
_context: Arc<TaskContext>,
237239
) -> Result<datafusion::physical_plan::SendableRecordBatchStream> {
240+
if partition != 0 {
241+
return exec_err!("GeoTiffTilesExec only has one partition (partition 0) but got partition {partition}");
242+
}
243+
238244
let schema_worker = self.schema.clone();
239245
let schema_empty = self.schema.clone();
240246
let schema_adapter = self.schema.clone();
@@ -248,7 +254,7 @@ impl ExecutionPlan for GeoTiffTilesExec {
248254
let schema = schema_worker.clone();
249255
SpawnedTask::spawn_blocking(move || build_batch_for_file(path, schema))
250256
})
251-
.buffered(4)
257+
.buffered(2)
252258
.map(move |res| match res {
253259
Ok(Ok(Some(batch))) => Ok(batch),
254260
Ok(Ok(None)) => Ok(RecordBatch::new_empty(schema_empty.clone())),
@@ -462,16 +468,6 @@ fn list_geotiffs(path: &str, recursive: bool) -> Result<Vec<String>> {
462468
}
463469
}
464470

465-
fn directory_separator_for_path(path: &str) -> &'static str {
466-
if path.starts_with("/vsi") || path.contains("://") {
467-
"/"
468-
} else if path.contains('\\') {
469-
"\\"
470-
} else {
471-
"/"
472-
}
473-
}
474-
475471
fn join_vsi_path(base: &str, separator: &str, child_name: &str) -> String {
476472
if base.ends_with(separator) {
477473
format!("{base}{child_name}")
@@ -506,11 +502,11 @@ fn div_ceil_u32(n: u32, d: u32) -> u32 {
506502
#[cfg(test)]
507503
mod tests {
508504
use super::*;
509-
use datafusion::catalog::TableProvider;
510505
use datafusion::prelude::SessionContext;
511506
use sedona_gdal::raster::types::Buffer;
512507
use std::path::PathBuf;
513508
use tempfile::tempdir;
509+
use TableProvider;
514510

515511
fn write_test_geotiff(base: &Path, name: &str) -> PathBuf {
516512
let path = base.join(name);
@@ -589,11 +585,6 @@ mod tests {
589585
assert!(is_geotiff_path_str("https://host/data.tif?token=abc#f"));
590586
assert!(!is_geotiff_path_str("/tmp/a.txt"));
591587
assert!(!is_geotiff_path_str("/tmp/a"));
592-
593-
assert_eq!(directory_separator_for_path("/vsis3/bucket/prefix"), "/");
594-
assert_eq!(directory_separator_for_path("https://host/data.tif"), "/");
595-
assert_eq!(directory_separator_for_path(r"C:\data\dir"), r"\");
596-
assert_eq!(directory_separator_for_path("/tmp/data"), "/");
597588
}
598589

599590
#[tokio::test]

0 commit comments

Comments
 (0)