-
Notifications
You must be signed in to change notification settings - Fork 96
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
Extend the existing possibilities of writing ogr datasets. #31
Conversation
pub fn OGRFree(ptr: *mut c_void); | ||
pub fn VSIFree(ptr: *mut c_void); | ||
} | ||
|
||
pub const OFT_INTEGER: c_int = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to change the OFT types into an enum and move it into ogr_enums.rs. This way all the FFI methods could take OgrFieldType instead of c_int as parameter. (link to the OGR enum)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking too about moving OFT types into an enum, so no problem, I will do it.
} | ||
|
||
pub fn set_field_string(&self, field_name: &str, value: &str){ | ||
let c_str_field_name = CString::new(field_name).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is possible please try not to use unwrap
in library methods. Methods like this should return Result<()>
.
I think we should add the returned error in errors.rs if we don't handle it already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright for returning Result
on these methods.
However is it possible here to not use unwrap()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error handling in rust is a bit difficult. Where is a lot about it in the book.
To make this easier rust-gdal uses the error-chain crate. We already have some wrapping errors in errors.rs:
FfiNulError(::std::ffi::NulError);
StrUtf8Error(::std::str::Utf8Error);
To handle the Ok
or return the (wrapped) Err
you can use the try!{}
macro or ?
. In this case all you need to do is to replace unwrap()
with ?
: let c_str_field_name = CString::new(field_name)?;
CString::new
returns Result<CString, ::std::ffi::NulError>
. In the error case ?
will convert NulError
into the wrapping error (FfiNulError
) and return it.
@mthh this looks really nice :) I can have a closer look this weekend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a bunch of nits related to naming, they aren't big issues.
@@ -26,6 +29,11 @@ impl Defn { | |||
total: total | |||
}; | |||
} | |||
|
|||
pub fn new_from_layer(lyr: &Layer) -> Defn { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be just from_layer
if you wanted to match conventions in std
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I will change it.
@@ -66,4 +74,16 @@ impl<'a> Field<'a> { | |||
let rv = unsafe { ogr::OGR_Fld_GetNameRef(self.c_field_defn) }; | |||
return _string(rv); | |||
} | |||
|
|||
pub fn get_type(&'a self) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be just type
if you wanted to match conventions in std
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I can't use type
as a function name as it is keyword in rust (that's why I chose get_type
in replacement and thus to be consistent over the methods of this object I also used get_width
and get_precision
). Any suggestions ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this will return a OGRFieldType
you could change the method name to field_type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
unsafe { ogr::OGR_Fld_GetType(self.c_field_defn) } | ||
} | ||
|
||
pub fn get_width(&'a self) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be just width
if you wanted to match conventions in std
.
unsafe { ogr::OGR_Fld_GetWidth(self.c_field_defn) } | ||
} | ||
|
||
pub fn get_precision(&'a self) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be just precision
if you wanted to match conventions in std
.
@@ -87,4 +150,12 @@ impl FieldValue { | |||
_ => panic!("not a RealValue") | |||
} | |||
} | |||
|
|||
/// Interpret the value as `i32`. Panics if the value is something else. | |||
pub fn as_int(self) -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Methods that consume self
are usually named into_<type>
, so in this case, it would be into_int
, but I notice above that the other methods on this structure use as_<type>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I also change the name of the two previous methods (as_real
-> to_real
and as_string
to_string
?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess this would be the right thing to do. Also i think these methods should return an Option instead of panicking...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the other changes but not this last one (returning an Option
instead of panicking), because I didn't know how to handle this when using a FieldValue
to set a field :
pub fn set_field(&self, field_name: &str, type_value: OGRFieldType, value: FieldValue) -> Result<()> {
match type_value {
OGRFieldType::OFTReal => self.set_field_double(field_name, value.to_real()),
like here, when using value.to_real()
. Is the use of is_some
ok ? Or should I look for a way to handle this nicely without using is_some
then unwrap
? (i will look into the error_chain
crate documentation then make the changes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really appreciate this contribution. It adds so much functionality to rust-gdal.
There are some unwraps left and i think we need more error types :)
|
||
pub fn set_field_string(&self, field_name: &str, value: &str) -> Result<()> { | ||
let c_str_field_name = CString::new(field_name)?; | ||
let c_str_value = CString::new(value).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap
to ?
-> CString::new(value)?;
pub fn set_field_string(&self, field_name: &str, value: &str) -> Result<()> { | ||
let c_str_field_name = CString::new(field_name)?; | ||
let c_str_value = CString::new(value).unwrap(); | ||
let idx = unsafe { ogr::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OGR_F_GetFieldIndex
will return -1 if a field name does not exist. I guess we need a new error. Maybe InvalidFieldName
?
|
||
pub fn set_field(&self, field_name: &str, type_value: OGRFieldType, value: FieldValue) -> Result<()> { | ||
match type_value { | ||
OGRFieldType::OFTReal => self.set_field_double(field_name, value.to_real()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the conversion of FieldValue
to the matching OGRFieldType
:
As i see it this method fails if FieldValue
and OGRFieldType
don't match. So at this point we don't need the OGRFieldType
at all... We can match based on the FieldValue:
pub enum FieldValue {
IntegerValue(i32),
StringValue(String),
RealValue(f64),
}
pub fn set_field(&self, field_name: &str, value: FieldValue) -> Result<()> {
match value {
FieldValue::RealValue(value) => self.set_field_double(field_name, value)?,
...
Also the set_field_double
method returns a result which should be used/handled here (using the ?
operator).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the Result
from set_field_double
(and others) just be passed to the caller of set_field
like this?
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(value) => self.set_field_string(field_name, value.as_str()),
FieldValue::IntegerValue(value) => self.set_field_integer(field_name, value)
}
}
This way, with an invalid field name we get the ErrKind::InvalidFieldName
error from the set_field_double/string/..
function, and we can't enter an invalid FieldValue due to the type checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right this should be perfectly fine. I was thinking to complicated.
OGRFieldType::OFTReal => self.set_field_double(field_name, value.to_real()), | ||
OGRFieldType::OFTString => self.set_field_string(field_name, value.to_string().as_str()), | ||
OGRFieldType::OFTInteger => self.set_field_integer(field_name, value.to_int()), | ||
_ => Err(ErrorKind::InvalidInput("set_field").into()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a better name for the error is InvalidFieldType
or 'UnhandledFieldType'.
} | ||
|
||
pub fn set_geometry(&mut self, geom: Geometry) -> Result<()> { | ||
self.geometry = geom; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the following method call fails? Wouldn't it be more safe if this is done after the GDAL call and before Ok(())
?
let val = &values[i]; | ||
match val { | ||
&FieldValue::StringValue(ref v) => { | ||
unsafe { ogr::OGR_F_SetFieldString(c_feature, idx, CString::new(v.as_str()).unwrap().as_ptr()) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap
to ?
|
||
impl FieldDefn { | ||
pub fn new(name: &str, field_type: ogr_enums::OGRFieldType) -> FieldDefn { | ||
let c_str = CString::new(name).unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let the method return a Result and change unwrap
to ?
} | ||
pub fn add_to_layer(&self, layer: &Layer) { | ||
let rv = unsafe { ogr::OGR_L_CreateField(layer.gdal_object_ptr(), self.c_obj, 1) }; | ||
assert_eq!(rv, ogr_enums::OGRErr::OGRERR_NONE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the last unwrap/assert :)
InvalidInput(method_name: &'static str) { | ||
description("Invalid input") | ||
display("Invalid input : {}", method_name) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more descriptive name for this error (maybe InvalidFieldType
)?
Also i added the method name to the errors related to the GDAL FFI calls to enable users to look up methods in the GDAL API. I'm not sure if it is needed here.
let rv = unsafe { ogr::OGR_F_GetFieldAsInteger(self.c_feature, field_id) }; | ||
return Some(FieldValue::IntegerValue(rv as i32)); | ||
}, | ||
_ => panic!("Unknown field type {:?}", field_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be an error for this :) . Maybe UnhandledFieldType
?
Many thanks for the detailed and instructive review!! I made most of the changes but i'm still unsure about 1 or 2 things. |
I guess I modified the various things discussed, except the clone method for which I did not know how to handle errors that might occur. |
This is great! |
Great that it can be merged ! |
Merged. |
This PR (related to #30) aims to extend the existing possibilities of writing ogr datasets.
Field
struct, allowing to retrieve the width, type and precision when reading a layer.IntergerValue
to theFieldValue
enum (allowing to get/set integer values too).Clone
trait on theGeometry
structI honestly don't know if I did it the right way or if there is too much redundancy in the way i implemented that (there is two commented examples (
read_write_ogr.rs
andwrite_ogr.rs
) to facilitate your potential reviews).Let me know if you have advices/requirements on how it should be designed (I should have asked for some guidance before doing it..!) and/or if it combines badly with the existing functionalities allowing to create/write ogr features.
For example in src/vector/test/mod.rs a dataset is currently written like this :
Now it could be possible to write :