geo
geo copied to clipboard
Porting scaling and other Affine transformations from JTS
Currently geo implements rotation and translations, but misses others like scale (see static methods on JTS AffineTransformation). Does it make sense to port the entire AffineTransformation as a single matrix-based transformation with similar interface? For my usecase I only need scale, but I'm sure others may encounter other needs. Additionally a matrix based transformation allows multiple transforms to be combined into one - something that doesn't appear to be possible with the rust-geo's implementation. If ok, I would be happy to try to implement it.
Sorry for the slow response.
I for one would love to see a comprehensive affine feature. Do you have a proposal for what the API would look like?
@michaelkirk not really, I am just now getting deeper into the geo-types and various algorithms. I would think it would be similar to JTS -- a builder pattern, combining various transformations into a single matrix, and later a function on that builder to convert a geo-type into a different geo-type (or possibly in-place). Uncertain yet, suggestions welcomed.
convert a geo-type into a different geo-type
The output type should always be the same as the input. A Linestring will never become a Point, right?
possibly in-place
generally I'd start with a mutable implementation, because then people have the option of using it without incurring the memory overhead of creating a new object. Building an immutable versions on top of that can be as trivial as.clone().some_mutable_method(). Though there may be reasons to prefer a different approach depending on the algorithm.
When reasonable, I like to expose both mutable and immutable flavors, like here: https://github.com/georust/proj/blob/main/src/geo_types.rs#L65
My first thought for some api usage would be something like:
(names subject to change, mostly thinking about the way the components interact)
use geo_types::{CoordNum, Coordinate, Polygon, Geometry};
pub struct AffineTransform<T: CoordNum>([[T; 3]; 3]);
impl<T: CoordNum> AffineTransform<T> {
pub fn scale(x: T, y: T) -> Self {
Self::identity().scaled(x, y)
}
pub fn scaled(mut self, x: T, y: T) -> Self {
self.0[0][0] = self.0[0][0] * x;
self.0[1][1] = self.0[1][1] * y;
self
}
pub fn identity() -> Self {
Self([
[T::one(), T::zero(), T::zero()],
[T::zero(), T::one(), T::zero()],
[T::zero(), T::zero(), T::one()],
])
}
pub fn compose(mut self, other: Self) -> Self {
todo!()
}
pub fn apply_coord(&self, coord: &mut Coordinate<T>) {
todo!()
}
pub fn apply_slice(&self, coords: &mut [Coordinate<T>]) {
// maybe there's something more optimal we can do for slices i.e. vectorize
todo!()
}
}
// NOTE: I expect, people will often look *on* the geometry types for this functionality
// so it would probably be good to have an inverse API on the geometries.
trait AffineTransformable<T: CoordNum> {
fn affine_transform(&mut self, transform: AffineTransform<T>);
fn scale(&mut self, x: T, y: T) {
self.affine_transform(AffineTransform::scale(x, y));
}
}
impl<T: CoordNum> AffineTransformable<T> for Polygon<T> {
fn affine_transform(&mut self, transform: AffineTransform<T>) {
todo!()
}
}
impl<T: CoordNum> AffineTransformable<T> for Geometry<T> {
fn affine_transform(&mut self, transform: AffineTransform<T>) {
todo!()
}
}
...etc.
This is done and we now support all affine operations as well as a generalised composable affine op mechanism.