Skip to content

Commit

Permalink
Subsume novalabsxyz/itm-rs into workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
JayKickliter committed Nov 20, 2023
2 parents 42f057a + 8270250 commit a13b3cd
Show file tree
Hide file tree
Showing 16 changed files with 665 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:

- name: Setup | Checkout
uses: actions/checkout@v3
with:
submodules: true

- name: Setup | Rust toolchain
uses: dtolnay/[email protected]
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "extern/itm"]
path = extern/itm
url = https://github.com/dirkcgrunwald/itm.git
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"geopath",
"itm",
"nasadem",
"propah",
"terrain",
Expand All @@ -10,12 +11,14 @@ resolver = "2"
[workspace.dependencies]
approx = "0.5.1"
byteorder = "1.4.3"
clap = { version = "4.4.2", features = ["derive"] }
criterion = { version = "0.5", features = ["html_reports"] }
dashmap = "5.5.3"
geo = "0.26.0"
log = "0.4.20"
memmap2 = "0.7.1"
num-traits = "0.2.16"
serde = { version = "1", features = ["derive"] }
thiserror = "1.0.48"

# We want meaninful stack traces when profiling/debugging
Expand Down
1 change: 1 addition & 0 deletions extern/itm
Submodule itm added at 31d068
33 changes: 33 additions & 0 deletions itm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
categories = ["science"]
description = "A wrapper around NTIA's Irregular Terrain Model"
edition = "2021"
homepage = "https://github.com/jaykickliter/geoprof"
keywords = ["rf", "radio", "modeling", "ntia"]
license-file = "LICENSE.md"
name = "itm"
readme = "README.md"
repository = "https://github.com/jaykickliter/geoprof"
version = "0.1.0"

[features]
default = []
address_sanitizer = []
serde = ["serde/derive"]

[dependencies]
cxx = "1"
serde = { workspace = true, optional = true }
thiserror = { workspace = true }

[build-dependencies]
cxx-build = "1"

[dev-dependencies]
anyhow = "1"
clap = { workspace = true }
geo = { workspace = true }
terrain = { path = "../terrain" }

[[example]]
name = "p2p"
44 changes: 44 additions & 0 deletions itm/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
fn main() {
let cxx_sources = [
"../extern/itm/src/ComputeDeltaH.cpp",
"../extern/itm/src/DiffractionLoss.cpp",
"../extern/itm/src/FindHorizons.cpp",
"../extern/itm/src/FreeSpaceLoss.cpp",
"../extern/itm/src/FresnelIntegral.cpp",
"../extern/itm/src/H0Function.cpp",
"../extern/itm/src/InitializeArea.cpp",
"../extern/itm/src/InitializePointToPoint.cpp",
"../extern/itm/src/InverseComplementaryCumulativeDistributionFunction.cpp",
"../extern/itm/src/KnifeEdgeDiffraction.cpp",
"../extern/itm/src/LineOfSightLoss.cpp",
"../extern/itm/src/LinearLeastSquaresFit.cpp",
"../extern/itm/src/LongleyRice.cpp",
"../extern/itm/src/QuickPfl.cpp",
"../extern/itm/src/SigmaHFunction.cpp",
"../extern/itm/src/SmoothEarthDiffraction.cpp",
"../extern/itm/src/TerrainRoughness.cpp",
"../extern/itm/src/TroposcatterLoss.cpp",
"../extern/itm/src/ValidateInputs.cpp",
"../extern/itm/src/Variability.cpp",
"../extern/itm/src/itm_area.cpp",
"../extern/itm/src/itm_p2p.cpp",
"wrapper/itm-wrapper.cpp",
];

let mut bridge = cxx_build::bridge("src/lib.rs");
bridge.flag("-std=c++11");
bridge.include("../extern/itm/include");
#[cfg(feature = "address_sanitizer")]
{
bridge.flag("-fno-omit-frame-pointer");
bridge.flag("-fsanitize=address");
bridge.flag("-ggdb");
}
for path in &cxx_sources {
bridge.file(path);
}
bridge.compile("itm_wrapper");

println!("cargo:rerun-if-changed=wrapper/itm-wrapper.cpp");
println!("cargo:rerun-if-changed=wrapper/itm-wrapper.h");
}
78 changes: 78 additions & 0 deletions itm/examples/p2p/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
mod options;

use anyhow::Error as AnyErr;
use clap::Parser;
use itm::{Climate, ModeVariability, Polarization};
use options::{Cli, LatLonAlt};
use terrain::{Profile, TileMode, Tiles};

// Example for 900 MHz across black rock desert.
// ```
// cargo run --example p2p -- --tile-dir=/path/to/nasadem/3-arcsecond/hgt/tiles/ --start=40.885629,-119.065844,10 --frequency=900e6 --end=40.904691,-119.043429,10
// ```
fn main() -> Result<(), AnyErr> {
let Cli {
tile_dir,
max_step,
start: LatLonAlt(start_coord, start_alt),
end: LatLonAlt(end_coord, end_alt),
frequency,
} = Cli::parse();

let tiles = Tiles::new(tile_dir, TileMode::MemMap)?;
let t0 = std::time::Instant::now();
let profile = Profile::<f32>::builder()
.start(start_coord)
.start_alt(start_alt)
.max_step(max_step)
.end(end_coord)
.end_alt(end_alt)
.build(&tiles)?;
let profile_runtime = t0.elapsed();

let climate = Climate::Desert;
let n0 = 301.;
let f_hz = frequency;
let pol = Polarization::Vertical;
let epsilon = 15.;
let sigma = 0.005;
let mdvar = ModeVariability::Accidental;
let time = 50.0;
let location = 50.0;
let situation = 50.0;
let step_size_m = profile.distances_m[1];
let terrain = profile.terrain_elev_m;
let t0 = std::time::Instant::now();
let attenuation_db = itm::p2p(
start_alt.into(),
end_alt.into(),
step_size_m.into(),
&terrain,
climate,
n0,
f_hz.into(),
pol,
epsilon,
sigma,
mdvar,
time,
location,
situation,
)?;
let itm_p2p_runtime = t0.elapsed();

let total_distance_m = profile.distances_m.last().unwrap();
let fspl = fspl(*total_distance_m, frequency);

println!("profile runtime: {profile_runtime:?}");
println!("itm runtime: {itm_p2p_runtime:?}");
println!("distance: {total_distance_m} m");
println!("fspl: {fspl} dB");
println!("attenuation: {attenuation_db} dB");

Ok(())
}

fn fspl(meters: f32, freq: f32) -> f32 {
20.0 * meters.log10() + 20.0 * freq.log10() - 147.55
}
53 changes: 53 additions & 0 deletions itm/examples/p2p/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use anyhow::{anyhow, Error as AnyError};
use clap::Parser;
use geo::geometry::Coord;
use std::{path::PathBuf, str::FromStr};

/// Generate point-to-point terrain profiles.
#[derive(Parser, Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
/// Directory elevation tiles.
#[arg(short, long)]
pub tile_dir: PathBuf,

/// Maximum path incremental step size, in meters.
#[arg(short, long, default_value_t = 90.0)]
pub max_step: f32,

/// Start "lat,lon,alt", where 'alt' is meters above ground.
#[arg(long)]
pub start: LatLonAlt,

/// Destination "lat,lon,alt", where 'alt' is meters above ground.
#[arg(long)]
pub end: LatLonAlt,

/// Signal frequency (Hz).
#[arg(long, short)]
pub frequency: f32,
}

#[derive(Clone, Debug, Copy)]
pub struct LatLonAlt(pub Coord<f32>, pub f32);

impl FromStr for LatLonAlt {
type Err = AnyError;
fn from_str(s: &str) -> Result<Self, AnyError> {
let (lat_str, lon_str, alt_str) = {
let idx = s
.find(',')
.ok_or_else(|| anyhow!("not a valid lat,lon,alt"))?;
let (lat_str, lon_alt_str) = s.split_at(idx);
let idx = lon_alt_str[1..]
.find(',')
.ok_or_else(|| anyhow!("not a valid lat,lon,alt"))?;
let (lon_str, alt_str) = lon_alt_str[1..].split_at(idx);
(lat_str, lon_str, &alt_str[1..])
};
let lat = f32::from_str(lat_str)?;
let lon = f32::from_str(lon_str)?;
let alt = f32::from_str(alt_str)?;
Ok(Self(Coord { y: lat, x: lon }, alt))
}
}
54 changes: 54 additions & 0 deletions itm/src/enums.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// Antenna polarization.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Polarization {
Horizontal = 0,
Vertical = 1,
}

/// Siting criteria.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SittingCriteria {
Random = 0,
Careful = 1,
VeryCareful = 2,
}

/// Radio climate.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Climate {
Equatorial = 1,
ContinentalSubtropical = 2,
MaritimeSubtropical = 3,
Desert = 4,
ContinentalTemperate = 5,
MaritimeTemperateOverLand = 6,
MaritimeTemperateOverSea = 7,
}

/// Propagation mode.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
NotSet = 0,
LineOfSight = 1,
Diffraction = 2,
Troposcatter = 3,
}

/// Mode of variability.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModeVariability {
SingleMessage = 0,
Accidental = 1,
Mobile = 2,
Broadcast = 3,
}
81 changes: 81 additions & 0 deletions itm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::ffi::c_int;

#[derive(Debug, thiserror::Error)]
pub enum ItmErrCode {
#[error("TX terminal height is out of range")]
TxTerminalHeight,
#[error("RX terminal height is out of range")]
RxTerminalHeight,
#[error("Invalid value for radio climate")]
InvalidRadioClimate,
#[error("Time percentage is out of range")]
InvalidTime,
#[error("Location percentage is out of range")]
InvalidLocation,
#[error("Situation percentage is out of range")]
InvalidSituation,
#[error("Confidence percentage is out of range")]
InvalidConfidence,
#[error("Reliability percentage is out of range")]
InvalidReliability,
#[error("Refractivity is out of range")]
Refractivity,
#[error("Frequency is out of range")]
Frequency,
#[error("Invalid value for polarization")]
Polarization,
#[error("Epsilon is out of range")]
Epsilon,
#[error("Sigma is out of range")]
Sigma,
#[error("The imaginary portion of the complex impedance is larger than the real portion")]
GroundImpedance,
#[error("Invalid value for mode of variability")]
Mdvar,
#[error("Internally computed effective earth radius is invalid")]
EffectiveEarth,
#[error("Path distance is out of range")]
PathDistance,
#[error("Delta H (terrain irregularity parameter) is out of range")]
DeltaH,
#[error("Invalid value for TX siting criteria")]
TxSitingCriteria,
#[error("Invalid value for RX siting criteria")]
RxSitingCriteria,
#[error("Internally computed surface refractivity value is too small")]
SurfaceRefractivitySmall,
#[error("Internally computed surface refractivity value is too large")]
SurfaceRefractivityLarge,
}

impl ItmErrCode {
pub fn from_retcode<T>(err_code: c_int, val: T) -> Result<T, ItmErrCode> {
let err = match err_code {
0 | 1 => return Ok(val),
1000 => ItmErrCode::TxTerminalHeight,
1001 => ItmErrCode::RxTerminalHeight,
1002 => ItmErrCode::InvalidRadioClimate,
1003 => ItmErrCode::InvalidTime,
1004 => ItmErrCode::InvalidLocation,
1005 => ItmErrCode::InvalidSituation,
1006 => ItmErrCode::InvalidConfidence,
1007 => ItmErrCode::InvalidReliability,
1008 => ItmErrCode::Refractivity,
1009 => ItmErrCode::Frequency,
1010 => ItmErrCode::Polarization,
1011 => ItmErrCode::Epsilon,
1012 => ItmErrCode::Sigma,
1013 => ItmErrCode::GroundImpedance,
1014 => ItmErrCode::Mdvar,
1016 => ItmErrCode::EffectiveEarth,
1017 => ItmErrCode::PathDistance,
1018 => ItmErrCode::DeltaH,
1019 => ItmErrCode::TxSitingCriteria,
1020 => ItmErrCode::RxSitingCriteria,
1021 => ItmErrCode::SurfaceRefractivitySmall,
1022 => ItmErrCode::SurfaceRefractivityLarge,
_ => unreachable!(),
};
Err(err)
}
}
Loading

0 comments on commit a13b3cd

Please sign in to comment.