Skip to content

Commit

Permalink
add support for no-std
Browse files Browse the repository at this point in the history
How it was done
===============

First step was to replace the references to the `std` crate by `core` or
`alloc` when possible.
Which was always the case, except for the `Error` trait which isn't
available in `core` on stable[1].

Another change, that may impact the performance a bit, was to replace
the usage of Hash{Map,Set} by the B-Tree variant, since the hash-based
one aren't available in core (due to a lack of a secure random number
generator).
There should be no visible impact on default build, since we're still
using hashtable when `std` is enabled (which is the default behavior).
Maybe I should give a shot to `hashbrown` to bring back the hashtable
version into `no-std` as well.

The last change was a bit more annoying (and surprising): most of the
math functions aren't available in core[2].
So I've implemented a fallback using `libm` when `std` isn't enabled.
Note that due to Cargo limitation[3] `libm` is always pulled as a
dependency, but it won't be linked unless necessary thanks to
conditional compilation.

Known limitations
================

Cannot use the `geo` feature without `std`, mainly because the `geo`
crate itself isn't `no-std` (as well as a bunch of its dependencies I
guess).

--------

[1]: rust-lang/rust#103765
[2]: rust-lang/rfcs#2505
[3]: rust-lang/cargo#1839

Closes: #19
  • Loading branch information
grim7reaper committed Jan 28, 2024
1 parent 1b081cf commit 6dbf502
Show file tree
Hide file tree
Showing 56 changed files with 431 additions and 192 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Possible sections are:
<!-- next-header -->
## [Unreleased] - ReleaseDate

### Added

- add `no_std` support (`std` is still enabled by default though)

## [0.5.1] - 2024-01-27

### Fixed
Expand Down
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ homepage = "https://docs.rs/h3o"
repository = "https://github.com/HydroniumLabs/h3o"
license = "BSD-3-Clause"
keywords = ["geography", "geospatial", "gis", "h3", "spatial-index"]
categories = ["science::geo"]
categories = ["science::geo", "no-std"]

[package.metadata.docs.rs]
all-features = true
Expand All @@ -24,21 +24,22 @@ pre-release-replacements = [
]

[features]
default = []
default = ["std"]
std = ["dep:ahash"]
geo = ["dep:geo", "dep:geojson"]
serde = ["dep:serde", "dep:serde_repr"]
tools = ["polyfit-rs"]

[dependencies]
ahash = { version = "0.8", default-features = false, features = ["std", "compile-time-rng"] }
ahash = { version = "0.8", optional = true, default-features = false, features = ["std", "compile-time-rng"] }
arbitrary = { version = "1.0", optional = true, default-features = false }
auto_ops = { version = "0.3", default-features = false }
konst = { version = "0.3", default-features = false, features = ["parsing"] }
either = { version = "1.0", default-features = false }
float_eq = { version = "1.0", default-features = false }
geo = { version = "0.27", optional = true, default-features = false }
geojson = { version = "0.24", optional = true, default-features = false, features = ["geo-types"] }
h3o-bit = { version = "0.1", default-features = false }
libm = { version = "0.2", default-features = false }
polyfit-rs = { version = "0.2", optional = true, default-features = false }
serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] }
serde_repr = { version = "0.1", optional = true, default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion src/base_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
coord::{CoordIJK, FaceIJK},
error, Direction, Face, NUM_PENTAGONS, NUM_PENT_VERTS,
};
use std::fmt;
use core::fmt;

/// Maximum value for a base cell.
pub const MAX: u8 = 121;
Expand Down
18 changes: 9 additions & 9 deletions src/boundary.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::LatLng;
use std::{fmt, ops::Deref};
use core::{fmt, ops::Deref};

/// Maximum number of cell boundary vertices.
///
Expand Down Expand Up @@ -42,14 +42,14 @@ impl Deref for Boundary {

impl fmt::Display for Boundary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[{}]",
self.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("-")
)
write!(f, "[",)?;
for (i, ll) in self.iter().enumerate() {
if i != 0 {
write!(f, "-")?;
}
write!(f, "{ll}")?;
}
write!(f, "]",)
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/coord/cube.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::CoordIJK;
use crate::math::{abs, round};

/// Cube coordinates.
///
Expand Down Expand Up @@ -29,11 +30,11 @@ impl CoordCube {

#[allow(clippy::cast_possible_truncation)] // on purpose
let (mut ri, mut rj, mut rk) =
{ (i.round() as i32, j.round() as i32, k.round() as i32) };
{ (round(i) as i32, round(j) as i32, round(k) as i32) };

let i_diff = (f64::from(ri) - i).abs();
let j_diff = (f64::from(rj) - j).abs();
let k_diff = (f64::from(rk) - k).abs();
let i_diff = abs(f64::from(ri) - i);
let j_diff = abs(f64::from(rj) - j);
let k_diff = abs(f64::from(rk) - k);

// Round, maintaining valid cube coords.
if i_diff > j_diff && i_diff > k_diff {
Expand Down
72 changes: 45 additions & 27 deletions src/coord/ijk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
//! a unique address consisting of the minimal positive `IJK` components; this
//! always results in at most two non-zero components.

#![allow(clippy::use_self)] // False positive with `auto_ops::impl_op_ex`

use super::{CoordCube, Vec2d, SQRT3_2};
use crate::{error::HexGridError, Direction};
use auto_ops::impl_op_ex;
use std::{cmp, fmt};
use crate::{
error::HexGridError,
math::{mul_add, round},
Direction,
};
use core::{
cmp, fmt,
ops::{Add, MulAssign, Sub},
};

// -----------------------------------------------------------------------------

Expand Down Expand Up @@ -116,7 +120,7 @@ impl CoordIJK {
}

pub fn distance(&self, other: &Self) -> i32 {
let diff = (self - other).normalize();
let diff = (*self - *other).normalize();

cmp::max(diff.i.abs(), cmp::max(diff.j.abs(), diff.k.abs()))
}
Expand All @@ -133,7 +137,7 @@ impl CoordIJK {
(f64::from(2 * i + j) / 7., f64::from(3 * j - i) / 7.)
};

Self::new(i.round() as i32, j.round() as i32, 0).normalize()
Self::new(round(i) as i32, round(j) as i32, 0).normalize()
}

/// Returns the normalized `IJK` coordinates of the indexing parent of a
Expand All @@ -154,7 +158,7 @@ impl CoordIJK {
)
};

Self::new(i.round() as i32, j.round() as i32, 0).checked_normalize()
Self::new(round(i) as i32, round(j) as i32, 0).checked_normalize()
}

/// Returns the normalized `IJK` coordinates of the hex centered on the
Expand Down Expand Up @@ -194,7 +198,7 @@ impl CoordIJK {
/// Returns the normalized `IJK` coordinates of the hex in the specified
/// direction from the current position.
pub fn neighbor(&self, direction: Direction) -> Self {
(self + direction.coordinate()).normalize()
(*self + direction.coordinate()).normalize()
}

/// Returns the `IJK` coordinates after a 60 degrees rotation.
Expand All @@ -213,35 +217,49 @@ impl CoordIJK {
}
}

impl_op_ex!(+ |lhs: &CoordIJK, rhs: &CoordIJK| -> CoordIJK {
CoordIJK{
i: lhs.i + rhs.i,
j: lhs.j + rhs.j,
k: lhs.k + rhs.k,
// -----------------------------------------------------------------------------

impl Add for CoordIJK {
type Output = Self;

fn add(self, other: Self) -> Self {
Self {
i: self.i + other.i,
j: self.j + other.j,
k: self.k + other.k,
}
}
});
}

impl Sub for CoordIJK {
type Output = Self;

impl_op_ex!(-|lhs: &CoordIJK, rhs: &CoordIJK| -> CoordIJK {
CoordIJK {
i: lhs.i - rhs.i,
j: lhs.j - rhs.j,
k: lhs.k - rhs.k,
fn sub(self, other: Self) -> Self {
Self {
i: self.i - other.i,
j: self.j - other.j,
k: self.k - other.k,
}
}
}

impl MulAssign<i32> for CoordIJK {
fn mul_assign(&mut self, rhs: i32) {
self.i *= rhs;
self.j *= rhs;
self.k *= rhs;
}
});
}

impl_op_ex!(*= |lhs: &mut CoordIJK, rhs: i32| {
lhs.i *= rhs;
lhs.j *= rhs;
lhs.k *= rhs;
});
// -----------------------------------------------------------------------------

impl From<CoordIJK> for Vec2d {
// Returns the center point in 2D cartesian coordinates of a hex.
fn from(value: CoordIJK) -> Self {
let i = f64::from(value.i - value.k);
let j = f64::from(value.j - value.k);

Self::new(0.5_f64.mul_add(-j, i), j * SQRT3_2)
Self::new(mul_add(0.5, -j, i), j * SQRT3_2)
}
}

Expand Down
71 changes: 37 additions & 34 deletions src/coord/latlng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ use super::{
RES0_U_GNOMONIC, SQRT7_POWERS,
};
use crate::{
error::InvalidLatLng, face, CellIndex, Face, Resolution, EARTH_RADIUS_KM,
TWO_PI,
error::InvalidLatLng,
face,
math::{acos, asin, atan2, cos, mul_add, sin, sqrt, tan},
CellIndex, Face, Resolution, EARTH_RADIUS_KM, TWO_PI,
};
use float_eq::float_eq;
use std::{
use core::{
f64::consts::{FRAC_PI_2, PI},
fmt,
};
use float_eq::float_eq;

/// Epsilon of ~0.1mm in degrees.
const EPSILON_DEG: f64 = 0.000000001;
Expand Down Expand Up @@ -153,15 +155,16 @@ impl LatLng {
/// ```
#[must_use]
pub fn distance_rads(self, other: Self) -> f64 {
let sin_lat = ((other.lat - self.lat) / 2.).sin();
let sin_lng = ((other.lng - self.lng) / 2.).sin();
let sin_lat = sin((other.lat - self.lat) / 2.);
let sin_lng = sin((other.lng - self.lng) / 2.);

let a = sin_lat.mul_add(
let a = mul_add(
sin_lat,
sin_lat,
self.lat.cos() * other.lat.cos() * sin_lng * sin_lng,
cos(self.lat) * cos(other.lat) * sin_lng * sin_lng,
);

2. * a.sqrt().atan2((1. - a).sqrt())
2. * atan2(sqrt(a), sqrt(1. - a))
}

/// The great circle distance, in kilometers, between two spherical
Expand Down Expand Up @@ -231,15 +234,15 @@ impl LatLng {

let r = {
// cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2
let r = (1. - distance / 2.).acos();
let r = acos(1. - distance / 2.);

if r < EPSILON {
return Vec2d::new(0., 0.);
}

// Perform gnomonic scaling of `r` (`tan(r)`) and scale for current
// resolution length `u`.
(r.tan() / RES0_U_GNOMONIC) * SQRT7_POWERS[usize::from(resolution)]
(tan(r) / RES0_U_GNOMONIC) * SQRT7_POWERS[usize::from(resolution)]
};

let theta = {
Expand All @@ -255,7 +258,7 @@ impl LatLng {
};

// Convert to local x, y.
Vec2d::new(r * theta.cos(), r * theta.sin())
Vec2d::new(r * cos(theta), r * sin(theta))
}

/// Finds the closest icosahedral face from the current coordinate.
Expand Down Expand Up @@ -290,12 +293,12 @@ impl LatLng {
/// Computes the azimuth to `other` from `self`, in radians.
#[must_use]
pub(crate) fn azimuth(self, other: &Self) -> f64 {
(other.lat.cos() * (other.lng - self.lng).sin()).atan2(
self.lat.cos().mul_add(
other.lat.sin(),
-self.lat.sin()
* other.lat.cos()
* (other.lng - self.lng).cos(),
atan2(
cos(other.lat) * sin(other.lng - self.lng),
mul_add(
cos(self.lat),
sin(other.lat),
-sin(self.lat) * cos(other.lat) * cos(other.lng - self.lng),
),
)
}
Expand All @@ -319,14 +322,14 @@ impl LatLng {
self.lat - distance // Due South.
}
} else {
self.lat
.sin()
.mul_add(
distance.cos(),
self.lat.cos() * distance.sin() * azimuth.cos(),
asin(
mul_add(
sin(self.lat),
cos(distance),
cos(self.lat) * sin(distance) * cos(azimuth),
)
.clamp(-1., 1.)
.asin()
.clamp(-1., 1.),
)
};

// Handle poles.
Expand All @@ -341,11 +344,11 @@ impl LatLng {
self.lng
} else {
let sinlng =
(azimuth.sin() * distance.sin() / lat.cos()).clamp(-1., 1.);
let coslng = self.lat.sin().mul_add(-lat.sin(), distance.cos())
/ self.lat.cos()
/ lat.cos();
self.lng + sinlng.atan2(coslng)
(sin(azimuth) * sin(distance) / cos(lat)).clamp(-1., 1.);
let coslng = mul_add(sin(self.lat), sin(-lat), cos(distance))
/ cos(self.lat)
/ cos(lat);
self.lng + atan2(sinlng, coslng)
};

// XXX: make sure longitudes are in the proper bounds.
Expand Down Expand Up @@ -401,11 +404,11 @@ impl From<LatLng> for Vec3d {
/// Computes the 3D coordinate on unit sphere from the latitude and
/// longitude.
fn from(value: LatLng) -> Self {
let r = value.lat.cos();
let r = cos(value.lat);

let z = value.lat.sin();
let x = value.lng.cos() * r;
let y = value.lng.sin() * r;
let z = sin(value.lat);
let x = cos(value.lng) * r;
let y = sin(value.lng) * r;

Self::new(x, y, z)
}
Expand Down
2 changes: 1 addition & 1 deletion src/coord/localij.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
index::bits,
BaseCell, CellIndex, Direction, Resolution, CCW, CW, DEFAULT_CELL_INDEX,
};
use std::{fmt, num::NonZeroU8};
use core::{fmt, num::NonZeroU8};

// -----------------------------------------------------------------------------

Expand Down

0 comments on commit 6dbf502

Please sign in to comment.