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

Implement Offset Algorithm #935

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from

Conversation

thehappycheese
Copy link
Contributor

@thehappycheese thehappycheese commented Nov 18, 2022

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

Defines an OffsetCurve trait and implements the associated function .offset_curve(distance) for some geometry types. The offset_curve function offsets the edges of a geometry by a specified distance, where the sign of distance determines if the edges of the geometry are offset to the 'left' or 'right'.

'Offset curve' is like a 'one-sided buffer' and, unlike buffer, it is only meaningful for some geometry types. The table below summarises the different outputs.

A 'clipped offset curve' has had parts of the output removed to ensure that all edges in the output are at least distance from the input, even if the input is self-overlapping. So far, this clipping step has not been implemented.

The following table is a work in progress:

Input Offset Curve Offset Curve - Clipped Buffer
Point Undefined Undefined Circle (Polygon)
Line Line Line Capsule (Polygon)
LineString LineString
(possibly self overlapping)
MultiLineString Sausage (Polygon)
Polygon Option<Polygon>
(always closed, but possibly malformed)
Option<Polygon>
(possibly unclosed, possibly malformed)
Polygon
... ... ... ...

GEOS and JTS

Choice of Algorithm

I am not sure if the algorithm I am trying to implement here is any better or worse than those, but it is one I have got working in the past in python so I figure I have the best chance of success with it. It loosely follows the algorithm described by
Xu-Zheng Liu, Jun-Hai Yong, Guo-Qin Zheng, Jia-Guang Sun. An offset algorithm for polyline curves. Computers in Industry, Elsevier, 2007, 15p. inria-00518005

Trait Implementations

  • Line<T>
  • LineString<T>
  • MultiLineString<T>
  • Triangle<T>
  • Rectangle<T>
  • Polygon<T>
  • MultiPolygon<T>
  • Point<T>
    • MultiPoint<T>
    • Geometry<T>
    • GeometryCollection<T>

For coordinate types

  • where T:CoordFloat
  • where T:CoordNum (??? seems tricky)

Note:
Offset for Point Can't be defined without confusion.
The user may expect the result to be one of the following:

  1. Return the input unchanged,
  2. a translation (but in what direction?)
  3. a circle (like a buffer, but as previously explained,
    the offset operation is different to the buffer operation).
  4. an empty LineString (weird, yes, but this is actually what GEOS does 🤔)

Issues to Fix:

  • Change name from Offset to OffsetCurve to match terminology in GEOS
    and jts
  • Match offset sign / direction with GEOS and JTS if possible
  • Make proper use of SimpleKernel / RobustKernel
  • Prevent execution when the specified offset distance is zero
  • Mitre-limit is implemented so that a LineString which doubles-back on
    itself will not produce an elbow at infinity
    • Make Mitre Limit configurable?
  • Option to clip output such that non-adjacent line segments in the output
    are not self-intersecting.

Hopefully I have started off my contribution in the right direction? :)

Many thanks for your time

Copy link
Member

@urschrei urschrei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @thehappycheese, welcome and thanks for the contribution! I've left a load of review comments – please don't feel that you need to address them all at once. In particular, it looks like you're having some trouble using the Kernel trait, and this might be a good place to start. I and other contributors are happy to help here, and we can do it inline in the PR, and / or on Discord: https://discord.gg/Fp2aape

// TODO: I'm still confused about how to use Kernel / RobustKernel;
// the following did not work. I need to read more code
// from the rest of this repo to understand.
// if Kernel::orient2d(*a, *b, *d) == Orientation::Collinear {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orientation is an enum, so you have to use a match for control flow.

something like

match Kernel::orient2d(*a, *b, *c) {
    Orientation::Collinear => { // keep going }
    _ => { // not collinear, bail out or test different combination?? }
}

As you note, the use of arbitrary "magic" threshold values without considerable justification and accompanying comments explaining the reasoning behind it for future maintainers is unlikely to be accepted (I've just finished removing an algorithm containing a lot of these that I had the misfortune of writing myself…), so if we can use existing mechanisms that would be much better.

@@ -0,0 +1,85 @@
use crate::CoordFloat;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused about this. Would the dot product sign, i.e. the Orientation suffice? It looks like you're just checking whether the result is -1, 1, or 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, looks like my inept git-fu has disconnected all your comments from the code 😕 I think maybe re-basing the branch on my fork was the wrong thing to do.

cross_product simplifies line_segment_intersection_with_parameters(): While it is used for a collinear check at the start of that function (which could be replaced with Kernel::dot_product_sign or Kernel::orient2d) it is used twice more further along to calculate the parameters t_ab and t_cd so I do not believe it can be substituted for an orientation check.

let t_ab = cross_product_2d(ac, cd) / ab_cross_cd;
let t_cd = -cross_product_2d(ab, ac) / ab_cross_cd;

line_segment_intersection_with_parameters() is currently only used via a wrapper function line_segment_intersection_with_relationships(); in that function it checks each t_ab and t_cd to find which of three ranges they lie in:

  • less than zero
  • between zero and one
  • greater than one

It's possible there is some tricky way to express this check in terms of orient2d(), I'll have a think about it.

It does feel weird to have cross_product() out in it's own module. In the past I have worked with libraries that have a Vector2 type where this kind of function would be implemented. It is simple enough to be inline perhaps, but I wrote up all the tests and documentation because in the past I have made many mistakes with putting negatives in the wrong place and if you make this kind of mistake an even number of times you might not realize since it cancels out 😋

geo/CHANGES.md Outdated
@@ -11,6 +11,7 @@
* <https://github.com/georust/geo/pull/928>
* Added `RemoveRepeatedPoints` trait allowing the removal of (consecutive)
repeated points.
* Added very basic `Offset` trait and implemented it for `Line` and `LineString`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor nit: there's no need to qualify this as "very basic", since the docs will make the limitations clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I will try to reword it to be more specific, or be more clear about any limitations in the rest of the docs

///
/// Signed offset of Geometry assuming cartesian coordinate system.
///
/// This is a cheap offset algorithm that is suitable for flat coordinate systems
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to specify this as geo operates only on coordinates in the real plane (with the exception of some explicitly documented convenience algorithms for spherical coordinates). There is an explicit assumption that only projected / cartesian coordinates are used. That this algorithm may be suitable for coordinates near the equator is a coincidence as far as the library is concerned.

/// some may be removed during development,
/// others are very hard to fix.
///
/// - No checking for zero length input.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific difficulty you've encountered while trying to implement a check against this?

/// others are very hard to fix.
///
/// - No checking for zero length input.
/// Invalid results may be caused by division by zero.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where could this occur?

///
/// - No checking for zero length input.
/// Invalid results may be caused by division by zero.
/// - No check is implemented to prevent execution if the specified offset
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need a check for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added this check to LineString but it probably isn't worth it for Line. I have added comments to the relevant code in my next commit.

/// distance is zero.
/// - Only local cropping where the output is self-intersecting.
/// Non-adjacent line segments in the output may be self-intersecting.
/// - There is no mitre-limit; A LineString which
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fairly significant limitation, but if the algorithm can't handle it, then we should consider a check for self-crossing LineStrings (I believe we already have one) and bail out early if this is encountered.

where
T: CoordFloat,
{
fn offset(&self, distance: T) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is that a positive (or outward) offset on a LineString produces a Polygon (as per JTS, GEOS, and derived libraries). Could you explain the reasoning here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no context on how JTS/GEOS/etc does things, but offsetting a LineString and getting a LineString has many use cases. If you start with something representing the center of a road and want the left or right edge, or the center of a lane, for example. To buffer around a LineString and get a polygon, you could offset inwards (the hole) and outwards (the exterior), right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dabreegster I'm struggling to visualise that. Do you have an example? The standard implementations of offset (in plain language, buffer) operations always produce a polygon from a Line or LineString as far as I know. The Shapely docs are representative here: https://shapely.readthedocs.io/en/stable/manual.html#object.buffer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disclaimer that I haven't looked at this implementation, but just a note on terminology...

JTS has an "offset curve" which is a line string to line string transformation.

a line lying on one side of another line at a given offset distance

http://lin-ear-th-inking.blogspot.com/2022/01/jts-offset-curves.html?m=1

Copy link
Contributor Author

@thehappycheese thehappycheese Nov 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @urschrei thank you so much for all your feedback I really appreciate it, I will do my best to address each one :)

Regarding the sign of the specified offset, I agree that it would be strange if a negative offset resulted in an inset for polygons. I am certainly open to changing this, I will add it to the TODO list in my PR.

On this @dabreegster has already explained my reasoning really well; To me a buffer operation different to an offset; I am interested in offsetting road center lines, not producing enclosed polygons.

screenshot

I recognize that a buffer operation is more generally useful, but I also just need this signed offset function. This offset trait can be extended by a a few clipping and stitching steps to a full buffer operation, this is the end goal of the paper I am following.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm struggling to visualise that. Do you have an example?

https://a-b-street.github.io/docs/tech/map/geometry/index.html#projecting-a-polyline, and for the inspiration behind it, https://wwwtyro.net/2019/11/18/instanced-lines.html. The JTS offset curve looks like the same thing. I don't know what this linestring -> linestring operation should be called, but I have plenty of use cases for it, as well as buffering (to get a polygon)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks all, it looks like it's just confusion about terminology, mostly on my part. It might be worth discussing whether the current trait name conveys the functionality clearly enough, though maybe it's just a question of ensuring that the module-level docstring does this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keen to align terminology with suggestions / other libraries :)

offset_curve is nice because it makes it more obvious why this type of signed offset cannot be implemented for a Point object. Point objects can only have a "buffer" or "outset" applied, not an 'offset_curve'.

Ah apologies for the broken link I picked it from one of my public repos but didn't realize the image link is to a private repo; this one should work?

Screenshot of road data custom PowerBI visualization

This is from a custom PowerBI visual I created. I will eventually make that code public when I get a chance to clean up /republish repo. I want the PowerBI visual to dynamically connect to a rust server for a specialized kind of chainage-based geo-coding.

@thehappycheese
Copy link
Contributor Author

thehappycheese commented Nov 20, 2022

I have deployed an interactive tester thing here https://geo-tester.thehappycheese.com/

Source code here: https://github.com/thehappycheese/geo-offset-test-server

Screenshot of interactive tester

Hoping it will help me find edge cases as I start to implement the miter limit an clipping step of the algorithm. To be honest though after playing with it for a bit, I am a little worried that some edge cases are going to be pretty challenging.

@michaelkirk
Copy link
Member

As an aside, Wow, that's a really nice demo!

We've talked in the past about having interactive tools like this as a kind of documentation for some of our algorithms, but I think think this is the first actual working example. 👏 👏👏

@michaelkirk
Copy link
Member

Hi @thehappycheese - this is an exciting new feature that I think a lot of people would like to use. Are you still interested in working on it? Is there anything I can do to help move it along?

@thehappycheese
Copy link
Contributor Author

thehappycheese commented Feb 15, 2023

Hi @thehappycheese - this is an exciting new feature that I think a lot of people would like to use. Are you still interested in working on it? Is there anything I can do to help move it along?

Hi @michaelkirk 😃

I have to admit I stalled out on this project; There are a few contributing factors

  1. While the basic offset algorithm is done, the clipping of the output part is not started.
    • Clipping the output geometry is pretty tricky, I'm feeling a bit apprehensive about tackling it
    • And also, my main motivating use-case for this feature does not actually require this additional clipping step
    • Perhaps this could just be an offset_curve_raw and in the future there could be an offset_curve_clipped?
  2. I have introduced several new data structures and traits which overlap/conflict with existing code elsewhere in the project... On the one hand it makes sense to push forward and get the offset_curve function working then resolve these later. On the other hand, perhaps I should be trying to build up the required infrastructure with smaller separate pull requests first.
  3. There are now two implementations of trait OffsetCurveOld (old) and trait OffsetCurve (new)
    • The new one uses trait LineStringOffsetSegmentPairs which was intended to make things tidy inside the trait OffsetCurve which I think it does... but it came at the cost of much headaches for me working out how to make iterators work.
    • Anyway I succeeded and now I think I will delete trait OffsetCurveOld
    • BUT the iterator trait LineStringOffsetSegmentPairs needs one more feature;
      • support wrapping over to the first segment at the end to work on closed shapes
      • I stopped before implementing that and took a break over Christmas and have not made it back here yet in the new year :/

I am still keen to get back into this but it might take me a few more weeks to get going again. In the meantime, if anyone is keen to jump in I would be happy to collaborate in any way possible :)

@thehappycheese
Copy link
Contributor Author

thehappycheese commented Jun 21, 2023

My current draft PR is stalled because it got a bit too messy with too many new features at once.
Partly due to actually needing new features, and partly because I am unsure how or hesitant on using existing features.

In this comment I am trying to figure out if I can break off smaller bite-sized contributions and have a go at those first.
I'll probably add to this comment later

Required for Simple Offset

  • In my current code I used a new trait impl VectorExtensions for Coord for the vector operations described in the dot points below. But VectorExtensions doesn't gel with the rest of the crate. To move forward I need to find replacements for the following functions

    • Desireable: 2D Wedge Product (aka exterior product, aka perpendicular product, aka 2d cross product) ( See )

      • Simplifies line intersection calculations see ( See example )
      • Currently implemented directly on geotypes::Point but its is not right it takes three arguments which is not what we need ( See )
      • It is related to Kernel::orient2d (which returns an enum) but we need the numeric value
      • Currently implemented as geotypes::Line::determinant() which is a bit wierd to use (Line::new(a, b).determinant() vs a.wedge(b))
      • nalgebra crate has perp for Vector2 like a.perp(&b)
    • Desireable: Dot Product

      • Currently implemented directly on geotypes::Point ( See )
      • Feels like it should live together with some of these other ops... should they all be in the impl Point or should it be in a trait?
      • nalgebra crate has dot for Vector2 like a.dot(&b)
    • Desireable: Vector Magnitude and MagnitudeSquared

      • Available as Line::line_euclidean_length, but this requires unnecessary construction of Line object
      • On further inspection separating these functions may not be needed... looks like with a bit of refactoring the EuclideanDistance trait could be used
      • I cant find a MagnitudeSquared... It is used for (perhaps unnecessary) micro-optimization to avoid calculating the square root when it is not needed.
    • Desireable: left and right functions for explicit 90 degree rotation around origin (for Coord or Point)

      • My current implementation uses Coord::<VectorExtensions>::left() and Coord::<VectorExtensions>::right() ( See )
        • These are "fast" and "precise" implementations because they just swap the x and y ordinates and negate one or the other
      • Alternatively there could be associated functions for Rotate trait ::left() and ::right()?
        • Actually I don't see a '::rotate_about_origin()'? is there a reason we only have ::rotate_around_point??
        • Rotate::rotate_about_origin(90.0) seems like it might be a reasonable implementation
        • Rotate::left() doesnt make sense for anything other than a point
      • Alternatively it could be a special constructor for struct AffineTransform ::left() and ::right().
        • Only really makes sense for Point/Coord
      • I still think a trait that explicitly allows 'left' and 'right' function directly on Coord and/or Point would help with clarity and testing
  • Required: Specialized Line segment intersection;

    • In my current code I am using a special implementation that can
      • report if the intersection Real | Virtual( negative_side | positive_side )
      • the parameters t1 and t0 of the intersection points
    • Alternatively could augment existing algorithm/line_intersection.rs or add alternative implementation to that module
    • There is a private cartesian_intersect predicate in simplify_vw.rs ( See ) which may be redundant
  • Required: trait LinesIter (already got this one)

    • Need a pairwise iterator to iterate over pairs of lines.
      • The standard library offers tuple_windows which clones elements
        • Line is Copy so this should be fine, I remember having trouble using it though
    • Maybe need a wrap_one implementation which would iterate over all segments plus the first one again
      • Not sure yet, but I have a feeling with wrap_one it would be trivial to implement offset_curve for closed shapes using almost the same code as open shapes

Required for Clipped Offset

  • Required: split impl SplitBy<LineString> for LineString {fn split_by(self, other)->MultiLineString}
    • These discussions might help: LineString intersection API #985 and LineString slicing #986
    • In old python implementation I did this in two steps; first an algorithm that finds the 'parameters' where the intersection occurs ( See ) then an split-at-parameters function ( see ) note sure if that's the way to go but it seemed pretty efficient
  • Required: 'CookieCut' operation; separate LineString by intersection with a circle
  • Required: Efficient Method to stitch together a collection of LineStrings where the endpoints are touching
  • Required: something called the 'General closest point pair' by the paper referenced in my original post See Definition 6 and Figure 12 I got it implemented in python here
  • Desireable: MeasuredLineString (and/or MeasuredLine?)
    • Reduce repeated magnitude calculations by storing segment length and/or overall line string length
    • Can mitigate the need for this by implementing supporting algorithms using "Segment-Fraction Line String Parameterization" where the integer part and fraction part of the parameter are used to refer to the segment number and segment fraction respectively.
    • Discussion here may be relevant Prepared Geometries #803

Point vs Coord, Z / M

With all that in mind, is the potential future trait Point the place to implement all the strictly 2D linear-algebra functions like dot, cross, perp, left/right etc?

@thehappycheese thehappycheese mentioned this pull request Jun 27, 2023
2 tasks
bors bot added a commit that referenced this pull request Jul 28, 2023
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct.
Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place.

Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together.

For example

- `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`,
  - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc.
  - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum
- `dot_product` `geotypes::Point::dot`,
- `magnitude` in `Line::line_euclidean_length`
- `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29))
- `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability.

For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . 

Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations?

Note:

 - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings.
 - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 
   - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps`

Many thanks for your time and any feedback you can provide


Co-authored-by: thehappycheese <the.nicholas.archer@gmail.com>
bors bot added a commit that referenced this pull request Jul 31, 2023
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct.
Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place.

Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together.

For example

- `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`,
  - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc.
  - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum
- `dot_product` `geotypes::Point::dot`,
- `magnitude` in `Line::line_euclidean_length`
- `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29))
- `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability.

For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . 

Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations?

Note:

 - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings.
 - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 
   - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps`

Many thanks for your time and any feedback you can provide


Co-authored-by: thehappycheese <the.nicholas.archer@gmail.com>
bors bot added a commit that referenced this pull request Jul 31, 2023
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct.
Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place.

Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together.

For example

- `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`,
  - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc.
  - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum
- `dot_product` `geotypes::Point::dot`,
- `magnitude` in `Line::line_euclidean_length`
- `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29))
- `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability.

For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . 

Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations?

Note:

 - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings.
 - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 
   - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps`

Many thanks for your time and any feedback you can provide


Co-authored-by: thehappycheese <the.nicholas.archer@gmail.com>
@thehappycheese thehappycheese mentioned this pull request Aug 3, 2023
13 tasks
dabreegster added a commit to dabreegster/geo that referenced this pull request Jan 12, 2024
dabreegster added a commit to dabreegster/geo that referenced this pull request Feb 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants