Skip to content

Commit

Permalink
Merge pull request #3 from Pandicon/main
Browse files Browse the repository at this point in the history
Add polygons support and prepare for version 0.2.0
  • Loading branch information
Pandicon authored Oct 7, 2024
2 parents 1ee51f2 + fa7b097 commit 4aab092
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 44 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
# Changelog
## 0.2.0 - 2024-10-07 - Polygons support
This release focused on bringing in polygons support, but that required adding several other features :D

### ⭐ Added
- Polygons construction from vertices
- Checking if a point is inside a polygon
- A function to get the closest point on an arc to a given point
- An example was added to showcase the use of the `Polygon` API
- The README file now includes an example of using the `Polygon::contains_point` function for determining which stars are inside a constellation.

### 🐛 Fixed
- Constructing a great circle perpendicular to an arc actually creates a great circle instead of a new arc.
- Identical great circles are now checked by using the circles' precomputed normals. Before they were checked using new normals, which were however not normalized, leading to wrong results when circles were defined by points close to each other.

### Improved
- The wording of the documentation was changed in several places.

## 0.1.0 - 2024-10-02 - Initial release
This is the initial release of the crate after splitting it away from another codebase.

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spherical_geometry"
version = "0.1.0"
version = "0.2.0"
authors = ["Pandicon"]
edition = "2021"

Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
# Spherical Geometry
## Table of contents

- [Basic information](#basic-information)
- [Examples](#examples)
- [Filtering stars by constellation](#filtering-stars-by-constellation)
- [GeCAA theory task](#gecaa-theory-task)
- [Library state](#library-state)

## Basic information
A library for handling geometry on the surface of a sphere.

This library combines spherical and vector geometry to perform operations with points, [great circles](https://en.wikipedia.org/wiki/Great_circle), great circle arcs... A great circle is an equivalent of a straight line in planar geometry - it is the shortest path between two points on a sphere.
This library combines spherical and vector geometry to perform operations with points, [great circles](https://en.wikipedia.org/wiki/Great_circle), great circle arcs, and spherical polygons. A great circle is an equivalent of a straight line in planar geometry - it is the shortest path between two points on a sphere.

Doing geometry on a sphere requires using [spherical trigonometry](https://en.wikipedia.org/wiki/Spherical_trigonometry) and being very careful when taking `arcsin` etc. to get angles, as one often gets false results.

## Examples
More examples can be found in the `examples` folder. The unit tests can also serve as ones.
### Filtering stars by constellation
This uses the `Polygon` API, checking if each of the stars is inside the tested constellation polygon.

Testing all constellations on the sky, all of them worked without any issues (stars deemed to be inside the constellation are marked in green).

<details open>
<summary>The constellation of Draco</summary>

![Draco](./images/constellations-detection/draco.png)

</details>

<details>
<summary>The constellation of Octans</summary>

![Octans](./images/constellations-detection/octans.png)

</details>

<details>
<summary>The constellation of Leo</summary>

![Leo](./images/constellations-detection/leo.png)

</details>

### GeCAA theory task
Below is an example of solving the [GeCAA 2020 Theory task 7](https://gecaa.ee/wp-content/uploads/2020/10/GeCAA-Theoretical-solutions.pdf) analytically.
```rust
use spherical_geometry::{SphericalPoint, GreatCircle};
Expand Down Expand Up @@ -36,14 +73,14 @@ fn gecaa_2020_theory_7() {
assert!((ra_2_corr - ra_2).abs() < delta && (dec_2_corr - dec_2).abs() < delta);
}
```
More examples are either in the documentation or the unit tests can well serve as ones.

## Library state
The library is in active development, more features are expected to be added, see the table below for planned features. The API should not change much from the current state, but there are no guarantees.

State key:
- 🟢 - fully implemented
- 🟡 - partially implemented
- 🟠 - implemented, but partially broken
- 🔴 - not yet implemented

| Feature | State |
Expand All @@ -66,6 +103,6 @@ State key:
| Intersection with great circle | 🟢 |
| Clamped intersection with great circle (returning the closest endpoint if no intersection is on the arc) | 🟢 |
| Intersection with another arc | 🔴 |
| **Polygons** | 🔴 |
| Construction from vertices | 🔴 |
| Check if it contains a point | 🔴 |
| **Polygons** | 🟢 |
| Construction from vertices | 🟢 |
| Check if it contains a point | 🟢 |
13 changes: 13 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Examples
This folder contains examples for using the library. You can run each of them with `cargo run --example <example-name>` (for example `cargo run --example cli_polygon_test`).

## cli polygon test
The core of this example was developed by [@bipentihexium](https://github.com/bipentihexium). It is a cli utility to test points on the sphere if they are inside a polygon. The default polygon is a non-convex one that kept causing issues in development :D It accepts several command line arguments, in order:
- `<height>` - how many rows to print the result into (kind of resolution). If it can not be parsed to an `i32`, defaults to `50`.
- `<width>` - how many columns to print the result into (kind of resolution). If it can not be parsed to an `i32`, defaults to `height*2`.
- `<dec-start>` - the declination to start at, in radians. If it can not be parsed to an `f32`, defaults to `-PI/2.0`.
- `<dec-end>` - the declination to end at, in radians. If it can not be parsed to an `f32`, defaults to `PI/2.0`.
- `<ra-start>` - the right ascension to start at, in radians. If it can not be parsed to an `f32`, defaults to `-PI`.
- `<ra-end>` - the right ascension to end at, in radians. If it can not be parsed to an `f32`, defaults to `PI`.

Points considered to be inside the polygon are yellow, those outside are purple, and colours in between indicate a state in between - it uses MSAA, so each printed point actually checks multiple points in its area and averages the result.
62 changes: 62 additions & 0 deletions examples/cli_polygon_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// The core of this example was developed by [@bipentihexium](https://github.com/bipentihexium)

use spherical_geometry::{EdgeDirection, Polygon, SphericalPoint};
use std::f32::consts::PI;

const DEFAULT_Y_RANGE: i32 = 50;

fn main() {
let args: Vec<String> = std::env::args().collect();

let pol = Polygon::new(
vec![
SphericalPoint::new(0.0, 0.0),
SphericalPoint::new(0.0, 0.5),
SphericalPoint::new(1.2, 0.5),
SphericalPoint::new(0.8, 0.25),
SphericalPoint::new(1.2, 0.0),
],
EdgeDirection::CounterClockwise,
)
.expect("The polygon should be constructable");
let y_range = if args.len() < 2 { DEFAULT_Y_RANGE } else { args[1].parse::<i32>().unwrap_or(DEFAULT_Y_RANGE) };
let x_range = if args.len() < 3 { y_range * 2 } else { args[2].parse::<i32>().unwrap_or(y_range * 2) };
let (y_start, y_end) = if args.len() < 5 {
(-PI / 2.0, PI / 2.0)
} else {
(args[3].parse::<f32>().unwrap_or(-PI / 2.0), args[4].parse::<f32>().unwrap_or(PI / 2.0))
};
let (x_start, x_end) = if args.len() < 7 {
(-PI, PI)
} else {
(args[5].parse::<f32>().unwrap_or(-PI), args[6].parse::<f32>().unwrap_or(PI))
};
let ysr = 4;
let xsr = 2;
for y in 0..y_range {
for x in 0..x_range {
let xfns = (x as f32) / (x_range as f32);
let yfns = (y as f32) / (y_range as f32);
let pns = SphericalPoint::new((x_end - x_start) * xfns + x_start, -((y_end - y_start) * yfns + y_start));
let mut inc = 0u32;
let samples = xsr * ysr;
for sy in 0..ysr {
for sx in 0..xsr {
let xf = ((x as f32) + (sx as f32) / (xsr as f32)) / (x_range as f32);
let yf = ((y as f32) + (sy as f32) / (ysr as f32)) / (y_range as f32);
let p = SphericalPoint::new((x_end - x_start) * xf + x_start, -((y_end - y_start) * yf + y_start));
if pol.contains_point(&p).unwrap() {
inc += 1;
}
}
}
let col = (inc as f32) / (samples as f32);
let col_r = 255.0 * (2.83 * col * col - 2.36 * col + 0.5);
let col_g = 255.0 * (2.0 * col - col * col);
let col_b = 255.0 * 0.7 * (1.0 - col * col);
let isp = pol.vertices().iter().map(|p2| pns.distance(p2)).fold(f32::INFINITY, |a, b| a.min(b)) < 0.1;
print!("\x1b[48;2;{};{};{}m{}", col_r as u32, col_g as u32, col_b as u32, if isp { '#' } else { ' ' });
}
println!("\x1b[0m");
}
}
Binary file added images/constellations-detection/draco.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/constellations-detection/leo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/constellations-detection/octans.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions src/great_circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl GreatCircle {
/// Creates a new great circle passing through the provided point and perpendicular to the current circle
///
/// # Errors
/// If the point and the pole of the current circle are essentially equal or essentially antipodal, returns `SphericalError::AntipodalOrTooClosePoints` as in the case of identical or antipodal points the great circle is not uniquely defined
/// If the point and the pole of the current circle are essentially equal or essentially antipodal, returns `SphericalError::AntipodalOrTooClosePoints` as in the case of identical or antipodal points the great circle is not uniquely defined.
pub fn perpendicular_through_point(&self, point: &SphericalPoint) -> Result<Self, SphericalError> {
let point_1 = SphericalPoint::from_cartesian_vector3(self.normal());
Self::new(point_1, *point)
Expand All @@ -63,8 +63,8 @@ impl GreatCircle {
/// # Errors
/// If the great circles are (essentially) parallel (equal to each other), returns `SphericalError::IdenticalGreatCircles` as then there is an infinite amount of intersections. You can handle this error as an equivalent of "all points on the circle are intersections".
pub fn intersect_great_circle(&self, other: &GreatCircle) -> Result<[SphericalPoint; 2], SphericalError> {
let normal1 = self.start.cartesian().cross(&self.end.cartesian());
let normal2 = other.start.cartesian().cross(&other.end.cartesian());
let normal1 = self.normal();
let normal2 = other.normal();

let res = normal1.cross(&normal2);
if res.magnitude_squared() < VEC_LEN_IS_ZERO.powi(2) {
Expand Down
Loading

0 comments on commit 4aab092

Please sign in to comment.