From f3cf18df416e6a556187701b4c574df8a2d356b9 Mon Sep 17 00:00:00 2001 From: ApsisRS contributors Date: Fri, 2 Oct 2026 01:26:58 +0800 Subject: [PATCH] docs: bilingual comments for access, coverage, isl and routing This completes the network layer. --- crates/network/apsis-access/src/batch.rs | 9 ++ .../network/apsis-coverage/src/footprint.rs | 48 ++++++++++ crates/network/apsis-coverage/src/geometry.rs | 25 +++++ .../network/apsis-coverage/src/visibility.rs | 96 +++++++++++++++++++ crates/network/apsis-isl/src/batch.rs | 2 + crates/network/apsis-isl/src/geometry.rs | 2 + crates/network/apsis-mesh/src/connectivity.rs | 9 ++ crates/network/apsis-routing/src/dijkstra.rs | 20 ++++ 8 files changed, 211 insertions(+) diff --git a/crates/network/apsis-access/src/batch.rs b/crates/network/apsis-access/src/batch.rs index bf04666..f4fc834 100644 --- a/crates/network/apsis-access/src/batch.rs +++ b/crates/network/apsis-access/src/batch.rs @@ -18,6 +18,10 @@ use apsis_math::parallel::map_index; /// /// The sequential form, provided for comparison and for small sets where /// starting a thread would cost more than the search. +/// +/// 按次序为许多轨迹搜索可见性。 +/// +/// 顺序形式,供比较以及线程启动代价高于搜索本身的小规模情形使用。 pub fn find_all( finder: &AccessFinder, tracks: &[Vec], @@ -30,6 +34,11 @@ pub fn find_all( /// The result matches [`find_all`] element for element and in the same order. /// A track that cannot be searched yields its error in the same position the /// sequential form would put it, so one bad track does not hide the rest. +/// +/// 跨多个线程为许多轨迹搜索可见性。 +/// +/// 结果与 [`find_all`] 逐元素、逐次序相同。无法搜索的轨迹在顺序形式会 +/// 放置它的同一位置给出其错误,故一条坏轨迹不会掩盖其余。 pub fn find_all_parallel( finder: &AccessFinder, tracks: &[Vec], diff --git a/crates/network/apsis-coverage/src/footprint.rs b/crates/network/apsis-coverage/src/footprint.rs index ed4363b..746293e 100644 --- a/crates/network/apsis-coverage/src/footprint.rs +++ b/crates/network/apsis-coverage/src/footprint.rs @@ -10,19 +10,32 @@ use core::f64::consts::PI; /// is visible above a given elevation mask. On a sphere it is an exact /// circle, described here by the central angle from the sub-satellite point /// and the radius of the body. +/// +/// 圆形地面覆盖区。 +/// +/// 覆盖区是能在地平遮蔽以上看见某卫星的那部分地表。在球面上它是精确 +/// 的圆,此处以自星下点的中心角和天体半径描述。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Footprint { /// The central half angle of the footprint, in radians. /// /// This is the angle at the centre of the Earth from the sub-satellite /// point to the edge of the footprint. + /// + /// 覆盖区的中心半角,单位为弧度。 + /// + /// 这是在地心处自星下点到覆盖区边缘的角度。 pub central_angle: f64, /// The radius of the body, in kilometers. + /// + /// 天体的半径,单位为千米。 pub body_radius: f64, } impl Footprint { /// Creates a footprint from its central angle and body radius. + /// + /// 由其中心角与天体半径创建覆盖区。 pub const fn new(central_angle: f64, body_radius: f64) -> Self { Self { central_angle, @@ -32,6 +45,8 @@ impl Footprint { /// Returns the footprint radius measured along the surface, in /// kilometers. + /// + /// 返回沿地表量得的覆盖区半径,单位为千米。 pub fn surface_radius(&self) -> f64 { self.central_angle * self.body_radius } @@ -45,6 +60,16 @@ impl Footprint { /// ``` /// /// which is exact for a spherical body. + /// + /// 返回覆盖区的面积,单位为平方千米。 + /// + /// 该面积是被圆锥截去的球面部分, + /// + /// ```text + /// A = 2 pi R^2 (1 - cos(central)) + /// ``` + /// + /// 对球形天体这是精确的。 pub fn area(&self) -> f64 { 2.0 * PI * self.body_radius * self.body_radius * (1.0 - self.central_angle.cos()) } @@ -53,6 +78,11 @@ impl Footprint { /// /// The value is between zero and one half, because a footprint from a /// point above the surface can never cover more than a hemisphere. + /// + /// 返回覆盖区占整个球面的比例。 + /// + /// 该值介于零与二分之一之间,因为来自地表之上某点的覆盖区覆盖范围 + /// 绝不会超过一个半球。 pub fn fraction_of_surface(&self) -> f64 { self.area() / (4.0 * PI * self.body_radius * self.body_radius) } @@ -62,6 +92,10 @@ impl Footprint { /// /// The comparison is inclusive, so a point exactly on the edge is /// inside. + /// + /// 当距星下点给定中心角的地面点位于覆盖区内时返回 `true`。 + /// + /// 比较是含端点的,故恰在边缘上的点算在内部。 pub fn contains_central_angle(&self, central_angle: f64) -> bool { central_angle.is_finite() && central_angle >= 0.0 && central_angle <= self.central_angle } @@ -136,6 +170,13 @@ pub fn footprint_fraction( /// /// Returns `None` when either vector has zero length or a non-finite /// component. +/// +/// 返回两个地表点之间的角度,单位为弧度。 +/// +/// 两矢量必须是相对天体中心、在同一坐标系中的位置。该角度是自中心看 +/// 它们之间的中心角。 +/// +/// 任一矢量为零长或含非有限分量时返回 `None`。 pub fn central_angle_between(first: Vec3, second: Vec3) -> Option { if !first.is_finite() || !second.is_finite() { return None; @@ -159,6 +200,13 @@ pub fn central_angle_between(first: Vec3, second: Vec3) -> Option { /// and the central half angle. /// /// Returns `None` when either vector is degenerate or not finite. +/// +/// 当某地面点位于某覆盖区内时返回 `true`。 +/// +/// 该点与星下点都是相对天体中心的位置,单位为千米。覆盖区提供天体半径 +/// 与中心半角。 +/// +/// 任一矢量退化或非有限时返回 `None`。 pub fn point_in_footprint( footprint: &Footprint, point: Vec3, diff --git a/crates/network/apsis-coverage/src/geometry.rs b/crates/network/apsis-coverage/src/geometry.rs index 207c356..21ca796 100644 --- a/crates/network/apsis-coverage/src/geometry.rs +++ b/crates/network/apsis-coverage/src/geometry.rs @@ -37,6 +37,13 @@ use core::f64::consts::FRAC_PI_2; /// /// Returns `None` when the radii are not usable, when the body radius /// exceeds the satellite radius, or when the elevation is not finite. +/// +/// 返回卫星处的天底角,单位为弧度。 +/// +/// 天底角在卫星处量取,介于到地心的方向与到地面点的方向之间。当地面点 +/// 恰在卫星正下方时它为零,并随该点移向地平而增大。 +/// +/// 半径不可用、天体半径超过卫星半径,或仰角非有限时返回 `None`。 pub fn nadir_angle(satellite_radius: f64, body_radius: f64, elevation: f64) -> Option { if !satellite_radius.is_finite() || !body_radius.is_finite() || !elevation.is_finite() { return None; @@ -46,6 +53,7 @@ pub fn nadir_angle(satellite_radius: f64, body_radius: f64, elevation: f64) -> O } let sine = body_radius * elevation.cos() / satellite_radius; // Rounding can push the sine a little above one for a grazing geometry. + // 对掠射几何,舍入可能把正弦略微推到一以上。 Some(sine.clamp(-1.0, 1.0).asin()) } @@ -64,6 +72,8 @@ pub fn earth_central_angle(satellite_radius: f64, body_radius: f64, elevation: f // Rounding can leave a small negative value at the zenith, where the // true angle is zero. The negative part is not physical, so it is // removed before the result is returned. + // 在天顶处(真实角为零)舍入可能留下小的负值。该负值没有物理意义, + // 故在返回结果前将其去掉。 Some((FRAC_PI_2 - elevation - nadir).max(0.0)) } @@ -118,6 +128,20 @@ pub fn slant_range(satellite_radius: f64, body_radius: f64, elevation: f64) -> O /// /// Returns `None` when the radii are not usable or when the central angle is /// not finite. +/// +/// 返回某中心角对应的仰角,单位为弧度。 +/// +/// 它反解 [`earth_central_angle`]。当已知地面点相对星下点的位置而要求 +/// 该点处仰角时使用它。 +/// +/// 关系为 +/// +/// ```text +/// tan(elevation) = (cos(central) - body_radius / satellite_radius) +/// / sin(central) +/// ``` +/// +/// 半径不可用或中心角非有限时返回 `None`。 pub fn elevation_from_central_angle( satellite_radius: f64, body_radius: f64, @@ -133,6 +157,7 @@ pub fn elevation_from_central_angle( let sine = central_angle.sin(); if sine == 0.0 { // Directly below the satellite the elevation is a right angle. + // 在卫星正下方仰角为直角。 return Some(FRAC_PI_2); } Some(((central_angle.cos() - ratio) / sine).atan()) diff --git a/crates/network/apsis-coverage/src/visibility.rs b/crates/network/apsis-coverage/src/visibility.rs index 44fd11b..632b181 100644 --- a/crates/network/apsis-coverage/src/visibility.rs +++ b/crates/network/apsis-coverage/src/visibility.rs @@ -6,22 +6,39 @@ use apsis_math::Vec3; use core::f64::consts::FRAC_PI_2; /// A ground station, given as a geodetic position on an ellipsoid. +/// +/// 一个地面站,以椭球上的大地位置给出。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct GroundPoint { /// The geodetic position of the station. + /// + /// 该站的大地位置。 pub position: Geodetic, /// The ellipsoid the position is referred to. + /// + /// 该位置所参照的椭球。 + /// + /// 该位置所参照的椭球。 + /// + /// 该位置所参照的椭球。 pub ellipsoid: Ellipsoid, /// The sensor elevation mask, in radians. /// /// The minimum elevation above the local horizon at which the station /// can see a satellite. A mask of zero means the station sees down to /// its own horizon. + /// + /// 传感器仰角遮蔽,单位为弧度。 + /// + /// 该站能看到卫星所需高出当地地平的最小仰角。遮蔽为零意味着该站 + /// 能看到直至自身地平。 pub elevation_mask: f64, } impl GroundPoint { /// Creates a ground point on the WGS 84 ellipsoid with a given mask. + /// + /// 在 WGS 84 椭球上以给定遮蔽创建地面点。 pub fn new(position: Geodetic, elevation_mask: f64) -> Self { Self { position, @@ -35,6 +52,11 @@ impl GroundPoint { /// /// The geodetic conversion yields meters, so the result is divided by a /// thousand to match the frame the orbit layer uses. + /// + /// 返回该站在地固直角坐标系中的位置,单位为千米。 + /// + /// 大地转换得到的是米,故结果除以一千,以与 orbit 层所用的坐标系 + /// 一致。 pub fn ecef(&self) -> Vec3 { self.position.to_cartesian(self.ellipsoid) / 1_000.0 } @@ -43,12 +65,18 @@ impl GroundPoint { /// /// This is the local radius of the ellipsoid at the station's latitude /// plus its height, so it includes the effect of the flattening. + /// + /// 返回距地心的距离,单位为千米。 + /// + /// 这是该站纬度处椭球的当地半径加上其高度,故包含了扁率的影响。 pub fn radius(&self) -> f64 { self.ecef().norm() } /// Returns `true` when the station and its mask describe a usable /// sensor. + /// + /// 当该站及其遮蔽描述一个可用传感器时返回 `true`。 pub fn is_valid(&self) -> bool { self.position.is_valid() && self.elevation_mask.is_finite() @@ -63,17 +91,28 @@ impl GroundPoint { /// because a station that can point anywhere with sufficient gain is /// modelled by its elevation mask alone; the direction matters only when the /// station has a fixed beam. +/// +/// 位于地面点、具有指向方向的传感器。 +/// +/// 方向是地固系中的单位矢量。它是可选的,因为能以足够增益指向任何 +/// 方向的站仅以其仰角遮蔽建模即可;只有当站具有固定波束时方向才要紧。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Sensor { /// The station. + /// + /// 该地面站。 pub ground: GroundPoint, /// The pointing direction in the Earth-fixed frame, if the station is /// directional. + /// + /// 地固系中的指向方向,若该站是定向的。 pub pointing: Option, } impl Sensor { /// Creates a station that can point anywhere above its mask. + /// + /// 创建能在其遮蔽之上指向任何方向的站。 pub const fn omnidirectional(ground: GroundPoint) -> Self { Self { ground, @@ -85,6 +124,10 @@ impl Sensor { /// /// The direction is normalized when it is used, so the caller may pass /// an unnormalized vector. + /// + /// 创建具有固定指向方向的站。 + /// + /// 该方向在使用时被归一化,故调用方可以传入未归一化的矢量。 pub const fn directional(ground: GroundPoint, pointing: Vec3) -> Self { Self { ground, @@ -94,11 +137,17 @@ impl Sensor { } /// The point on the Earth's surface directly below a satellite. +/// +/// 卫星正下方的地表点。 #[derive(Debug, Clone, Copy, PartialEq)] pub struct SubSatellitePoint { /// The geodetic position of the sub-satellite point. + /// + /// 星下点的大地位置。 pub position: Geodetic, /// The ellipsoid the position is referred to. + /// + /// 该位置所参照的椭球。 pub ellipsoid: Ellipsoid, } @@ -116,6 +165,16 @@ pub struct SubSatellitePoint { /// /// Returns `None` when the position is not finite or lies on the polar axis, /// where no longitude is defined. +/// +/// 返回地固系中某位置的星下点。 +/// +/// 卫星位置单位为千米。星下点是从地心过卫星的直线与椭球的交点,即把 +/// 卫星高度置零而纬经度不变所得的大地位置。 +/// +/// 这是地心投影,而非过卫星的椭球法线与地表相交的点。对低轨卫星两者 +/// 相差至多约二十千米,而覆盖区几何用的是地心形式。 +/// +/// 位置非有限,或位于未定义经度的极轴上时返回 `None`。 pub fn sub_satellite_point( position_ecef_km: Vec3, ellipsoid: Ellipsoid, @@ -142,6 +201,15 @@ pub fn sub_satellite_point( /// below the horizon. /// /// Returns `None` when either position is not usable. +/// +/// 返回自某站看某卫星的仰角,单位为弧度。 +/// +/// 卫星位置在地固系中,单位为千米。仰角自当地地平量起,后者是垂直于该 +/// 站处椭球法线的平面。 +/// +/// 正值表示卫星在地平之上,直角值表示在天顶,负值表示在地平之下。 +/// +/// 任一位置不可用时返回 `None`。 pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option { if !satellite_ecef_km.is_finite() || !ground.is_valid() { return None; @@ -153,6 +221,8 @@ pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option< } // The local up direction is the ellipsoid normal at the station, which // for a geodetic position is the direction the surface normal points. + // 当地朝上方向是该站处的椭球法线,对大地位置而言即地表法线所指的 + // 方向。 let up = local_up(ground); let sine = line_of_sight.normalize()?.dot(up.normalize()?); Some(sine.clamp(-1.0, 1.0).asin()) @@ -164,6 +234,11 @@ pub fn elevation_angle(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option< /// geodetic position it follows from the latitude and longitude, because the /// normal of a biaxial ellipsoid lies in the meridian plane and makes the /// geodetic latitude with the equatorial plane. +/// +/// 返回沿某站处椭球法线的单位矢量。 +/// +/// 这是当地垂线,即铅垂线所指的方向。对大地位置而言,它由纬度与经度 +/// 得出,因为双轴椭球的法线位于子午面内,并与赤道面夹大地纬度。 pub fn local_up(ground: &GroundPoint) -> Vec3 { let latitude = ground.position.latitude; let longitude = ground.position.longitude; @@ -182,6 +257,12 @@ pub fn local_up(ground: &GroundPoint) -> Vec3 { /// other visibility functions. /// /// Returns `None` when either position is not usable. +/// +/// 返回自某站看某卫星的仰角,单位为弧度。 +/// +/// 这与 [`elevation_angle`] 相同,其命名是为与其余可见性函数对称。 +/// +/// 任一位置不可用时返回 `None`。 pub fn line_of_sight(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option { elevation_angle(satellite_ecef_km, ground) } @@ -194,6 +275,14 @@ pub fn line_of_sight(satellite_ecef_km: Vec3, ground: &GroundPoint) -> Option Option { let elevation = elevation_angle(satellite_ecef_km, &sensor.ground)?; if elevation < sensor.ground.elevation_mask { @@ -216,6 +305,13 @@ pub fn visible_from(satellite_ecef_km: Vec3, sensor: &Sensor) -> Option { /// position, without building a [`GroundPoint`]. /// /// Returns `None` when the satellite position or the station is not usable. +/// +/// 返回自某大地站看某卫星的仰角。 +/// +/// 这是为直接把站作为大地位置给出、而不构建 [`GroundPoint`] 的便利 +/// 函数。 +/// +/// 卫星位置或该站不可用时返回 `None`。 pub fn visible_from_geodetic( satellite_ecef_km: Vec3, position: Geodetic, diff --git a/crates/network/apsis-isl/src/batch.rs b/crates/network/apsis-isl/src/batch.rs index 859445d..b2cfc68 100644 --- a/crates/network/apsis-isl/src/batch.rs +++ b/crates/network/apsis-isl/src/batch.rs @@ -61,6 +61,8 @@ pub fn visibility_matrix_parallel(positions: &[Vec3], body_radius: f64) -> Vec Option { if length_squared == 0.0 { // The two points coincide, so the distance is the radius of that // point. + // 两点重合,故距离即该点的半径。 return Some(first.norm()); } // The parameter of the closest point on the infinite line, clamped to // the segment. + // 无限直线上最接近点的参数,被夹逼到该线段。 let parameter = (-first.dot(segment) / length_squared).clamp(0.0, 1.0); let closest = first + segment * parameter; Some(closest.norm()) diff --git a/crates/network/apsis-mesh/src/connectivity.rs b/crates/network/apsis-mesh/src/connectivity.rs index 59c45d7..e57b145 100644 --- a/crates/network/apsis-mesh/src/connectivity.rs +++ b/crates/network/apsis-mesh/src/connectivity.rs @@ -71,6 +71,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) } // A residual capacity for each edge, indexed by the edge position. + // 每条边一个残余容量,按边的位置索引。 let edges = topology.edges(); let mut residual: Vec = vec![1; edges.len()]; @@ -78,6 +79,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) loop { // Breadth first search for an augmenting path in the residual // graph. + // 在残余图中广度优先搜索一条增广路径。 let mut parent_edge: Vec> = vec![None; topology.node_count()]; let mut visited = vec![false; topology.node_count()]; visited[source] = true; @@ -90,6 +92,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) } for (index, edge) in edges.iter().enumerate() { // Forward edges with residual capacity. + // 具有残余容量的正向边。 if edge.from == node && residual[index] > 0 && !visited[edge.to] { visited[edge.to] = true; parent_edge[edge.to] = Some(index); @@ -98,6 +101,7 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) } // Reverse edges, which exist when flow has been pushed the // other way and which cancel it. + // 反向边,当流量已沿另一方向推送时存在,用以抵消它。 if edge.to == node && residual[index] == 0 && !visited[edge.from] { visited[edge.from] = true; parent_edge[edge.from] = Some(index); @@ -113,6 +117,8 @@ pub fn edge_connectivity(topology: &Topology, source: usize, destination: usize) // Walk the path back, adjusting the residual capacities. An edge // traversed in its own direction consumes one unit; an edge // traversed against its direction restores one. + // 沿路径回溯,调整残余容量。沿自身方向穿越的边消耗一个单位; + // 逆自身方向穿越的边恢复一个单位。 let mut node = destination; while node != source { let index = parent_edge[node]?; @@ -163,6 +169,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize { } // First pass: the finishing order over the forward edges. + // 第一遍:在正向边上求完成次序。 let mut visited = vec![false; node_count]; let mut order = Vec::with_capacity(node_count); for start in 0..node_count { @@ -171,6 +178,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize { } // An iterative depth first search, pushing a node again to mark the // point at which its children are done. + // 迭代式深度优先搜索,把某节点再次压栈以标记其子节点完成之处。 let mut stack = vec![(start, false)]; while let Some((node, processed)) = stack.pop() { if processed { @@ -192,6 +200,7 @@ pub fn strongly_connected_components(topology: &Topology) -> usize { // Second pass: the reverse edges, taking nodes in reverse finishing // order. + // 第二遍:在反向边上按完成次序的逆序取节点。 let mut assigned = vec![false; node_count]; let mut components = 0; for node in order.into_iter().rev() { diff --git a/crates/network/apsis-routing/src/dijkstra.rs b/crates/network/apsis-routing/src/dijkstra.rs index 106bff5..d5e5939 100644 --- a/crates/network/apsis-routing/src/dijkstra.rs +++ b/crates/network/apsis-routing/src/dijkstra.rs @@ -242,6 +242,23 @@ pub fn shortest_path( /// the one described in Fraire, J. A. et al., *Routing in Delay-Tolerant /// Networks Comprising Heterogeneous Populations of Deterministic Link /// Dynamics*, IEEE Transactions on Mobile Computing, 2020. +/// +/// 返回在拓扑序列某区间上有效的最短路由。 +/// +/// 该路由在贯穿整个区间都存在的链路上计算,故它在整个区间上保持有效。 +/// 该区间取自 `start_seconds` 处生效样本的起点,到 `end_seconds` 处生效 +/// 样本的起点,并对该跨度内每个样本的链路求交。 +/// +/// 所返回的路段记录该路由有效的区间,它以 `end_seconds` 之后的下一处 +/// 拓扑变化为界。 +/// +/// 任一节点越界或两者相同、区间次序不对,或没有路由能存活整个区间时 +/// 返回错误。 +/// +/// 出处:时变图上的存储转发路由模型见 Fraire, J. A. 等,*Routing in +/// Delay-Tolerant Networks Comprising Heterogeneous Populations of +/// Deterministic Link Dynamics*,IEEE Transactions on Mobile Computing, +/// 2020。 pub fn route_over_series( series: &TopologySeries, source: usize, @@ -284,6 +301,8 @@ pub fn route_over_series( // Intersect the links of every sample spanned by the interval, taking // the cost from the first sample that has each link. + // 对该区间所跨的每个样本的链路求交,代价取自拥有每条链路的第一 + // 个样本。 let mut intersection = Topology::new(series.node_count()); let reference = &series.samples()[first].topology; for edge in reference.edges() { @@ -302,6 +321,7 @@ pub fn route_over_series( let route = shortest_path(&intersection, source, destination)?; // The leg is valid until the topology changes after the interval ends. + // 该路段有效直到区间结束之后的拓扑变化。 let end_of_validity = series .interval_of(last) .map_or(end_seconds, |(_, next)| next);