Skip to content

Commit

Permalink
Merge pull request #72 from nmandery/datetime
Browse files Browse the repository at this point in the history
read/write support for date und datetime fields
  • Loading branch information
jdroenner authored Feb 13, 2020
2 parents 23521c5 + 2952106 commit 56cf139
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"]
74 changes: 74 additions & 0 deletions examples/read_write_ogr_datetime.rs
Original file line number Diff line number Diff line change
@@ -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();
}

19 changes: 19 additions & 0 deletions fixtures/points_with_datetime.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
101 changes: 100 additions & 1 deletion src/vector/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<DateTime<FixedOffset>> {
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() {
Expand Down Expand Up @@ -141,11 +184,41 @@ impl<'a> Feature<'a> {
Ok(())
}

#[cfg(feature = "datetime")]
pub fn set_field_datetime(&self, field_name: &str, value: DateTime<FixedOffset>) -> 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)),
}
}

Expand All @@ -170,6 +243,13 @@ pub enum FieldValue {
IntegerValue(i32),
StringValue(String),
RealValue(f64),

#[cfg(feature = "datetime")]
DateValue(Date<FixedOffset>),


#[cfg(feature = "datetime")]
DateTimeValue(DateTime<FixedOffset>),
}


Expand Down Expand Up @@ -197,4 +277,23 @@ impl FieldValue {
_ => None
}
}

/// Interpret the value as `Date`.
#[cfg(feature = "datetime")]
pub fn into_date(self) -> Option<Date<FixedOffset>> {
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<DateTime<FixedOffset>> {
match self {
FieldValue::DateTimeValue(rv) => Some(rv),
_ => None
}
}
}

0 comments on commit 56cf139

Please sign in to comment.