EarthMagneticFluxDensityEstimator.java

/*
 * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.navigation.inertial.wmm;

import com.irurueta.units.Angle;
import com.irurueta.units.AngleConverter;
import com.irurueta.units.AngleUnit;

/**
 * Estimates Earth magnetic flux density resolved around NED frame at a
 * given Earth location.
 * Magnetic flux density depends of magnitude of the magnetic field,
 * the declination and the dip angle.
 * Magnitude typically varies from about 30 µT at the equator to about 60 µT
 * at the poles.
 * The dip (or inclination) angle is essentially the magnetic latitude and
 * it is typically within about 10º of the geodetic latitude.
 * The declination angle gives the bearing of the magnetic field from
 * true north, and is the only of the three parameters needed to determine a
 * user's heading form magnetic field measurements.
 * The declination angle may be calculated as a function of position and
 * time using global models, such as the 275-coefficient International
 * Geomagnetic Reference Field (IGRF) or the 336-coefficient U.D/U.K
 * World Magnetic Model (WMM).
 * <p>
 * IGRF: <a href="https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html">https://www.ngdc.noaa.gov/IAGA/vmod/igrf.html</a>
 * WMM: <a href="https://www.ngdc.noaa.gov/geomag/WMM">https://www.ngdc.noaa.gov/geomag/WMM</a>
 */
public class EarthMagneticFluxDensityEstimator {

    /**
     * Private constructor to prevent instantiation.
     */
    private EarthMagneticFluxDensityEstimator() {
    }

    /**
     * Estimates Earth magnetic flux density.
     *
     * @param magnitude   magnitude of magnetic field expressed in Teslas (T).
     * @param declination declination angle expressed in radians.
     * @param dip         dip (or inclination) angle expressed in radians.
     * @param result      instance where magnetic flux will be stored resolved
     *                    around NED frame.
     */
    @SuppressWarnings("DuplicatedCode")
    public static void estimate(
            final double magnitude, final double declination, final double dip, final NEDMagneticFluxDensity result) {

        final var cosDeclination = Math.cos(declination);
        final var sinDeclination = Math.sin(declination);

        final var cosDip = Math.cos(dip);
        final var sinDip = Math.sin(dip);

        final var bn = cosDeclination * cosDip * magnitude;
        final var be = sinDeclination * cosDip * magnitude;
        final var bd = sinDip * magnitude;

        result.setCoordinates(bn, be, bd);
    }

    /**
     * Estimates Earth magnetic flux density.
     *
     * @param magnitude   magnitude of magnetic field expressed in Teslas (T).
     * @param declination declination angle.
     * @param dip         dip (or inclination) angle.
     * @param result      instance where magnetic flux will be stored resolved
     *                    around NED frame.
     */
    public static void estimate(
            final double magnitude, final Angle declination, final Angle dip, final NEDMagneticFluxDensity result) {
        estimate(magnitude, convertAngle(declination), convertAngle(dip), result);
    }

    /**
     * Estimates Earth magnetic flux density.
     *
     * @param magnitude   magnitude of magnetic field expressed in Teslas (T).
     * @param declination declination angle expressed in radians.
     * @param dip         dip (or inclination) angle expressed in radians.
     * @return Earth magnetic flux density resolved around NED frame.
     */
    public static NEDMagneticFluxDensity estimate(final double magnitude, final double declination, final double dip) {
        final var result = new NEDMagneticFluxDensity();
        estimate(magnitude, declination, dip, result);
        return result;
    }

    /**
     * Estimates Earth magnetic flux density.
     *
     * @param magnitude   magnitude of magnetic field expressed in Teslas (T).
     * @param declination declination angle.
     * @param dip         dip (or inclination) angle.
     * @return Earth magnetic flux density resolved around NED frame.
     */
    public static NEDMagneticFluxDensity estimate(final double magnitude, final Angle declination, final Angle dip) {
        return estimate(magnitude, convertAngle(declination), convertAngle(dip));
    }

    /**
     * Gets declination angle expressed in radians.
     *
     * @param b a magnetic flux density resolved around NED frame.
     * @return declination angle.
     */
    public static double getDeclination(final NEDMagneticFluxDensity b) {
        final var bn = b.getBn();
        final var be = b.getBe();

        return Math.atan2(be, bn);
    }

    /**
     * Gets declination angle.
     *
     * @param b      a magnetic flux density resolved around NED frame.
     * @param result instance where declination angle will be stored.
     */
    public static void getDeclinationAsAngle(final NEDMagneticFluxDensity b, final Angle result) {
        result.setUnit(AngleUnit.RADIANS);
        result.setValue(getDeclination(b));
    }

    /**
     * Gets declination angle.
     *
     * @param b a magnetic flux density resolved around NED frame.
     * @return declination angle.
     */
    public static Angle getDeclinationAsAngle(final NEDMagneticFluxDensity b) {
        return new Angle(getDeclination(b), AngleUnit.RADIANS);
    }

    /**
     * Gets dip (a.k.a. inclination) angle expressed in radians.
     *
     * @param b a magnetic flux density resolved around NED frame.
     * @return dip angle.
     */
    @SuppressWarnings("JavaExistingMethodCanBeUsed")
    public static double getDip(final NEDMagneticFluxDensity b) {
        final var bn = b.getBn();
        final var be = b.getBe();
        final var bd = b.getBd();

        final var bn2 = bn * bn;
        final var be2 = be * be;
        return Math.atan(bd / Math.sqrt(bn2 + be2));
    }

    /**
     * Gets dip (a.k.a. inclination) angle expressed in radians.
     *
     * @param b      a magnetic flux density resolved around NED frame.
     * @param result instance where dip angle will be stored.
     */
    public static void getDipAsAngle(final NEDMagneticFluxDensity b, final Angle result) {
        result.setUnit(AngleUnit.RADIANS);
        result.setValue(getDip(b));
    }

    /**
     * Gets dip (a.k.a. inclination) angle expressed in radians.
     *
     * @param b a magnetic flux density resolved around NED frame.
     * @return dip angle.
     */
    public static Angle getDipAsAngle(final NEDMagneticFluxDensity b) {
        return new Angle(getDip(b), AngleUnit.RADIANS);
    }

    /**
     * Converts an angle instance into radians.
     *
     * @param angle angle to be converted.
     * @return converted value into radians.
     */
    private static double convertAngle(final Angle angle) {
        return AngleConverter.convert(angle.getValue().doubleValue(), angle.getUnit(), AngleUnit.RADIANS);
    }
}