Skip to content

Commit

Permalink
Added Geometry::set_points
Browse files Browse the repository at this point in the history
  • Loading branch information
JmsPae committed Jan 12, 2025
1 parent 94b4936 commit 92901d2
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 2 deletions.
10 changes: 10 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ pub enum GdalError {
to: String,
msg: Option<String>,
},
#[error("Invalid coordinate layout supplied to '{method_name}': '{msg:?}'")]
InvalidCoordinateLayout {
msg: Option<String>,
method_name: &'static str,
},
#[error("Invalid data supplied to '{method_name}': '{msg:?}'")]
InvalidDataInput {
msg: Option<String>,
method_name: &'static str,
},
#[error("Axis not found for key '{key}' in method '{method_name}'")]
AxisNotFoundError {
key: String,
Expand Down
145 changes: 143 additions & 2 deletions src/vector/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub enum CoordinateComponent {
Y = 0b00010,
Z = 0b00100,
M = 0b01000,
// Whether or not the layout is sequential (eg. XyXy as opposed to XxYy.)
Sequential = 0b10000,
}

Expand Down Expand Up @@ -54,11 +55,11 @@ impl CoordinateLayout {
CoordinateComponent::M,
];

fn has_component(&self, layout: CoordinateComponent) -> bool {
pub fn has_component(&self, layout: CoordinateComponent) -> bool {
(*self as u8 & layout as u8) != 0
}

fn coordinate_size(&self) -> usize {
pub fn coordinate_size(&self) -> usize {
Self::COMPONENTS
.into_iter()
.filter(|c| self.has_component(*c))
Expand Down Expand Up @@ -271,6 +272,90 @@ impl Geometry {
(x, y, z, m)
}

/// Writes all points from `in_points` to the geometry, overwriting any existing points.
///
/// If the coordinate layout contains more dimensions than the geometry, the geometry type will
/// be upgraded.
///
/// One-dimensional data is unsupported.
pub fn set_points(&mut self, in_points: &[f64], layout: CoordinateLayout) -> Result<()> {
let coord_size = layout.coordinate_size();

if coord_size <= 1 {
return Err(GdalError::InvalidCoordinateLayout {
msg: Some(format!("One-dimensional layout '{layout:?}' unsupported.")),
method_name: "set_points",
});
}

if in_points.len() % coord_size != 0 {
return Err(
GdalError::InvalidDataInput {
msg: Some(format!(
"in_points length is not a multiple of {coord_size} as determined by '{layout:?}'"
)),
method_name: "set_points"
}
);
}
let num_points = in_points.len() / coord_size;

let component_size = size_of::<f64>();

let (stride, ptr_offset): (c_int, isize) =
match layout.has_component(CoordinateComponent::Sequential) {
true => (
(component_size * coord_size) as c_int,
component_size as isize,
),
false => (
component_size as c_int,
(num_points * component_size) as isize,
),
};

unsafe {
let data = in_points.as_ptr() as *mut c_void;

// Per-component ptr offset.
let mut curr_ptr_offset: isize = 0;

let component_loc = |layout: &CoordinateLayout,
component: CoordinateComponent,
curr_ptr_offset: &mut isize|
-> *mut c_void {
match layout.has_component(component) {
true => {
let ret = data.byte_offset(*curr_ptr_offset);
*curr_ptr_offset += ptr_offset;
ret
}
false => std::ptr::null_mut(),
}
};

let x_ptr = component_loc(&layout, CoordinateComponent::X, &mut curr_ptr_offset);
let y_ptr = component_loc(&layout, CoordinateComponent::Y, &mut curr_ptr_offset);
let z_ptr = component_loc(&layout, CoordinateComponent::Z, &mut curr_ptr_offset);
let m_ptr = component_loc(&layout, CoordinateComponent::M, &mut curr_ptr_offset);

// Should be OK to just use the offset even for unused components...
gdal_sys::OGR_G_SetPointsZM(
self.c_geometry(),
num_points as c_int,
x_ptr,
stride,
y_ptr,
stride,
z_ptr,
stride,
m_ptr,
stride,
);
}
Ok(())
}

/// Writes all points in the geometry to `out_points` according to the specified `layout`.
///
/// For some geometry types, like polygons, that don't consist of points, 0 will be returned
Expand Down Expand Up @@ -809,6 +894,62 @@ mod tests {
assert!(!points.is_empty());
}

#[test]
fn test_set_points() {
let mut line = Geometry::empty(wkbLineString).unwrap();

line.set_points(&[0.0, 0.0, 1.0, 0.0, 1.0, 1.0], CoordinateLayout::XyXy)
.unwrap();
assert_eq!(line.geometry_type(), OGRwkbGeometryType::wkbLineString);
assert_eq!(line.get_point(0), (0.0, 0.0, 0.0));
assert_eq!(line.get_point(1), (1.0, 0.0, 0.0));
assert_eq!(line.get_point(2), (1.0, 1.0, 0.0));

line.set_points(
&[0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 2.0],
CoordinateLayout::XyzXyz,
)
.unwrap();
assert_eq!(line.geometry_type(), OGRwkbGeometryType::wkbLineString25D);
assert_eq!(line.get_point(0), (0.0, 0.0, 0.0));
assert_eq!(line.get_point(1), (1.0, 0.0, 1.0));
assert_eq!(line.get_point(2), (1.0, 1.0, 2.0));

line.set_points(
&[0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.5, 1.0, 1.0, 2.0, 1.0],
CoordinateLayout::XyzmXyzm,
)
.unwrap();
assert_eq!(line.geometry_type(), OGRwkbGeometryType::wkbLineStringZM);
assert_eq!(line.get_point_zm(0), (0.0, 0.0, 0.0, 0.0));
assert_eq!(line.get_point_zm(1), (1.0, 0.0, 1.0, 0.5));
assert_eq!(line.get_point_zm(2), (1.0, 1.0, 2.0, 1.0));

// M-values kept.
line.set_points(
&[0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.0, 1.0, 2.0],
CoordinateLayout::XxYyZz,
)
.unwrap();
assert_eq!(line.geometry_type(), OGRwkbGeometryType::wkbLineStringZM);
assert_eq!(line.get_point_zm(0), (0.0, 1.0, 0.0, 0.0));
assert_eq!(line.get_point_zm(1), (1.0, 1.0, 1.0, 0.5));
assert_eq!(line.get_point_zm(2), (1.0, 2.0, 2.0, 1.0));

assert!(line
.set_points(
&[0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.0, 1.0],
CoordinateLayout::XxYyZz
)
.is_err());
assert!(line
.set_points(
&[0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 0.0, 1.0],
CoordinateLayout::X
)
.is_err());
}

#[test]
fn test_get_points() {
let mut line = Geometry::empty(wkbLineStringZM).unwrap();
Expand Down

0 comments on commit 92901d2

Please sign in to comment.