diff --git a/Cargo.toml b/Cargo.toml index 0333d328..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,6 +25,7 @@ geo-types = "0.4" gdal-sys = "0.2" num-traits = "0.2" ndarray = {version = "0.12.1", optional = true } +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 new file mode 100644 index 00000000..46b42ba0 --- /dev/null +++ b/examples/read_write_ogr_datetime.rs @@ -0,0 +1,74 @@ + +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)?; + + // 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(()) +} + +#[cfg(not(feature = "datetime"))] +fn run() -> Result<(), Error> { + println!("gdal crate was build without datetime support"); + 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..30802b60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,9 @@ extern crate num_traits; #[cfg(feature = "ndarray")] extern crate ndarray; +#[cfg(feature = "datetime")] +extern crate chrono; + pub use version::version_info; pub mod config; diff --git a/src/vector/feature.rs b/src/vector/feature.rs index 3974cc2a..798fb3c1 100644 --- a/src/vector/feature.rs +++ b/src/vector/feature.rs @@ -6,6 +6,9 @@ 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::*; /// OGR Feature @@ -65,10 +68,50 @@ 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())) + }, _ => Err(ErrorKind::UnhandledFieldType{field_type, method_name: "OGR_Fld_GetType"})? } } + #[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; + 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 +184,41 @@ 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())}; + 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), + + #[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)), } } @@ -170,6 +243,13 @@ pub enum FieldValue { IntegerValue(i32), StringValue(String), RealValue(f64), + + #[cfg(feature = "datetime")] + DateValue(Date), + + + #[cfg(feature = "datetime")] + DateTimeValue(DateTime), } @@ -197,4 +277,23 @@ impl FieldValue { _ => None } } + + /// Interpret the value as `Date`. + #[cfg(feature = "datetime")] + 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`. + #[cfg(feature = "datetime")] + pub fn into_datetime(self) -> Option> { + match self { + FieldValue::DateTimeValue(rv) => Some(rv), + _ => None + } + } }