Skip to content

Commit 87497bf

Browse files
Add DriverManager::get_drivers_for_filename for the ability to auto detect compatible Drivers for writing data (#510)
* Add DriverManager::get_drivers_for_filename * Add docstring to DriverManager::get_drivers_for_filename; use match * Satisfy clippy * Improve readability for DriverManager::get_drivers_for_filename * only test for available drivers in DriverManager::get_driver_by_name * Add test for ESRI Shapefile for .shp file extension * bool for vector/raster in DriverManager::get_drivers_for_filename * Fix: modify test for gpkg.zip only for gdal v 3.7 onwards * Fix: modify test for shp.zip only for gdal v 3.1 onwards * Fixed the test failed due to Elasticsearch name capitalization change * Rename the function and minor changes * Use DriverIterator for looping through drivers * Make `DriverManager::all()` return an Iterator * Add function to get a single driver based on file extension * Use `AsRef<Path>` instead of `&str` in guess_driver(s)_for_write * Small cleanups * Fix test * Try to debug test * Remove debugging code * Rename methods and ignore case * Add PR link to CHANGES.md * Rename DriverProperties to DriverType * Fix: wrong PR link location --------- Co-authored-by: Laurențiu Nicola <[email protected]>
1 parent 82db2fe commit 87497bf

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
- Add `DriverIterator` format to iterate through drivers, as well as `DriverManager::all()` method that provides the iterator.
5+
- <https://github.com/georust/gdal/pull/512>
56

67
- **Breaking**: `Feature::set_field_xxx` now take `&mut self`
78
- <https://github.com/georust/gdal/pull/505>
@@ -10,6 +11,9 @@
1011

1112
- <https://github.com/georust/gdal/pull/504>
1213

14+
- Added `DriverManager::get_output_driver_for_dataset_name` and `DriverManager::get_output_drivers_for_dataset_name` for the ability to auto detect compatible `Driver`(s) for writing data.
15+
- <https://github.com/georust/gdal/pull/510>
16+
1317
- Added `Feature::unset_field`
1418

1519
- <https://github.com/georust/gdal/pull/503>

src/driver.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,137 @@ impl DriverManager {
410410
Ok(Driver { c_driver })
411411
}
412412

413+
/// Get one [`Driver`] that can create a file with the given name.
414+
///
415+
/// Searches for registered drivers that can create files and support
416+
/// the file extension or the connection prefix.
417+
///
418+
/// See also: [`get_driver_by_name`](Self::get_driver_by_name)
419+
/// and [`Dataset::open`](Dataset::open).
420+
///
421+
/// # Note
422+
///
423+
/// This functionality is implemented natively in GDAL 3.9, but this crate
424+
/// emulates it in previous versions.
425+
///
426+
/// # Example
427+
///
428+
/// ```rust, no_run
429+
/// use gdal::{DriverManager, DriverType};
430+
/// # fn main() -> gdal::errors::Result<()> {
431+
/// let compatible_driver =
432+
/// DriverManager::get_output_driver_for_dataset_name("test.gpkg", DriverType::Vector).unwrap();
433+
/// println!("{}", compatible_driver.short_name());
434+
/// # Ok(())
435+
/// # }
436+
/// ```
437+
/// ```text
438+
/// "GPKG"
439+
/// ```
440+
pub fn get_output_driver_for_dataset_name<P: AsRef<Path>>(
441+
filepath: P,
442+
properties: DriverType,
443+
) -> Option<Driver> {
444+
let mut drivers = Self::get_output_drivers_for_dataset_name(filepath, properties);
445+
drivers.next().map(|d| match d.short_name().as_str() {
446+
"GMT" => drivers
447+
.find(|d| d.short_name().eq_ignore_ascii_case("netCDF"))
448+
.unwrap_or(d),
449+
"COG" => drivers
450+
.find(|d| d.short_name().eq_ignore_ascii_case("GTiff"))
451+
.unwrap_or(d),
452+
_ => d,
453+
})
454+
}
455+
456+
/// Get the [`Driver`]s that can create a file with the given name.
457+
///
458+
/// Searches for registered drivers that can create files and support
459+
/// the file extension or the connection prefix.
460+
///
461+
/// See also: [`get_driver_by_name`](Self::get_driver_by_name)
462+
/// and [`Dataset::open`](Dataset::open).
463+
///
464+
/// # Note
465+
///
466+
/// This functionality is implemented natively in GDAL 3.9, but this crate
467+
/// emulates it in previous versions.
468+
///
469+
/// # Example
470+
///
471+
/// ```rust, no_run
472+
/// use gdal::{DriverManager, DriverType};
473+
/// # fn main() -> gdal::errors::Result<()> {
474+
/// let compatible_drivers =
475+
/// DriverManager::get_output_drivers_for_dataset_name("test.gpkg", DriverType::Vector)
476+
/// .map(|d| d.short_name())
477+
/// .collect::<Vec<String>>();
478+
/// println!("{:?}", compatible_drivers);
479+
/// # Ok(())
480+
/// # }
481+
/// ```
482+
/// ```text
483+
/// ["GPKG"]
484+
/// ```
485+
pub fn get_output_drivers_for_dataset_name<P: AsRef<Path>>(
486+
path: P,
487+
properties: DriverType,
488+
) -> impl Iterator<Item = Driver> {
489+
let path = path.as_ref();
490+
let path_lower = path.to_string_lossy().to_ascii_lowercase();
491+
492+
// NOTE: this isn't exactly correct for e.g. `.gpkg.zip`
493+
// (which is not a GPKG), but this code is going away.
494+
let ext = if path_lower.ends_with(".zip") {
495+
if path_lower.ends_with(".shp.zip") {
496+
"shp.zip".to_string()
497+
} else if path_lower.ends_with(".gpkg.zip") {
498+
"gpkg.zip".to_string()
499+
} else {
500+
"zip".to_string()
501+
}
502+
} else {
503+
Path::new(&path_lower)
504+
.extension()
505+
.map(|e| e.to_string_lossy().into_owned())
506+
.unwrap_or_default()
507+
};
508+
509+
DriverManager::all()
510+
.filter(move |d| {
511+
let can_create = d.metadata_item("DCAP_CREATE", "").is_some()
512+
|| d.metadata_item("DCAP_CREATECOPY", "").is_some();
513+
match properties {
514+
DriverType::Raster => {
515+
can_create && d.metadata_item("DCAP_RASTER", "").is_some()
516+
}
517+
DriverType::Vector => {
518+
(can_create && d.metadata_item("DCAP_VECTOR", "").is_some())
519+
|| d.metadata_item("DCAP_VECTOR_TRANSLATE_FROM", "").is_some()
520+
}
521+
}
522+
})
523+
.filter(move |d| {
524+
if let Some(e) = &d.metadata_item("DMD_EXTENSION", "") {
525+
if *e == ext {
526+
return true;
527+
}
528+
}
529+
if let Some(e) = d.metadata_item("DMD_EXTENSIONS", "") {
530+
if e.split(' ').any(|s| s == ext) {
531+
return true;
532+
}
533+
}
534+
535+
if let Some(pre) = d.metadata_item("DMD_CONNECTION_PREFIX", "") {
536+
if path_lower.starts_with(&pre.to_ascii_lowercase()) {
537+
return true;
538+
}
539+
}
540+
false
541+
})
542+
}
543+
413544
/// Register a driver for use.
414545
///
415546
/// Wraps [`GDALRegisterDriver()`](https://gdal.org/api/raster_c_api.html#_CPPv418GDALRegisterDriver11GDALDriverH)
@@ -461,6 +592,11 @@ impl DriverManager {
461592
}
462593
}
463594

595+
pub enum DriverType {
596+
Vector,
597+
Raster,
598+
}
599+
464600
/// Iterator for the registered [`Driver`]s in [`DriverManager`]
465601
pub struct DriverIterator {
466602
current: usize,
@@ -496,6 +632,84 @@ mod tests {
496632
assert!(DriverManager::get_driver(0).is_ok());
497633
}
498634

635+
#[test]
636+
fn test_driver_by_extension() {
637+
fn test_driver(d: &Driver, filename: &str, properties: DriverType) {
638+
assert_eq!(
639+
DriverManager::get_output_driver_for_dataset_name(filename, properties)
640+
.unwrap()
641+
.short_name(),
642+
d.short_name()
643+
);
644+
}
645+
646+
if let Ok(d) = DriverManager::get_driver_by_name("ESRI Shapefile") {
647+
test_driver(&d, "test.shp", DriverType::Vector);
648+
test_driver(&d, "my.test.shp", DriverType::Vector);
649+
// `shp.zip` only supported from gdal version 3.1
650+
// https://gdal.org/drivers/vector/shapefile.html#compressed-files
651+
if cfg!(all(major_ge_3, minor_ge_1)) {
652+
test_driver(&d, "test.shp.zip", DriverType::Vector);
653+
test_driver(&d, "my.test.shp.zip", DriverType::Vector);
654+
}
655+
}
656+
657+
if let Ok(d) = DriverManager::get_driver_by_name("GTiff") {
658+
test_driver(&d, "test.tiff", DriverType::Raster);
659+
test_driver(&d, "my.test.tiff", DriverType::Raster);
660+
}
661+
if let Ok(d) = DriverManager::get_driver_by_name("netCDF") {
662+
test_driver(&d, "test.nc", DriverType::Raster);
663+
}
664+
}
665+
666+
#[test]
667+
fn test_drivers_by_extension() {
668+
// convert the driver into short_name for testing purposes
669+
let drivers = |filename, is_vector| {
670+
DriverManager::get_output_drivers_for_dataset_name(
671+
filename,
672+
if is_vector {
673+
DriverType::Vector
674+
} else {
675+
DriverType::Raster
676+
},
677+
)
678+
.map(|d| d.short_name())
679+
.collect::<HashSet<String>>()
680+
};
681+
if DriverManager::get_driver_by_name("ESRI Shapefile").is_ok() {
682+
assert!(drivers("test.shp", true).contains("ESRI Shapefile"));
683+
assert!(drivers("my.test.shp", true).contains("ESRI Shapefile"));
684+
// `shp.zip` only supported from gdal version 3.1
685+
// https://gdal.org/drivers/vector/shapefile.html#compressed-files
686+
if cfg!(all(major_ge_3, minor_ge_1)) {
687+
assert!(drivers("test.shp.zip", true).contains("ESRI Shapefile"));
688+
assert!(drivers("my.test.shp.zip", true).contains("ESRI Shapefile"));
689+
}
690+
}
691+
if DriverManager::get_driver_by_name("GPKG").is_ok() {
692+
assert!(drivers("test.gpkg", true).contains("GPKG"));
693+
assert!(drivers("my.test.gpkg", true).contains("GPKG"));
694+
// `gpkg.zip` only supported from gdal version 3.7
695+
// https://gdal.org/drivers/vector/gpkg.html#compressed-files
696+
if cfg!(all(major_ge_3, minor_ge_7)) {
697+
assert!(drivers("test.gpkg.zip", true).contains("GPKG"));
698+
assert!(drivers("my.test.gpkg.zip", true).contains("GPKG"));
699+
}
700+
}
701+
if DriverManager::get_driver_by_name("GTiff").is_ok() {
702+
assert!(drivers("test.tiff", false).contains("GTiff"));
703+
assert!(drivers("my.test.tiff", false).contains("GTiff"));
704+
}
705+
if DriverManager::get_driver_by_name("netCDF").is_ok() {
706+
assert!(drivers("test.nc", false).contains("netCDF"));
707+
}
708+
if DriverManager::get_driver_by_name("PostgreSQL").is_ok() {
709+
assert!(drivers("PG:test", true).contains("PostgreSQL"));
710+
}
711+
}
712+
499713
#[test]
500714
fn test_driver_iterator() {
501715
assert_eq!(DriverManager::count(), DriverManager::all().count());

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub use dataset::Dataset;
129129
pub use geo_transform::{GeoTransform, GeoTransformEx};
130130
pub use options::{DatasetOptions, GdalOpenFlags};
131131

132-
pub use driver::{Driver, DriverManager};
132+
pub use driver::{Driver, DriverManager, DriverType};
133133
pub use gcp::{Gcp, GcpRef};
134134
#[cfg(any(major_ge_4, all(major_is_3, minor_ge_6)))]
135135
pub use gdal_sys::ArrowArrayStream;

0 commit comments

Comments
 (0)