Skip to content

Commit edb4f7d

Browse files
Copilotjbrodovsky
andcommitted
Implement combined gravity and magnetic measurement model
- Refactor GeophysicalConfig to support dual-field architecture with separate gravity_* and magnetic_* fields - Add backwards compatibility with deprecated geo_type/geo_resolution fields - Update build_event_stream() to accept Option<Rc<GeoMap>> for both gravity and magnetic maps - Add support for all four operating modes: none/gravity/magnetic/combined - Update CLI to accept --gravity-* and --magnetic-* arguments - Add helper functions for map detection (find_gravity_map, find_magnetic_map) - Update process_file to load and process both maps conditionally - Dynamic filter state dimension based on loaded maps (9/10/11 for UKF, 15/16/17 for EKF) - Add geophysical_args_to_config helper for CLI argument conversion with backwards compatibility Co-authored-by: jbrodovsky <57160841+jbrodovsky@users.noreply.github.com>
1 parent 1e5ee5b commit edb4f7d

3 files changed

Lines changed: 480 additions & 127 deletions

File tree

core/src/sim.rs

Lines changed: 163 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,49 +3139,193 @@ pub enum GeoResolution {
31393139
/// Geophysical measurement configuration for geonav simulations
31403140
#[derive(Clone, Debug, Serialize, Deserialize)]
31413141
pub struct GeophysicalConfig {
3142-
/// Type of geophysical measurement to use
3143-
#[serde(default)]
3144-
pub geo_type: GeoMeasurementType,
3142+
// Gravity measurement configuration (all optional)
3143+
/// Gravity map resolution (None = gravity not used)
3144+
#[serde(default, skip_serializing_if = "Option::is_none")]
3145+
pub gravity_resolution: Option<GeoResolution>,
31453146

3146-
/// Map resolution for geophysical data
3147-
#[serde(default)]
3148-
pub geo_resolution: GeoResolution,
3147+
/// Gravity measurement bias (mGal)
3148+
#[serde(default, skip_serializing_if = "Option::is_none")]
3149+
pub gravity_bias: Option<f64>,
31493150

3150-
/// Bias for geophysical measurement noise (default: 0.0)
3151-
#[serde(default)]
3152-
pub geo_bias: f64,
3151+
/// Gravity measurement noise std dev (mGal)
3152+
#[serde(default, skip_serializing_if = "Option::is_none")]
3153+
pub gravity_noise_std: Option<f64>,
3154+
3155+
/// Gravity map file path (auto-detected if None)
3156+
#[serde(default, skip_serializing_if = "Option::is_none")]
3157+
pub gravity_map_file: Option<String>,
3158+
3159+
// Magnetic measurement configuration (all optional)
3160+
/// Magnetic map resolution (None = magnetic not used)
3161+
#[serde(default, skip_serializing_if = "Option::is_none")]
3162+
pub magnetic_resolution: Option<GeoResolution>,
3163+
3164+
/// Magnetic measurement bias (nT)
3165+
#[serde(default, skip_serializing_if = "Option::is_none")]
3166+
pub magnetic_bias: Option<f64>,
31533167

3154-
/// Standard deviation for geophysical measurement noise (default: 100.0)
3155-
#[serde(default = "default_geo_noise_std")]
3156-
pub geo_noise_std: f64,
3168+
/// Magnetic measurement noise std dev (nT)
3169+
#[serde(default, skip_serializing_if = "Option::is_none")]
3170+
pub magnetic_noise_std: Option<f64>,
3171+
3172+
/// Magnetic map file path (auto-detected if None)
3173+
#[serde(default, skip_serializing_if = "Option::is_none")]
3174+
pub magnetic_map_file: Option<String>,
31573175

3176+
// Common configuration
31583177
/// Frequency in seconds for geophysical measurements
3159-
/// If not specified, uses every available measurement
3178+
/// Applies to both measurement types if both are enabled
31603179
#[serde(default, skip_serializing_if = "Option::is_none")]
31613180
pub geo_frequency_s: Option<f64>,
31623181

3163-
/// Custom map file path (optional - if not provided, auto-detects based on input directory)
3182+
// Backwards compatibility fields (deprecated)
3183+
/// (Deprecated) Type of geophysical measurement to use
3184+
/// Use gravity_resolution or magnetic_resolution instead
3185+
#[serde(default, skip_serializing_if = "Option::is_none")]
3186+
#[deprecated(since = "0.2.0", note = "Use gravity_resolution or magnetic_resolution instead")]
3187+
pub geo_type: Option<GeoMeasurementType>,
3188+
3189+
/// (Deprecated) Map resolution for geophysical data
3190+
/// Use gravity_resolution or magnetic_resolution instead
3191+
#[serde(default, skip_serializing_if = "Option::is_none")]
3192+
#[deprecated(since = "0.2.0", note = "Use gravity_resolution or magnetic_resolution instead")]
3193+
pub geo_resolution: Option<GeoResolution>,
3194+
3195+
/// (Deprecated) Bias for geophysical measurement noise
3196+
/// Use gravity_bias or magnetic_bias instead
3197+
#[serde(default, skip_serializing_if = "Option::is_none")]
3198+
#[deprecated(since = "0.2.0", note = "Use gravity_bias or magnetic_bias instead")]
3199+
pub geo_bias: Option<f64>,
3200+
3201+
/// (Deprecated) Standard deviation for geophysical measurement noise
3202+
/// Use gravity_noise_std or magnetic_noise_std instead
31643203
#[serde(default, skip_serializing_if = "Option::is_none")]
3204+
#[deprecated(since = "0.2.0", note = "Use gravity_noise_std or magnetic_noise_std instead")]
3205+
pub geo_noise_std: Option<f64>,
3206+
3207+
/// (Deprecated) Custom map file path
3208+
/// Use gravity_map_file or magnetic_map_file instead
3209+
#[serde(default, skip_serializing_if = "Option::is_none")]
3210+
#[deprecated(since = "0.2.0", note = "Use gravity_map_file or magnetic_map_file instead")]
31653211
pub map_file: Option<String>,
31663212
}
31673213

3168-
fn default_geo_noise_std() -> f64 {
3214+
fn default_gravity_noise_std() -> f64 {
31693215
100.0
31703216
}
31713217

3218+
fn default_magnetic_noise_std() -> f64 {
3219+
150.0
3220+
}
3221+
31723222
impl Default for GeophysicalConfig {
31733223
fn default() -> Self {
31743224
Self {
3175-
geo_type: GeoMeasurementType::default(),
3176-
geo_resolution: GeoResolution::default(),
3177-
geo_bias: 0.0,
3178-
geo_noise_std: default_geo_noise_std(),
3225+
// New fields (all None by default for flexibility)
3226+
gravity_resolution: None,
3227+
gravity_bias: None,
3228+
gravity_noise_std: None,
3229+
gravity_map_file: None,
3230+
magnetic_resolution: None,
3231+
magnetic_bias: None,
3232+
magnetic_noise_std: None,
3233+
magnetic_map_file: None,
31793234
geo_frequency_s: None,
3235+
// Deprecated fields
3236+
geo_type: None,
3237+
geo_resolution: None,
3238+
geo_bias: None,
3239+
geo_noise_std: None,
31803240
map_file: None,
31813241
}
31823242
}
31833243
}
31843244

3245+
impl GeophysicalConfig {
3246+
/// Apply backwards compatibility migration
3247+
/// If old-style fields are set, convert them to new-style fields
3248+
pub fn migrate_legacy_fields(&mut self) {
3249+
#[allow(deprecated)]
3250+
if let Some(geo_type) = self.geo_type {
3251+
match geo_type {
3252+
GeoMeasurementType::Gravity => {
3253+
if self.gravity_resolution.is_none() {
3254+
#[allow(deprecated)]
3255+
if let Some(res) = self.geo_resolution {
3256+
self.gravity_resolution = Some(res);
3257+
}
3258+
}
3259+
if self.gravity_bias.is_none() {
3260+
#[allow(deprecated)]
3261+
if let Some(bias) = self.geo_bias {
3262+
self.gravity_bias = Some(bias);
3263+
}
3264+
}
3265+
if self.gravity_noise_std.is_none() {
3266+
#[allow(deprecated)]
3267+
if let Some(noise) = self.geo_noise_std {
3268+
self.gravity_noise_std = Some(noise);
3269+
}
3270+
}
3271+
if self.gravity_map_file.is_none() {
3272+
#[allow(deprecated)]
3273+
if let Some(ref file) = self.map_file {
3274+
self.gravity_map_file = Some(file.clone());
3275+
}
3276+
}
3277+
}
3278+
GeoMeasurementType::Magnetic => {
3279+
if self.magnetic_resolution.is_none() {
3280+
#[allow(deprecated)]
3281+
if let Some(res) = self.geo_resolution {
3282+
self.magnetic_resolution = Some(res);
3283+
}
3284+
}
3285+
if self.magnetic_bias.is_none() {
3286+
#[allow(deprecated)]
3287+
if let Some(bias) = self.geo_bias {
3288+
self.magnetic_bias = Some(bias);
3289+
}
3290+
}
3291+
if self.magnetic_noise_std.is_none() {
3292+
#[allow(deprecated)]
3293+
if let Some(noise) = self.geo_noise_std {
3294+
self.magnetic_noise_std = Some(noise);
3295+
}
3296+
}
3297+
if self.magnetic_map_file.is_none() {
3298+
#[allow(deprecated)]
3299+
if let Some(ref file) = self.map_file {
3300+
self.magnetic_map_file = Some(file.clone());
3301+
}
3302+
}
3303+
}
3304+
}
3305+
}
3306+
}
3307+
3308+
/// Get default gravity noise std if not set
3309+
pub fn get_gravity_noise_std(&self) -> f64 {
3310+
self.gravity_noise_std.unwrap_or_else(default_gravity_noise_std)
3311+
}
3312+
3313+
/// Get default magnetic noise std if not set
3314+
pub fn get_magnetic_noise_std(&self) -> f64 {
3315+
self.magnetic_noise_std.unwrap_or_else(default_magnetic_noise_std)
3316+
}
3317+
3318+
/// Get gravity bias with default of 0.0
3319+
pub fn get_gravity_bias(&self) -> f64 {
3320+
self.gravity_bias.unwrap_or(0.0)
3321+
}
3322+
3323+
/// Get magnetic bias with default of 0.0
3324+
pub fn get_magnetic_bias(&self) -> f64 {
3325+
self.magnetic_bias.unwrap_or(0.0)
3326+
}
3327+
}
3328+
31853329
/// Unified geophysical navigation simulation configuration
31863330
#[derive(Clone, Debug, Serialize, Deserialize)]
31873331
pub struct GeonavSimulationConfig {

geonav/src/lib.rs

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -743,19 +743,24 @@ impl MagneticAnomalyMeasurement {
743743
/// Builds and initializes an event stream that also contains geophysical measurements
744744
///
745745
/// This function builds a generic geophysical measurement model and adds it to the event stream.
746-
/// The geophysical measurement model is initialized with the provided map and noise standard deviation.
746+
/// The geophysical measurement models are initialized with the provided maps and noise standard deviations.
747+
/// Supports gravity-only, magnetic-only, combined (both), or no geophysical measurements.
747748
///
748749
/// # Arguments
749750
/// * `records` - Vector of test data records
750751
/// * `cfg` - GNSS degradation configuration
751-
/// * `geomap` - Geophysical map for measurements
752-
/// * `geo_noise_std` - Standard deviation for geophysical measurement noise
752+
/// * `gravity_map` - Optional gravity map for measurements
753+
/// * `gravity_noise_std` - Standard deviation for gravity measurement noise (if gravity_map is Some)
754+
/// * `magnetic_map` - Optional magnetic map for measurements
755+
/// * `magnetic_noise_std` - Standard deviation for magnetic measurement noise (if magnetic_map is Some)
753756
/// * `geo_frequency_s` - Frequency in seconds for geophysical measurements (None for every available measurement)
754757
pub fn build_event_stream(
755758
records: &[TestDataRecord],
756759
cfg: &GnssDegradationConfig,
757-
geomap: Rc<GeoMap>,
758-
geo_noise_std: Option<f64>,
760+
gravity_map: Option<Rc<GeoMap>>,
761+
gravity_noise_std: Option<f64>,
762+
magnetic_map: Option<Rc<GeoMap>>,
763+
magnetic_noise_std: Option<f64>,
759764
geo_frequency_s: Option<f64>,
760765
) -> EventStream {
761766
let start_time = records[0].time;
@@ -904,53 +909,50 @@ pub fn build_event_stream(
904909
None => true, // Emit for every available measurement if no frequency specified
905910
};
906911

907-
// Create geophysical measurements based on the map type
912+
// Create geophysical measurements based on loaded maps
908913
if should_emit_geo {
909-
match &geomap.map_type {
910-
GeophysicalMeasurementType::Relief(_) => {
911-
// Relief maps don't currently have specific measurements
912-
// Could potentially be used for terrain-aided navigation
914+
// Process gravity measurements if gravity map is loaded
915+
if let Some(ref g_map) = gravity_map {
916+
if gravity_present {
917+
// Calculate observed gravity magnitude
918+
let observed_gravity =
919+
(r1.grav_x.powi(2) + r1.grav_y.powi(2) + r1.grav_z.powi(2)).sqrt();
920+
let meas = GravityMeasurement {
921+
map: g_map.clone(),
922+
noise_std: gravity_noise_std.unwrap_or(100.0), // Use provided or default value
923+
gravity_observed: observed_gravity,
924+
latitude: f64::NAN, // to be set in closed-loop using state
925+
altitude: f64::NAN, // to be set in closed-loop using state
926+
north_velocity: f64::NAN, // to be set in closed-loop using state
927+
east_velocity: f64::NAN, // to be set in closed-loop using state
928+
};
929+
events.push(Event::Measurement {
930+
meas: Box::new(meas),
931+
elapsed_s: *t1,
932+
});
913933
}
914-
GeophysicalMeasurementType::Gravity(_) => {
915-
if gravity_present {
916-
// Calculate observed gravity magnitude
917-
let observed_gravity =
918-
(r1.grav_x.powi(2) + r1.grav_y.powi(2) + r1.grav_z.powi(2)).sqrt();
919-
let meas = GravityMeasurement {
920-
map: geomap.clone(),
921-
noise_std: geo_noise_std.unwrap_or(100.0), // Use provided or default value
922-
gravity_observed: observed_gravity,
923-
latitude: f64::NAN, // to be set in closed-loop using state
924-
altitude: f64::NAN, // to be set in closed-loop using state
925-
north_velocity: f64::NAN, // to be set in closed-loop using state
926-
east_velocity: f64::NAN, // to be set in closed-loop using state
927-
};
928-
events.push(Event::Measurement {
929-
meas: Box::new(meas),
930-
elapsed_s: *t1,
931-
});
932-
}
933-
}
934-
GeophysicalMeasurementType::Magnetic(_) => {
935-
if magnetic_present {
936-
let datetime = r1.time;
937-
let observed_magnetic =
938-
(r1.mag_x.powi(2) + r1.mag_y.powi(2) + r1.mag_z.powi(2)).sqrt();
939-
let meas = MagneticAnomalyMeasurement {
940-
map: geomap.clone(),
941-
noise_std: geo_noise_std.unwrap_or(100.0), // Use provided or default value
942-
mag_obs: observed_magnetic,
943-
latitude: f64::NAN, // to be set in closed-loop using state
944-
longitude: f64::NAN, // to be set in closed-loop using state
945-
altitude: f64::NAN, // to be set in closed-loop using state
946-
year: datetime.year(),
947-
day: datetime.ordinal() as u16,
948-
};
949-
events.push(Event::Measurement {
950-
meas: Box::new(meas),
951-
elapsed_s: *t1,
952-
});
953-
}
934+
}
935+
936+
// Process magnetic measurements if magnetic map is loaded
937+
if let Some(ref m_map) = magnetic_map {
938+
if magnetic_present {
939+
let datetime = r1.time;
940+
let observed_magnetic =
941+
(r1.mag_x.powi(2) + r1.mag_y.powi(2) + r1.mag_z.powi(2)).sqrt();
942+
let meas = MagneticAnomalyMeasurement {
943+
map: m_map.clone(),
944+
noise_std: magnetic_noise_std.unwrap_or(150.0), // Use provided or default value
945+
mag_obs: observed_magnetic,
946+
latitude: f64::NAN, // to be set in closed-loop using state
947+
longitude: f64::NAN, // to be set in closed-loop using state
948+
altitude: f64::NAN, // to be set in closed-loop using state
949+
year: datetime.year(),
950+
day: datetime.ordinal() as u16,
951+
};
952+
events.push(Event::Measurement {
953+
meas: Box::new(meas),
954+
elapsed_s: *t1,
955+
});
954956
}
955957
}
956958
}

0 commit comments

Comments
 (0)