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

Extend the existing possibilities of writing ogr datasets. #31

Merged
merged 11 commits into from
Jan 17, 2017
53 changes: 53 additions & 0 deletions examples/read_write_ogr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
extern crate gdal;

use std::fs;
use std::path::Path;
use gdal::vector::*;
use gdal::spatial_ref::{SpatialRef, CoordTransform};

fn main() {
let mut dataset_a = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap();
let layer_a = dataset_a.layer(0).unwrap();
let fields_defn = layer_a.defn().fields()
.map(|field| (field.name(), field.get_type(), field.get_width()))
.collect::<Vec<_>>();

// Create a new dataset :
fs::remove_file("/tmp/abcde.shp");
let drv = Driver::get("ESRI Shapefile").unwrap();
let mut ds = drv.create(Path::new("/tmp/abcde.shp")).unwrap();
let lyr = ds.create_layer().unwrap();

// Copy the origin layer shema to the destination layer :
for fd in &fields_defn {
let field_defn = FieldDefn::new(&fd.0, fd.1);
field_defn.set_width(fd.2);
field_defn.add_to_layer(&lyr);
}

// Prepare the origin and destination spatial references objects :
let spatial_ref_src = SpatialRef::from_epsg(4326).unwrap();
let spatial_ref_dst = SpatialRef::from_epsg(3025).unwrap();

// And the feature used to actually transform the geometries :
let htransform = CoordTransform::new(&spatial_ref_src, &spatial_ref_dst).unwrap();

// Get the definition to use on each feature :
let defn = Defn::new_from_layer(&lyr);

for feature_a in layer_a.features() {
// Get the original geometry :
let geom = feature_a.geometry();
// Get a new transformed geometry :
let new_geom = geom.transform(&htransform).unwrap();
// Create the new feature, set its geometry :
let mut ft = Feature::new(&defn);
ft.set_geometry(new_geom);
// copy each field value of the feature :
for fd in &fields_defn {
ft.set_field(&fd.0, fd.1, feature_a.field(&fd.0).unwrap())
}
// Add the feature to the layer :
ft.create(&lyr);
}
}
2 changes: 1 addition & 1 deletion examples/spatial_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn main() {
println!("Before transformation :\n{:?} {:?}", xs, ys);
htransform.transform_coord(xs, ys, &mut [0.0, 0.0]);
println!("After transformation :\n{:?} {:?}\n", xs, ys);
let mut geom = Geometry::from_wkt("POLYGON((23.43 37.58, 23.43 40.0, 25.29 40.0, 25.29 37.58, 23.43 37.58))").unwrap();
let geom = Geometry::from_wkt("POLYGON((23.43 37.58, 23.43 40.0, 25.29 40.0, 25.29 37.58, 23.43 37.58))").unwrap();
println!("Polygon before transformation:\n{:?}\n", geom.wkt().unwrap());
geom.transform(&htransform).unwrap();
println!("Polygon after transformation:\n{:?}\n", geom.wkt().unwrap());
Expand Down
80 changes: 80 additions & 0 deletions examples/write_ogr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
extern crate gdal;

use std::path::Path;
use std::fs;
use gdal::vector::{Defn, Driver, Feature, FieldDefn, Geometry, OFT_INTEGER, OFT_REAL, OFT_STRING, FieldValue};

fn main(){
/// Example 1, the detailed way :
{
fs::remove_file("/tmp/output1.geojson");
let drv = Driver::get("GeoJSON").unwrap();
let mut ds = drv.create(Path::new("/tmp/output1.geojson")).unwrap();

let lyr = ds.create_layer().unwrap();

let field_defn = FieldDefn::new("Name", OFT_STRING);
field_defn.set_width(80);
field_defn.add_to_layer(&lyr);

let field_defn = FieldDefn::new("Value", OFT_REAL);
field_defn.add_to_layer(&lyr);

let defn = Defn::new_from_layer(&lyr);

// 1st feature :
let mut ft = Feature::new(&defn);
ft.set_geometry(Geometry::from_wkt("POINT (45.21 21.76)").unwrap());
ft.set_field_string("Name", "Feature 1");
ft.set_field_double("Value", 45.78);
ft.create(&lyr);

// 2nd feature :
let mut ft = Feature::new(&defn);
ft.set_geometry(Geometry::from_wkt("POINT (46.50 22.50)").unwrap());
ft.set_field_string("Name", "Feature 2");
ft.set_field_double("Value", 0.789);
ft.create(&lyr);
}

/// Example 2, same output, shortened way :
{
fs::remove_file("/tmp/output2.geojson");
let driver = Driver::get("GeoJSON").unwrap();
let mut ds = driver.create(Path::new("/tmp/output2.geojson")).unwrap();
let mut layer = ds.create_layer().unwrap();

layer.create_defn_fields(&[("Name", OFT_STRING), ("Value", OFT_REAL)]);
// Shortcut for :
// let field_defn = FieldDefn::new("Name", OFT_STRING);
// field_defn.add_to_layer(&layer);
// let field_defn = FieldDefn::new("Value", OFT_REAL);
// field_defn.add_to_layer(&layer);

layer.create_feature_fields(
Geometry::from_wkt("POINT (45.21 21.76)").unwrap(),
&["Name", "Value"],
&[FieldValue::StringValue("Feature 1".to_string()), FieldValue::RealValue(45.78)]
);
layer.create_feature_fields(
Geometry::from_wkt("POINT (46.50 22.50)").unwrap(),
&["Name", "Value"],
&[FieldValue::StringValue("Feature 2".to_string()), FieldValue::RealValue(0.789)]
);
// Shortcuts for :
// let defn = Defn::new_from_layer(&layer);
//
// let mut ft = Feature::new(&defn);
// ft.set_geometry(Geometry::from_wkt("POINT (45.21 21.76)").unwrap());
// ft.set_field("Name", OFT_STRING, "Feature 1");
// ft.set_field("Value", OFT_REAL, 45.78);
// ft.create(&lyr);
//
// let mut ft = Feature::new(&defn);
// ft.set_geometry(Geometry::from_wkt("POINT (46.50 22.50)").unwrap());
// ft.set_field("Name", OFT_STRING, "Feature 2");
// ft.set_field("Value", OFT_REAL, 0.789);
// ft.create(&lyr);
}

}
14 changes: 14 additions & 0 deletions gdal-sys/src/ogr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@ extern {
pub fn OGR_L_GetNextFeature(hLayer: *const c_void) -> *const c_void;
pub fn OGR_L_SetSpatialFilter(hLayer: *const c_void, hGeom: *const c_void);
pub fn OGR_L_CreateFeature(hLayer: *const c_void, hFeat: *const c_void) -> OGRErr;
pub fn OGR_L_CreateField(hLayer: *const c_void, hField: *const c_void, bApproxOK: c_int) -> OGRErr;
pub fn OGR_FD_GetFieldCount(hDefn: *const c_void) -> c_int;
pub fn OGR_FD_GetFieldDefn(hDefn: *const c_void, iField: c_int) -> *const c_void;
pub fn OGR_F_Create(hDefn: *const c_void) -> *const c_void;
pub fn OGR_F_GetFieldIndex(hFeat: *const c_void, pszName: *const c_char) -> c_int;
pub fn OGR_F_GetFieldDefnRef(hFeat: *const c_void, i: c_int) -> *const c_void;
pub fn OGR_F_GetFieldAsString(hFeat: *const c_void, iField: c_int) -> *const c_char;
pub fn OGR_F_GetFieldAsDouble(hFeat: *const c_void, iField: c_int) -> c_double;
pub fn OGR_F_GetFieldAsInteger(hFeat: *const c_void, iField: c_int) -> c_int;
pub fn OGR_F_GetGeometryRef(hFeat: *const c_void) -> *const c_void;
pub fn OGR_F_SetGeometry(hFeat: *const c_void, hGeom: *const c_void) -> OGRErr;
pub fn OGR_F_SetGeometryDirectly(hFeat: *const c_void, hGeom: *const c_void) -> OGRErr;
pub fn OGR_F_SetFieldString(hFeat: *const c_void, iField: c_int, pszValue: *const c_char) -> c_void;
pub fn OGR_F_SetFieldDouble(hFeat: *const c_void, iField: c_int, dfValue: c_double) -> c_void;
pub fn OGR_F_SetFieldInteger(hFeat: *const c_void, iField: c_int, nValue: c_int) -> c_void;
pub fn OGR_F_Destroy(hFeat: *const c_void);
pub fn OGR_G_CreateGeometry(eGeometryType: c_int) -> *const c_void;
pub fn OGR_G_CreateFromWkt(ppszData: &mut *const c_char, hSRS: *const c_void, phGeometry: &mut *const c_void) -> OGRErr;
pub fn OGR_G_Clone(OGRGeometryH: *const c_void) -> *const c_void;
pub fn OGR_G_GetGeometryType(hGeom: *const c_void) -> c_int;
pub fn OGR_G_GetPoint(hGeom: *const c_void, i: c_int, pdfX: &mut c_double, pdfY: &mut c_double, pdfZ: &mut c_double);
pub fn OGR_G_GetPointCount(hGeom: *const c_void) -> c_int;
Expand All @@ -42,10 +49,17 @@ extern {
pub fn OGR_G_DestroyGeometry(hGeom: *mut c_void);
pub fn OGR_Fld_GetNameRef(hDefn: *const c_void) -> *const c_char;
pub fn OGR_Fld_GetType(hDefn: *const c_void) -> c_int;
pub fn OGR_Fld_Create(pszName: *const c_char, eType: c_int) -> *const c_void;
pub fn OGR_Fld_GetWidth(hDefn: *const c_void) -> c_int;
pub fn OGR_Fld_GetPrecision(hDefn: *const c_void) -> c_int;
pub fn OGR_Fld_SetWidth(hDefn: *const c_void, nNewWidth: c_int) -> c_void;
pub fn OGR_Fld_SetPrecision(hDefn: *const c_void, nNewPrecision: c_int) -> c_void;
pub fn OGR_Fld_Destroy(hDefn: *mut c_void) -> c_void;
pub fn OGRFree(ptr: *mut c_void);
pub fn VSIFree(ptr: *mut c_void);
}

pub const OFT_INTEGER: c_int = 0;
Copy link
Member

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)

Copy link
Member Author

@mthh mthh Jan 15, 2017

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 const OFT_REAL: c_int = 2;
pub const OFT_STRING: c_int = 4;

Expand Down
4 changes: 2 additions & 2 deletions gdal-sys/src/ogr_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#[allow(dead_code)]
#[repr(C)]
pub enum OGRErr {
OGRERR_NONE = 0,
OGRERR_NONE = 0,
OGRERR_NOT_ENOUGH_DATA = 1,
OGRERR_NOT_ENOUGH_MEMORY = 2,
OGRERR_UNSUPPORTED_GEOMETRY_TYPE = 3,
Expand All @@ -12,4 +12,4 @@ pub enum OGRErr {
OGRERR_UNSUPPORTED_SRS = 7,
OGRERR_INVALID_HANDLE = 8,
OGRERR_NON_EXISTING_FEATURE = 9
}
}
4 changes: 2 additions & 2 deletions src/spatial_ref/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ fn transform_coordinates(){
fn transform_ogr_geometry(){
//let expected_value = "POLYGON ((5509543.150809700600803 1716062.191619219258428,5467122.000330002978444 1980151.204280239529908,5623571.028492723591626 2010213.310253676958382,5671834.921544363722205 1746968.078280254499987,5509543.150809700600803 1716062.191619219258428))";
let expected_value = "POLYGON ((5509543.15080969966948 1716062.191619222285226,5467122.000330002047122 1980151.204280242323875,5623571.028492721728981 2010213.31025367998518,5671834.921544362790883 1746968.078280256595463,5509543.15080969966948 1716062.191619222285226))";
let mut geom = Geometry::from_wkt("POLYGON((23.43 37.58, 23.43 40.0, 25.29 40.0, 25.29 37.58, 23.43 37.58))").unwrap();
let geom = Geometry::from_wkt("POLYGON((23.43 37.58, 23.43 40.0, 25.29 40.0, 25.29 37.58, 23.43 37.58))").unwrap();
let spatial_ref1 = SpatialRef::from_proj4("+proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs").unwrap();
let spatial_ref2 = SpatialRef::from_wkt("GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",7030]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",6326]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",8901]],UNIT[\"DMSH\",0.0174532925199433,AUTHORITY[\"EPSG\",9108]],AXIS[\"Lat\",NORTH],AXIS[\"Long\",EAST],AUTHORITY[\"EPSG\",4326]]").unwrap();
let htransform = CoordTransform::new(&spatial_ref2, &spatial_ref1).unwrap();
geom.transform(&htransform).unwrap();
geom.transform_inplace(&htransform).unwrap();
assert_eq!(expected_value, geom.wkt().unwrap());
}
20 changes: 20 additions & 0 deletions src/vector/defn.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use libc::{c_int, c_void};
use utils::_string;
use gdal_sys::ogr;
use vector::layer::Layer;
use gdal_major_object::MajorObject;


/// Layer definition
///
Expand All @@ -26,6 +29,11 @@ impl Defn {
total: total
};
}

pub fn new_from_layer(lyr: &Layer) -> Defn {
Copy link
Member

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.

Copy link
Member Author

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.

let c_defn = unsafe { ogr::OGR_L_GetLayerDefn(lyr.gdal_object_ptr())};
Defn {c_defn: c_defn}
}
}

pub struct FieldIterator<'a> {
Expand Down Expand Up @@ -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 {
Copy link
Member

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.

Copy link
Member Author

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 ?

Copy link
Member

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.

Copy link
Member Author

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 {
Copy link
Member

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 {
Copy link
Member

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.

unsafe { ogr::OGR_Fld_GetPrecision(self.c_field_defn) }
}
}
75 changes: 73 additions & 2 deletions src/vector/feature.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use std::ffi::CString;
use libc::{c_void};
use libc::{c_void, c_double, c_int};
use vector::Defn;
use utils::_string;
use gdal_sys::ogr;
use gdal_sys::{ogr, ogr_enums};
use vector::geometry::Geometry;
use vector::layer::Layer;
use gdal_major_object::MajorObject;

use errors::*;

/// OGR Feature
pub struct Feature<'a> {
Expand All @@ -15,6 +18,15 @@ pub struct Feature<'a> {


impl<'a> Feature<'a> {
pub fn new(defn: &'a Defn) -> Feature {
let c_feature = unsafe { ogr::OGR_F_Create(defn.c_defn()) };
unsafe { Feature {
_defn: defn,
c_feature: c_feature,
geometry: Geometry::lazy_feature_geometry(),
} }
}

pub unsafe fn _with_c_feature(defn: &'a Defn, c_feature: *const c_void) -> Feature {
return Feature{
_defn: defn,
Expand Down Expand Up @@ -43,6 +55,10 @@ impl<'a> Feature<'a> {
let rv = unsafe { ogr::OGR_F_GetFieldAsDouble(self.c_feature, field_id) };
return Some(FieldValue::RealValue(rv as f64));
},
ogr::OFT_INTEGER => {
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)
}
}
Expand All @@ -55,6 +71,52 @@ impl<'a> Feature<'a> {
}
return &self.geometry;
}

pub fn create(&self, lyr: &Layer) -> Result<()> {
let rv = unsafe { ogr::OGR_L_CreateFeature(lyr.gdal_object_ptr(), self.c_feature) };
if rv != ogr_enums::OGRErr::OGRERR_NONE {
return Err(ErrorKind::OgrError(rv, "OGR_L_CreateFeature").into());
}
Ok(())
}

pub fn set_field_string(&self, field_name: &str, value: &str){
let c_str_field_name = CString::new(field_name).unwrap();
Copy link
Member

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.

Copy link
Member Author

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() ?

Copy link
Member

@jdroenner jdroenner Jan 15, 2017

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.

let c_str_value = CString::new(value).unwrap();
Copy link
Member

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)?;

let idx = unsafe { ogr::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())};
Copy link
Member

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 ?

unsafe { ogr::OGR_F_SetFieldString(self.c_feature, idx, c_str_value.as_ptr()) };
}

pub fn set_field_double(&self, field_name: &str, value: f64){
let c_str_field_name = CString::new(field_name).unwrap();
let idx = unsafe { ogr::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())};
unsafe { ogr::OGR_F_SetFieldDouble(self.c_feature, idx, value as c_double) };
}

pub fn set_field_integer(&self, field_name: &str, value: i32){
let c_str_field_name = CString::new(field_name).unwrap();
let idx = unsafe { ogr::OGR_F_GetFieldIndex(self.c_feature, c_str_field_name.as_ptr())};
unsafe { ogr::OGR_F_SetFieldInteger(self.c_feature, idx, value as c_int) };
}

pub fn set_field(&self, field_name: &str, type_value: i32, value: FieldValue){
if type_value == 2 {
self.set_field_double(field_name, value.as_real());
} else if type_value == 4 {
self.set_field_string(field_name, value.as_string().as_str());
} else if type_value == 0 {
self.set_field_integer(field_name, value.as_int());
}
}

pub fn set_geometry(&mut self, geom: Geometry) -> Result<()> {
self.geometry = geom;
Copy link
Member

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 rv = unsafe { ogr::OGR_F_SetGeometry(self.c_feature, self.geometry.c_geometry()) };
if rv != ogr_enums::OGRErr::OGRERR_NONE {
return Err(ErrorKind::OgrError(rv, "OGR_G_SetGeometry").into());
}
Ok(())
}
}


Expand All @@ -66,6 +128,7 @@ impl<'a> Drop for Feature<'a> {


pub enum FieldValue {
IntegerValue(i32),
StringValue(String),
RealValue(f64),
}
Expand All @@ -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 {
Copy link
Member

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>.

Copy link
Member Author

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 ?)

Copy link
Member

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...

Copy link
Member Author

@mthh mthh Jan 15, 2017

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).

match self {
FieldValue::IntegerValue(rv) => rv,
_ => panic!("not an IntegerValue")
}
}
}
Loading