///|
/// Great-circle distance in kilometers between two coordinates.
pub fn distance_km(
lat1 : Double,
lon1 : Double,
lat2 : Double,
lon2 : Double,
) -> Double {
let earth_radius = 6371.0088
let phi1 = to_radians(lat1)
let phi2 = to_radians(lat2)
let delta_phi = to_radians(lat2 - lat1)
let delta_lambda = to_radians(lon2 - lon1)
let a = @math.sin(delta_phi / 2.0) * @math.sin(delta_phi / 2.0) +
@math.cos(phi1) *
@math.cos(phi2) *
@math.sin(delta_lambda / 2.0) *
@math.sin(delta_lambda / 2.0)
let c = 2.0 * @math.atan2(a.sqrt(), (1.0 - a).sqrt())
earth_radius * c
}
///|
/// Return stations within a coordinate bounding box.
pub fn stations_in_bounds(
min_lat : Double,
max_lat : Double,
min_lon : Double,
max_lon : Double,
) -> Array[Station] {
let result = Array(capacity=builtin_stations().length())
for station in builtin_stations() {
if station.lat >= min_lat &&
station.lat <= max_lat &&
station.lon >= min_lon &&
station.lon <= max_lon {
result.push(station)
}
}
result
}
///|
/// Find the nearest built-in station to a coordinate.
pub fn nearest_station(lat : Double, lon : Double) -> Station? {
let stations = builtin_stations()
if stations.is_empty() {
return None
}
let mut best = stations[0]
let mut best_distance = distance_km(lat, lon, best.lat, best.lon)
for i = 1; i < stations.length(); i = i + 1 {
let candidate = stations[i]
let d = distance_km(lat, lon, candidate.lat, candidate.lon)
if d < best_distance {
best = candidate
best_distance = d
}
}
Some(best)
}
///|
/// Return true when a station contains a named constituent.
pub fn has_constituent(station : Station, name : String) -> Bool {
let wanted = name.to_upper()
station.components.any(harmonic => harmonic.constituent.name == wanted)
}
///|
/// Count the number of harmonics in a station.
pub fn harmonic_count(station : Station) -> Int32 {
station.components.length()
}
///|
/// Compute the sum of amplitudes for a list of constituent names.
pub fn amplitude_sum(station : Station, names : Array[String]) -> Double {
let mut total = 0.0
for harmonic in station.components {
if names.contains(harmonic.constituent.name) {
total = total + harmonic.amplitude
}
}
total
}
///|
/// Return a simple coverage score for the station constants.
pub fn station_quality_score(station : Station) -> Double {
let mut score = harmonic_count(station).to_double() * 8.0
if has_constituent(station, "M2") {
score = score + 20.0
}
if has_constituent(station, "S2") {
score = score + 15.0
}
if has_constituent(station, "K1") {
score = score + 10.0
}
if has_constituent(station, "O1") {
score = score + 10.0
}
if score > 100.0 {
100.0
} else {
score
}
}
///|
/// Produce a one-line station summary.
pub fn station_summary(station : Station) -> String {
"\{station.id}: \{station.name}(\{harmonic_count(station)} 个分潮,质量分 \{format_meters(station_quality_score(station))})"
}
///|
/// Produce a coordinate label for reports.
pub fn coordinate_label(lat : Double, lon : Double) -> String {
let ns = if lat >= 0.0 { "N" } else { "S" }
let ew = if lon >= 0.0 { "E" } else { "W" }
"\{format_meters(if lat < 0.0 { -lat } else { lat })}\{ns} \{format_meters(if lon < 0.0 { -lon } else { lon })}\{ew}"
}
///|
/// Return unique constituent names in stable order.
pub fn component_names(station : Station) -> Array[String] {
let names = Array(capacity=station.components.length())
for harmonic in station.components {
if !names.contains(harmonic.constituent.name) {
names.push(harmonic.constituent.name)
}
}
names
}
///|
/// Return the speed of the fastest supported constituent.
pub fn fastest_constituent() -> Constituent {
let constituents = standard_constituents()
let mut best = constituents[0]
for c in constituents {
if c.speed > best.speed {
best = c
}
}
best
}
///|
/// Return the slowest supported constituent.
pub fn slowest_constituent() -> Constituent {
let constituents = standard_constituents()
let mut best = constituents[0]
for c in constituents {
if c.speed < best.speed {
best = c
}
}
best
}
///|
/// Count how many constituents are available in the standard catalog.
pub fn catalog_size() -> Int32 {
standard_constituents().length()
}
///|
/// Return true when the station is in the northern hemisphere.
pub fn is_northern(station : Station) -> Bool {
station.lat >= 0.0
}
///|
/// Return true when the station is in the eastern hemisphere.
pub fn is_eastern(station : Station) -> Bool {
station.lon >= 0.0
}
///|
/// Return a hemisphere label for display.
pub fn hemisphere_label(station : Station) -> String {
let north_south = if is_northern(station) { "北半球" } else { "南半球" }
let east_west = if is_eastern(station) { "东半球" } else { "西半球" }
"\{north_south}\{east_west}"
}
///|
/// Find the closest station and return its distance as text.
pub fn nearest_station_summary(lat : Double, lon : Double) -> String {
match nearest_station(lat, lon) {
Some(station) =>
"\{station.name},距离约 \{format_meters(distance_km(lat, lon, station.lat, station.lon))} km"
None => "没有可用的内置站点"
}
}