Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DriverManager::get_drivers_for_filename for the ability to auto detect compatible Drivers for writing data #510

Merged
merged 24 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5f44a24
Add DriverManager::get_drivers_for_filename
Atreyagaurav Dec 25, 2023
9fb996b
Add docstring to DriverManager::get_drivers_for_filename; use match
Atreyagaurav Dec 29, 2023
f7d6bcb
Satisfy clippy
Atreyagaurav Dec 29, 2023
3d569e0
Improve readability for DriverManager::get_drivers_for_filename
Atreyagaurav Jan 1, 2024
0b8d80d
only test for available drivers in DriverManager::get_driver_by_name
Atreyagaurav Jan 1, 2024
79300cd
Add test for ESRI Shapefile for .shp file extension
Atreyagaurav Jan 1, 2024
563eb97
bool for vector/raster in DriverManager::get_drivers_for_filename
Atreyagaurav Jan 1, 2024
62f7ccd
Fix: modify test for gpkg.zip only for gdal v 3.7 onwards
Atreyagaurav Jan 2, 2024
3dbc42f
Fix: modify test for shp.zip only for gdal v 3.1 onwards
Atreyagaurav Jan 2, 2024
05f46c4
Fixed the test failed due to Elasticsearch name capitalization change
Atreyagaurav Jan 2, 2024
7c90f62
Rename the function and minor changes
Atreyagaurav Jan 3, 2024
223b846
Merge branch 'master' of github.com:georust/gdal
Atreyagaurav Jan 5, 2024
2feaf29
Use DriverIterator for looping through drivers
Atreyagaurav Jan 5, 2024
5c32a2c
Make `DriverManager::all()` return an Iterator
Atreyagaurav Jan 5, 2024
ed5a991
Add function to get a single driver based on file extension
Atreyagaurav Jan 8, 2024
45473a6
Use `AsRef<Path>` instead of `&str` in guess_driver(s)_for_write
Atreyagaurav Jan 9, 2024
fc0361a
Small cleanups
lnicola Jan 10, 2024
2c1fc76
Fix test
lnicola Jan 10, 2024
773872d
Try to debug test
lnicola Jan 10, 2024
a187d38
Remove debugging code
lnicola Jan 10, 2024
f54cd81
Rename methods and ignore case
lnicola Jan 11, 2024
326feda
Add PR link to CHANGES.md
Atreyagaurav Jan 15, 2024
04a549c
Rename DriverProperties to DriverType
Atreyagaurav Jan 15, 2024
98fbc23
Fix: wrong PR link location
Atreyagaurav Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

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

- Added `DriverManager::guess_driver_for_write` and `DriverManager::guess_drivers_for_write` for the ability to auto detect compatible `Driver`(s) for writing data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to add the PR link here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Done.

- Added `Feature::unset_field`

- <https://github.com/georust/gdal/pull/503>
Expand Down
214 changes: 214 additions & 0 deletions src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,137 @@ impl DriverManager {
Ok(Driver { c_driver })
}

/// Get one [`Driver`] that can create a file with the given name.
///
/// Searches for registered drivers that can create files and support
/// the file extension or the connection prefix.
///
/// See also: [`get_driver_by_name`](Self::get_driver_by_name)
/// and [`Dataset::open`](Dataset::open).
///
/// # Note
///
/// This functionality is implemented natively in GDAL 3.9, but this crate
/// emulates it in previous versions.
///
/// # Example
///
/// ```rust, no_run
/// use gdal::{DriverManager, DriverProperties};
/// # fn main() -> gdal::errors::Result<()> {
/// let compatible_driver =
/// DriverManager::get_output_driver_for_dataset_name("test.gpkg", DriverProperties::Vector).unwrap();
/// println!("{}", compatible_driver.short_name());
/// # Ok(())
/// # }
/// ```
/// ```text
/// "GPKG"
/// ```
pub fn get_output_driver_for_dataset_name<P: AsRef<Path>>(
filepath: P,
properties: DriverProperties,
) -> Option<Driver> {
let mut drivers = Self::get_output_drivers_for_dataset_name(filepath, properties);
drivers.next().map(|d| match d.short_name().as_str() {
"GMT" => drivers
.find(|d| d.short_name().eq_ignore_ascii_case("netCDF"))
.unwrap_or(d),
"COG" => drivers
.find(|d| d.short_name().eq_ignore_ascii_case("GTiff"))
.unwrap_or(d),
_ => d,
})
}

/// Get the [`Driver`]s that can create a file with the given name.
///
/// Searches for registered drivers that can create files and support
/// the file extension or the connection prefix.
///
/// See also: [`get_driver_by_name`](Self::get_driver_by_name)
/// and [`Dataset::open`](Dataset::open).
///
/// # Note
///
/// This functionality is implemented natively in GDAL 3.9, but this crate
/// emulates it in previous versions.
///
/// # Example
///
/// ```rust, no_run
/// use gdal::{DriverManager, DriverProperties};
/// # fn main() -> gdal::errors::Result<()> {
/// let compatible_drivers =
/// DriverManager::get_output_drivers_for_dataset_name("test.gpkg", DriverProperties::Vector)
/// .map(|d| d.short_name())
/// .collect::<Vec<String>>();
/// println!("{:?}", compatible_drivers);
/// # Ok(())
/// # }
/// ```
/// ```text
/// ["GPKG"]
/// ```
pub fn get_output_drivers_for_dataset_name<P: AsRef<Path>>(
path: P,
properties: DriverProperties,
) -> impl Iterator<Item = Driver> {
let path = path.as_ref();
let path_lower = path.to_string_lossy().to_ascii_lowercase();

// NOTE: this isn't exactly correct for e.g. `.gpkg.zip`
// (which is not a GPKG), but this code is going away.
let ext = if path_lower.ends_with(".zip") {
if path_lower.ends_with(".shp.zip") {
"shp.zip".to_string()
} else if path_lower.ends_with(".gpkg.zip") {
"gpkg.zip".to_string()
} else {
"zip".to_string()
}
} else {
Path::new(&path_lower)
.extension()
.map(|e| e.to_string_lossy().into_owned())
.unwrap_or_default()
};

DriverManager::all()
.filter(move |d| {
let can_create = d.metadata_item("DCAP_CREATE", "").is_some()
|| d.metadata_item("DCAP_CREATECOPY", "").is_some();
match properties {
DriverProperties::Raster => {
can_create && d.metadata_item("DCAP_RASTER", "").is_some()
}
DriverProperties::Vector => {
(can_create && d.metadata_item("DCAP_VECTOR", "").is_some())
|| d.metadata_item("DCAP_VECTOR_TRANSLATE_FROM", "").is_some()
}
}
})
.filter(move |d| {
if let Some(e) = &d.metadata_item("DMD_EXTENSION", "") {
if *e == ext {
return true;
}
}
if let Some(e) = d.metadata_item("DMD_EXTENSIONS", "") {
if e.split(' ').any(|s| s == ext) {
return true;
}
}

if let Some(pre) = d.metadata_item("DMD_CONNECTION_PREFIX", "") {
if path_lower.starts_with(&pre.to_ascii_lowercase()) {
return true;
}
}
false
})
}

/// Register a driver for use.
///
/// Wraps [`GDALRegisterDriver()`](https://gdal.org/api/raster_c_api.html#_CPPv418GDALRegisterDriver11GDALDriverH)
Expand Down Expand Up @@ -461,6 +592,11 @@ impl DriverManager {
}
}

pub enum DriverProperties {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the name DriverType, as "Properties" usually implies a collection of settings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I was initially thinking that properties might be weird. It's renamed now.

Vector,
Raster,
}

/// Iterator for the registered [`Driver`]s in [`DriverManager`]
pub struct DriverIterator {
current: usize,
Expand Down Expand Up @@ -496,6 +632,84 @@ mod tests {
assert!(DriverManager::get_driver(0).is_ok());
}

#[test]
fn test_driver_by_extension() {
fn test_driver(d: &Driver, filename: &str, properties: DriverProperties) {
assert_eq!(
DriverManager::get_output_driver_for_dataset_name(filename, properties)
.unwrap()
.short_name(),
d.short_name()
);
}

if let Ok(d) = DriverManager::get_driver_by_name("ESRI Shapefile") {
test_driver(&d, "test.shp", DriverProperties::Vector);
test_driver(&d, "my.test.shp", DriverProperties::Vector);
// `shp.zip` only supported from gdal version 3.1
// https://gdal.org/drivers/vector/shapefile.html#compressed-files
if cfg!(all(major_ge_3, minor_ge_1)) {
test_driver(&d, "test.shp.zip", DriverProperties::Vector);
test_driver(&d, "my.test.shp.zip", DriverProperties::Vector);
}
}

if let Ok(d) = DriverManager::get_driver_by_name("GTiff") {
test_driver(&d, "test.tiff", DriverProperties::Raster);
test_driver(&d, "my.test.tiff", DriverProperties::Raster);
}
if let Ok(d) = DriverManager::get_driver_by_name("netCDF") {
test_driver(&d, "test.nc", DriverProperties::Raster);
}
}

#[test]
fn test_drivers_by_extension() {
// convert the driver into short_name for testing purposes
let drivers = |filename, is_vector| {
DriverManager::get_output_drivers_for_dataset_name(
filename,
if is_vector {
DriverProperties::Vector
} else {
DriverProperties::Raster
},
)
.map(|d| d.short_name())
.collect::<HashSet<String>>()
};
if DriverManager::get_driver_by_name("ESRI Shapefile").is_ok() {
assert!(drivers("test.shp", true).contains("ESRI Shapefile"));
assert!(drivers("my.test.shp", true).contains("ESRI Shapefile"));
// `shp.zip` only supported from gdal version 3.1
// https://gdal.org/drivers/vector/shapefile.html#compressed-files
if cfg!(all(major_ge_3, minor_ge_1)) {
assert!(drivers("test.shp.zip", true).contains("ESRI Shapefile"));
assert!(drivers("my.test.shp.zip", true).contains("ESRI Shapefile"));
}
}
if DriverManager::get_driver_by_name("GPKG").is_ok() {
assert!(drivers("test.gpkg", true).contains("GPKG"));
assert!(drivers("my.test.gpkg", true).contains("GPKG"));
// `gpkg.zip` only supported from gdal version 3.7
// https://gdal.org/drivers/vector/gpkg.html#compressed-files
if cfg!(all(major_ge_3, minor_ge_7)) {
assert!(drivers("test.gpkg.zip", true).contains("GPKG"));
assert!(drivers("my.test.gpkg.zip", true).contains("GPKG"));
}
}
if DriverManager::get_driver_by_name("GTiff").is_ok() {
assert!(drivers("test.tiff", false).contains("GTiff"));
assert!(drivers("my.test.tiff", false).contains("GTiff"));
}
if DriverManager::get_driver_by_name("netCDF").is_ok() {
assert!(drivers("test.nc", false).contains("netCDF"));
}
if DriverManager::get_driver_by_name("PostgreSQL").is_ok() {
assert!(drivers("PG:test", true).contains("PostgreSQL"));
}
}

#[test]
fn test_driver_iterator() {
assert_eq!(DriverManager::count(), DriverManager::all().count());
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub use dataset::Dataset;
pub use geo_transform::{GeoTransform, GeoTransformEx};
pub use options::{DatasetOptions, GdalOpenFlags};

pub use driver::{Driver, DriverManager};
pub use driver::{Driver, DriverManager, DriverProperties};
pub use gcp::{Gcp, GcpRef};
#[cfg(any(major_ge_4, all(major_is_3, minor_ge_6)))]
pub use gdal_sys::ArrowArrayStream;
Expand Down