geo icon indicating copy to clipboard operation
geo copied to clipboard

Porting scaling and other Affine transformations from JTS

Open nyurik opened this issue 3 years ago • 3 comments
trafficstars

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.

nyurik avatar Feb 13 '22 14:02 nyurik

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 avatar Mar 07 '22 18:03 michaelkirk

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

nyurik avatar Mar 07 '22 19:03 nyurik

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.

michaelkirk avatar Mar 07 '22 19:03 michaelkirk

This is done and we now support all affine operations as well as a generalised composable affine op mechanism.

urschrei avatar Sep 03 '22 11:09 urschrei