From 6e94b67d105dc4792e7ff69bd04f7e86fe0c6f94 Mon Sep 17 00:00:00 2001 From: Nico Mandery Date: Mon, 10 Feb 2020 20:29:28 +0100 Subject: [PATCH 1/3] read/write support for date und datetime fields --- Cargo.toml | 1 + examples/read_write_ogr_datetime.rs | 61 +++++++++++++++++++++ fixtures/points_with_datetime.json | 19 +++++++ src/lib.rs | 1 + src/vector/feature.rs | 84 ++++++++++++++++++++++++++++- 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 examples/read_write_ogr_datetime.rs create mode 100644 fixtures/points_with_datetime.json diff --git a/Cargo.toml b/Cargo.toml index 0333d328..cca14504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ geo-types = "0.4" gdal-sys = "0.2" num-traits = "0.2" ndarray = {version = "0.12.1", optional = true } +chrono = "0.4" [workspace] members = ["gdal-sys"] diff --git a/examples/read_write_ogr_datetime.rs b/examples/read_write_ogr_datetime.rs new file mode 100644 index 00000000..656239c4 --- /dev/null +++ b/examples/read_write_ogr_datetime.rs @@ -0,0 +1,61 @@ +extern crate gdal; +extern crate chrono; + +use std::path::Path; +use gdal::errors::Error; +use gdal::vector::*; +use std::fs; +use chrono::Duration; +use std::ops::Add; + +fn run() -> Result<(), Error> { + let mut dataset_a = Dataset::open(Path::new("fixtures/points_with_datetime.json"))?; + let layer_a = dataset_a.layer(0)?; + + // Create a new dataset: + let _ = fs::remove_file("/tmp/later.geojson"); + let drv = Driver::get("GeoJSON")?; + let mut ds = drv.create(Path::new("/tmp/later.geojson"))?; + let lyr = ds.create_layer()?; + + // Copy the origin layer shema to the destination layer: + for field in layer_a.defn().fields() { + let field_defn = FieldDefn::new(&field.name(), field.field_type())?; + field_defn.set_width(field.width()); + field_defn.add_to_layer(lyr)?; + } + + // Get the definition to use on each feature: + let defn = Defn::from_layer(lyr); + + for feature_a in layer_a.features() { + let mut ft = Feature::new(&defn)?; + ft.set_geometry(feature_a.geometry().clone())?; + // copy each field value of the feature: + for field in defn.fields() { + ft.set_field(&field.name(), &match feature_a.field(&field.name())? { + + // add one day to dates + FieldValue::DateValue(value) => { + println!("{} = {}", field.name(), value); + FieldValue::DateValue(value.add(Duration::days(1))) + }, + + // add 6 hours to datetimes + FieldValue::DateTimeValue(value) => { + println!("{} = {}", field.name(), value); + FieldValue::DateTimeValue(value.add(Duration::hours(6))) + }, + v => v + })?; + } + // Add the feature to the layer: + ft.create(lyr)?; + } + Ok(()) +} + +fn main() { + run().unwrap(); +} + diff --git a/fixtures/points_with_datetime.json b/fixtures/points_with_datetime.json new file mode 100644 index 00000000..2c0c22bf --- /dev/null +++ b/fixtures/points_with_datetime.json @@ -0,0 +1,19 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 34.4, + 12.3 + ] + }, + "properties": { + "dt": "2011-07-14T19:43:37-0500", + "d": "2018-01-04" + } + } + ] +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1a76c2c2..fef10cec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ extern crate num_traits; #[cfg(feature = "ndarray")] extern crate ndarray; +extern crate chrono; pub use version::version_info; diff --git a/src/vector/feature.rs b/src/vector/feature.rs index 3974cc2a..c41fb6bd 100644 --- a/src/vector/feature.rs +++ b/src/vector/feature.rs @@ -5,6 +5,7 @@ use utils::{_string, _last_null_pointer_err}; use gdal_sys::{self, OGRErr, OGRFeatureH, OGRFieldType}; use vector::geometry::Geometry; use vector::layer::Layer; +use chrono::{Date, FixedOffset, DateTime, TimeZone, Datelike, Timelike}; use errors::*; @@ -65,10 +66,47 @@ impl<'a> Feature<'a> { let rv = unsafe { gdal_sys::OGR_F_GetFieldAsInteger(self.c_feature, field_id) }; Ok(FieldValue::IntegerValue(rv as i32)) }, + OGRFieldType::OFTDateTime => { + Ok(FieldValue::DateTimeValue(self.get_field_datetime(field_id)?)) + }, + OGRFieldType::OFTDate => { + Ok(FieldValue::DateValue(self.get_field_datetime(field_id)?.date())) + }, _ => Err(ErrorKind::UnhandledFieldType{field_type, method_name: "OGR_Fld_GetType"})? } } + fn get_field_datetime(&self, field_id: c_int) -> Result> { + let mut year: c_int = 0; + let mut month: c_int = 0; + let mut day: c_int = 0; + let mut hour: c_int = 0; + let mut minute: c_int = 0; + let mut second: c_int = 0; + let mut tzflag: c_int = 0; + + let success = unsafe { + gdal_sys::OGR_F_GetFieldAsDateTime( + self.c_feature, field_id, + &mut year, &mut month, &mut day, &mut hour, &mut minute, &mut second, &mut tzflag + ) + }; + if success == 0 { + Err(ErrorKind::OgrError { err: OGRErr::OGRERR_FAILURE, method_name: "OGR_F_GetFieldAsDateTime" })?; + } + + // from https://github.com/OSGeo/gdal/blob/33a8a0edc764253b582e194d330eec3b83072863/gdal/ogr/ogrutils.cpp#L1309 + let tzoffset_secs = if tzflag == 0 || tzflag == 100 { + 0 + } else { + (tzflag as i32 - 100) * 15 * 60 + }; + let rv = FixedOffset::east(tzoffset_secs) + .ymd(year as i32, month as u32, day as u32) + .and_hms(hour as u32, minute as u32, second as u32); + Ok(rv) + } + /// Get the field's geometry. pub fn geometry(&self) -> &Geometry { if !self.geometry[0].has_gdal_ptr() { @@ -141,11 +179,36 @@ impl<'a> Feature<'a> { Ok(()) } + pub fn set_field_datetime(&self, field_name: &str, value: DateTime) -> Result<()> { + let c_str_field_name = CString::new(field_name)?; + let idx = unsafe { gdal_sys::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())}; + if idx == -1 { + Err(ErrorKind::InvalidFieldName{field_name: field_name.to_string(), method_name: "OGR_F_GetFieldIndex"})?; + } + + let year = value.year() as c_int; + let month = value.month() as c_int; + let day = value.day() as c_int; + let hour= value.hour() as c_int; + let minute = value.minute() as c_int; + let second = value.second() as c_int; + let tzflag: c_int = if value.offset().local_minus_utc() == 0 { + 0 + } else { + 100 + (value.offset().local_minus_utc() / (15 * 60)) + }; + + unsafe { gdal_sys::OGR_F_SetFieldDateTime(self.c_feature, idx, year, month, day, hour, minute, second, tzflag) }; + Ok(()) + } + pub fn set_field(&self, field_name: &str, value: &FieldValue) -> Result<()> { match *value { FieldValue::RealValue(value) => self.set_field_double(field_name, value), FieldValue::StringValue(ref value) => self.set_field_string(field_name, value.as_str()), - FieldValue::IntegerValue(value) => self.set_field_integer(field_name, value) + FieldValue::IntegerValue(value) => self.set_field_integer(field_name, value), + FieldValue::DateTimeValue(value) => self.set_field_datetime(field_name, value), + FieldValue::DateValue(value) => self.set_field_datetime(field_name, value.and_hms(0, 0, 0)), } } @@ -170,6 +233,8 @@ pub enum FieldValue { IntegerValue(i32), StringValue(String), RealValue(f64), + DateValue(Date), + DateTimeValue(DateTime), } @@ -197,4 +262,21 @@ impl FieldValue { _ => None } } + + /// Interpret the value as `Date`. + pub fn into_date(self) -> Option> { + match self { + FieldValue::DateValue(rv) => Some(rv), + FieldValue::DateTimeValue(rv) => Some(rv.date()), + _ => None + } + } + + /// Interpret the value as `DateTime`. + pub fn into_datetime(self) -> Option> { + match self { + FieldValue::DateTimeValue(rv) => Some(rv), + _ => None + } + } } From e8115aef59119e40d0ec187f102481bedd688feb Mon Sep 17 00:00:00 2001 From: Nico Mandery Date: Wed, 12 Feb 2020 19:31:59 +0100 Subject: [PATCH 2/3] feature gate for datetime support --- src/vector/feature.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/vector/feature.rs b/src/vector/feature.rs index c41fb6bd..798fb3c1 100644 --- a/src/vector/feature.rs +++ b/src/vector/feature.rs @@ -5,6 +5,8 @@ use utils::{_string, _last_null_pointer_err}; use gdal_sys::{self, OGRErr, OGRFeatureH, OGRFieldType}; use vector::geometry::Geometry; use vector::layer::Layer; + +#[cfg(feature = "datetime")] use chrono::{Date, FixedOffset, DateTime, TimeZone, Datelike, Timelike}; use errors::*; @@ -66,9 +68,11 @@ impl<'a> Feature<'a> { let rv = unsafe { gdal_sys::OGR_F_GetFieldAsInteger(self.c_feature, field_id) }; Ok(FieldValue::IntegerValue(rv as i32)) }, + #[cfg(feature = "datetime")] OGRFieldType::OFTDateTime => { Ok(FieldValue::DateTimeValue(self.get_field_datetime(field_id)?)) }, + #[cfg(feature = "datetime")] OGRFieldType::OFTDate => { Ok(FieldValue::DateValue(self.get_field_datetime(field_id)?.date())) }, @@ -76,6 +80,7 @@ impl<'a> Feature<'a> { } } + #[cfg(feature = "datetime")] fn get_field_datetime(&self, field_id: c_int) -> Result> { let mut year: c_int = 0; let mut month: c_int = 0; @@ -179,6 +184,7 @@ impl<'a> Feature<'a> { Ok(()) } + #[cfg(feature = "datetime")] pub fn set_field_datetime(&self, field_name: &str, value: DateTime) -> Result<()> { let c_str_field_name = CString::new(field_name)?; let idx = unsafe { gdal_sys::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())}; @@ -207,7 +213,11 @@ impl<'a> Feature<'a> { FieldValue::RealValue(value) => self.set_field_double(field_name, value), FieldValue::StringValue(ref value) => self.set_field_string(field_name, value.as_str()), FieldValue::IntegerValue(value) => self.set_field_integer(field_name, value), + + #[cfg(feature = "datetime")] FieldValue::DateTimeValue(value) => self.set_field_datetime(field_name, value), + + #[cfg(feature = "datetime")] FieldValue::DateValue(value) => self.set_field_datetime(field_name, value.and_hms(0, 0, 0)), } } @@ -233,7 +243,12 @@ pub enum FieldValue { IntegerValue(i32), StringValue(String), RealValue(f64), + + #[cfg(feature = "datetime")] DateValue(Date), + + + #[cfg(feature = "datetime")] DateTimeValue(DateTime), } @@ -264,6 +279,7 @@ impl FieldValue { } /// Interpret the value as `Date`. + #[cfg(feature = "datetime")] pub fn into_date(self) -> Option> { match self { FieldValue::DateValue(rv) => Some(rv), @@ -273,6 +289,7 @@ impl FieldValue { } /// Interpret the value as `DateTime`. + #[cfg(feature = "datetime")] pub fn into_datetime(self) -> Option> { match self { FieldValue::DateTimeValue(rv) => Some(rv), From 2952106e0ce656d23be76c8ed6266852e2aa708d Mon Sep 17 00:00:00 2001 From: Nico Mandery Date: Wed, 12 Feb 2020 19:41:48 +0100 Subject: [PATCH 3/3] feature gate for datetime support - 2nd part --- Cargo.toml | 3 ++- examples/read_write_ogr_datetime.rs | 13 +++++++++++++ src/lib.rs | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cca14504..15303b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ documentation = "https://georust.github.io/gdal/" bindgen = ["gdal-sys/bindgen"] gdal_2_2 = ["gdal-sys/min_gdal_version_2_2"] array = ["ndarray"] +datetime = ["chrono"] [dependencies] failure = "0.1" @@ -24,7 +25,7 @@ geo-types = "0.4" gdal-sys = "0.2" num-traits = "0.2" ndarray = {version = "0.12.1", optional = true } -chrono = "0.4" +chrono = { version = "0.4", optional = true } [workspace] members = ["gdal-sys"] diff --git a/examples/read_write_ogr_datetime.rs b/examples/read_write_ogr_datetime.rs index 656239c4..46b42ba0 100644 --- a/examples/read_write_ogr_datetime.rs +++ b/examples/read_write_ogr_datetime.rs @@ -1,14 +1,20 @@ + extern crate gdal; +#[cfg(feature = "datetime")] extern crate chrono; use std::path::Path; use gdal::errors::Error; use gdal::vector::*; use std::fs; +#[cfg(feature = "datetime")] use chrono::Duration; use std::ops::Add; +#[cfg(feature = "datetime")] fn run() -> Result<(), Error> { + println!("gdal crate was build with datetime support"); + let mut dataset_a = Dataset::open(Path::new("fixtures/points_with_datetime.json"))?; let layer_a = dataset_a.layer(0)?; @@ -55,6 +61,13 @@ fn run() -> Result<(), Error> { Ok(()) } +#[cfg(not(feature = "datetime"))] +fn run() -> Result<(), Error> { + println!("gdal crate was build without datetime support"); + Ok(()) +} + + fn main() { run().unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index fef10cec..30802b60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,8 @@ extern crate num_traits; #[cfg(feature = "ndarray")] extern crate ndarray; + +#[cfg(feature = "datetime")] extern crate chrono; pub use version::version_info;