/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * Adam M Rivera * With direction from: Andrew Tridgell, Jason Short, Justin Beech * * Adapted from: http://www.societyofrobots.com/robotforum/index.php?topic=11855.0 * Scott Ferguson * scottfromscott@gmail.com * */ #include "AP_Declination.h" #include #include #include /* calculate magnetic field intensity and orientation */ bool AP_Declination::get_mag_field_ef(float latitude_deg, float longitude_deg, float &intensity_gauss, float &declination_deg, float &inclination_deg) { bool valid_input_data = true; /* round down to nearest sampling resolution. On some platforms (e.g. clang on macOS), the behaviour of implicit casts from int32 to float can be undefined thus making it explicit here. */ float min_lat = float(static_cast(static_cast(floorf(latitude_deg / SAMPLING_RES)) * SAMPLING_RES)); float min_lon = float(static_cast(static_cast(floorf(longitude_deg / SAMPLING_RES)) * SAMPLING_RES)); /* for the rare case of hitting the bounds exactly * the rounding logic wouldn't fit, so enforce it. */ /* limit to table bounds - required for maxima even when table spans full globe range */ if (latitude_deg <= SAMPLING_MIN_LAT) { min_lat = float(static_cast(SAMPLING_MIN_LAT)); valid_input_data = false; } if (latitude_deg >= SAMPLING_MAX_LAT) { min_lat = float(static_cast(static_cast(latitude_deg / SAMPLING_RES) * SAMPLING_RES - SAMPLING_RES)); valid_input_data = false; } if (longitude_deg <= SAMPLING_MIN_LON) { min_lon = float(static_cast(SAMPLING_MIN_LON)); valid_input_data = false; } if (longitude_deg >= SAMPLING_MAX_LON) { min_lon = float(static_cast(static_cast(longitude_deg / SAMPLING_RES) * SAMPLING_RES - SAMPLING_RES)); valid_input_data = false; } /* find index of nearest low sampling point */ uint32_t min_lat_index = constrain_int32(static_cast((-(SAMPLING_MIN_LAT) + min_lat) / SAMPLING_RES), 0, LAT_TABLE_SIZE - 2); uint32_t min_lon_index = constrain_int32(static_cast((-(SAMPLING_MIN_LON) + min_lon) / SAMPLING_RES), 0, LON_TABLE_SIZE -2); /* calculate intensity */ float data_sw = intensity_table[min_lat_index][min_lon_index]; float data_se = intensity_table[min_lat_index][min_lon_index + 1]; float data_ne = intensity_table[min_lat_index + 1][min_lon_index + 1]; float data_nw = intensity_table[min_lat_index + 1][min_lon_index]; /* perform bilinear interpolation on the four grid corners */ float data_min = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_se - data_sw) + data_sw; float data_max = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_ne - data_nw) + data_nw; intensity_gauss = ((latitude_deg - min_lat) / SAMPLING_RES) * (data_max - data_min) + data_min; /* calculate declination */ data_sw = declination_table[min_lat_index][min_lon_index]; data_se = declination_table[min_lat_index][min_lon_index + 1]; data_ne = declination_table[min_lat_index + 1][min_lon_index + 1]; data_nw = declination_table[min_lat_index + 1][min_lon_index]; /* perform bilinear interpolation on the four grid corners */ data_min = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_se - data_sw) + data_sw; data_max = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_ne - data_nw) + data_nw; declination_deg = ((latitude_deg - min_lat) / SAMPLING_RES) * (data_max - data_min) + data_min; /* calculate inclination */ data_sw = inclination_table[min_lat_index][min_lon_index]; data_se = inclination_table[min_lat_index][min_lon_index + 1]; data_ne = inclination_table[min_lat_index + 1][min_lon_index + 1]; data_nw = inclination_table[min_lat_index + 1][min_lon_index]; /* perform bilinear interpolation on the four grid corners */ data_min = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_se - data_sw) + data_sw; data_max = ((longitude_deg - min_lon) / SAMPLING_RES) * (data_ne - data_nw) + data_nw; inclination_deg = ((latitude_deg - min_lat) / SAMPLING_RES) * (data_max - data_min) + data_min; return valid_input_data; } /* calculate magnetic field intensity and orientation */ float AP_Declination::get_declination(float latitude_deg, float longitude_deg) { float declination_deg=0, inclination_deg=0, intensity_gauss=0; get_mag_field_ef(latitude_deg, longitude_deg, intensity_gauss, declination_deg, inclination_deg); return declination_deg; } /* get earth field as a Vector3f in Gauss given a Location */ Vector3f AP_Declination::get_earth_field_ga(const Location &loc) { float declination_deg=0, inclination_deg=0, intensity_gauss=0; get_mag_field_ef(loc.lat*1.0e-7f, loc.lng*1.0e-7f, intensity_gauss, declination_deg, inclination_deg); // create earth field Vector3f mag_ef = Vector3f(intensity_gauss, 0.0, 0.0); Matrix3f R; R.from_euler(0.0f, -ToRad(inclination_deg), ToRad(declination_deg)); mag_ef = R * mag_ef; return mag_ef; }