33 lines
1.8 KiB
Python
33 lines
1.8 KiB
Python
"""Distance certificates for Q2; does not change Q1's angular polygon model."""
|
|
|
|
from math import cos, sin, radians, isfinite
|
|
from .localization import Measurement
|
|
|
|
|
|
def first_bearing_candidate_disk(measurement: Measurement,
|
|
max_range_m: float = 1500,
|
|
guaranteed_range_m: float = 1000) -> dict:
|
|
"""Enclose the entire truncated first-bearing sector, then erode its disk.
|
|
|
|
For half-angle <=45 degrees the sector MEC has center distance and radius
|
|
a=R/(2*cos(delta)). Intersecting with Omega only shrinks the source set.
|
|
A missing sufficient disk is NOT proof that Q is empty. The certificate
|
|
guarantees distance only: directional transmitters may still be invisible.
|
|
"""
|
|
if any(isinstance(v, bool) or not isinstance(v, (int, float)) or not isfinite(v)
|
|
or v <= 0 for v in (max_range_m, guaranteed_range_m)):
|
|
raise ValueError("接收半径必须为有限正数")
|
|
if measurement.half_angle_deg > 45:
|
|
raise ValueError("此窄角域证书仅支持半角不超过45度;题设为1度")
|
|
delta, theta = radians(measurement.half_angle_deg), radians(measurement.bearing_deg % 360)
|
|
a = max_range_m/(2*cos(delta))
|
|
center = [measurement.x+a*cos(theta), measurement.y+a*sin(theta)]
|
|
available = guaranteed_range_m-a
|
|
return {"source_model": "first_bearing_sector_with_range_upper_bound",
|
|
"sector_enclosing_circle": {"center": center, "radius_m": a},
|
|
"sufficient_candidate_disk": {"center": center, "radius_m": available}
|
|
if available >= 0 else None,
|
|
"candidate_nonempty_certified": available >= 0,
|
|
"distance_only": True,
|
|
"max_range_m": max_range_m, "guaranteed_range_m": guaranteed_range_m}
|