classes

GeomagneticField.as

Stiahnuť GeomagneticField.as

/* 
source
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/hardware/GeomagneticField.java?av=f
*/


package sk.yoz.hardware
{
    public class GeomagneticField
    {
        private var mX:Number;
        private var mY:Number;
        private var mZ:Number;
        
        private var mGcLatitudeRad:Number;
        private var mGcLongitudeRad:Number;
        private var mGcRadiusKm:Number;
        
        private static const EARTH_SEMI_MAJOR_AXIS_KM:Number = 6378.137;
        private static const EARTH_SEMI_MINOR_AXIS_KM:Number = 6356.7523142;
        private static const EARTH_REFERENCE_RADIUS_KM:Number = 6371.2;
        
        private static const G_COEFF:Array = [
            [ 0.0 ],
            [ -29496.6, -1586.3 ],
            [ -2396.6, 3026.1, 1668.6 ],
            [ 1340.1, -2326.2, 1231.9, 634.0 ],
            [ 912.6, 808.9, 166.7, -357.1, 89.4 ],
            [ -230.9, 357.2, 200.3, -141.1, -163.0, -7.8 ],
            [ 72.8, 68.6, 76.0, -141.4, -22.8, 13.2, -77.9 ],
            [ 80.5, -75.1, -4.7, 45.3, 13.9, 10.4, 1.7, 4.9 ],
            [ 24.4, 8.1, -14.5, -5.6, -19.3, 11.5, 10.9, -14.1, -3.7 ],
            [ 5.4, 9.4, 3.4, -5.2, 3.1, -12.4, -0.7, 8.4, -8.5, -10.1 ],
            [ -2.0, -6.3, 0.9, -1.1, -0.2, 2.5, -0.3, 2.2, 3.1, -1.0, -2.8 ],
            [ 3.0, -1.5, -2.1, 1.7, -0.5, 0.5, -0.8, 0.4, 1.8, 0.1, 0.7, 3.8 ],
            [ -2.2, -0.2, 0.3, 1.0, -0.6, 0.9, -0.1, 0.5, -0.4, -0.4, 0.2, -0.8, 0.0 ] ];
        
        private static const H_COEFF:Array = [
            [ 0.0 ],
            [ 0.0, 4944.4 ],
            [ 0.0, -2707.7, -576.1 ],
            [ 0.0, -160.2, 251.9, -536.6 ],
            [ 0.0, 286.4, -211.2, 164.3, -309.1 ],
            [ 0.0, 44.6, 188.9, -118.2, 0.0, 100.9 ],
            [ 0.0, -20.8, 44.1, 61.5, -66.3, 3.1, 55.0 ],
            [ 0.0, -57.9, -21.1, 6.5, 24.9, 7.0, -27.7, -3.3 ],
            [ 0.0, 11.0, -20.0, 11.9, -17.4, 16.7, 7.0, -10.8, 1.7 ],
            [ 0.0, -20.5, 11.5, 12.8, -7.2, -7.4, 8.0, 2.1, -6.1, 7.0 ],
            [ 0.0, 2.8, -0.1, 4.7, 4.4, -7.2, -1.0, -3.9, -2.0, -2.0, -8.3 ],
            [ 0.0, 0.2, 1.7, -0.6, -1.8, 0.9, -0.4, -2.5, -1.3, -2.1, -1.9, -1.8 ],
            [ 0.0, -0.9, 0.3, 2.1, -2.5, 0.5, 0.6, 0.0, 0.1, 0.3, -0.9, -0.2, 0.9 ] ];
        
        private static const DELTA_G:Array = [
            [ 0.0 ],
            [ 11.6, 16.5 ],
            [ -12.1, -4.4, 1.9 ],
            [ 0.4, -4.1, -2.9, -7.7 ],
            [ -1.8, 2.3, -8.7, 4.6, -2.1 ],
            [ -1.0, 0.6, -1.8, -1.0, 0.9, 1.0 ],
            [ -0.2, -0.2, -0.1, 2.0, -1.7, -0.3, 1.7 ],
            [ 0.1, -0.1, -0.6, 1.3, 0.4, 0.3, -0.7, 0.6 ],
            [ -0.1, 0.1, -0.6, 0.2, -0.2, 0.3, 0.3, -0.6, 0.2 ],
            [ 0.0, -0.1, 0.0, 0.3, -0.4, -0.3, 0.1, -0.1, -0.4, -0.2 ],
            [ 0.0, 0.0, -0.1, 0.2, 0.0, -0.1, -0.2, 0.0, -0.1, -0.2, -0.2 ],
            [ 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.1, 0.0 ],
            [ 0.0, 0.0, 0.1, 0.1, -0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.1, 0.1 ] ];
        
        private static const DELTA_H:Array = [
            [ 0.0 ],
            [ 0.0, -25.9 ],
            [ 0.0, -22.5, -11.8 ],
            [ 0.0, 7.3, -3.9, -2.6 ],
            [ 0.0, 1.1, 2.7, 3.9, -0.8 ],
            [ 0.0, 0.4, 1.8, 1.2, 4.0, -0.6 ],
            [ 0.0, -0.2, -2.1, -0.4, -0.6, 0.5, 0.9 ],
            [ 0.0, 0.7, 0.3, -0.1, -0.1, -0.8, -0.3, 0.3 ],
            [ 0.0, -0.1, 0.2, 0.4, 0.4, 0.1, -0.1, 0.4, 0.3 ],
            [ 0.0, 0.0, -0.2, 0.0, -0.1, 0.1, 0.0, -0.2, 0.3, 0.2 ],
            [ 0.0, 0.1, -0.1, 0.0, -0.1, -0.1, 0.0, -0.1, -0.2, 0.0, -0.1 ],
            [ 0.0, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1, 0.0, -0.1, -0.1, 0.0, -0.1 ],
            [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] ];
        
        private static const BASE_TIME:Number = new Date(2010, 1, 1).time;
        
        private static const SCHMIDT_QUASI_NORM_FACTORS:Array =
            computeSchmidtQuasiNormFactors(G_COEFF.length);
        
        public function GeomagneticField(gdLatitudeDeg:Number, gdLongitudeDeg:Number, 
            altitudeMeters:Number, timeMillis:Number)
        {
            const MAX_N:int = G_COEFF.length;
            
            gdLatitudeDeg = Math.min(90.0 - 1e-5,
                Math.max(-90.0 + 1e-5, gdLatitudeDeg));
            computeGeocentricCoordinates(gdLatitudeDeg,
                gdLongitudeDeg,
                altitudeMeters);
            
            //assert G_COEFF.length == H_COEFF.length;
            
            var legendre:LegendreTable =
                new LegendreTable(MAX_N - 1,
                    (Math.PI / 2.0 - mGcLatitudeRad));
            
            var relativeRadiusPower:Array = new Array(MAX_N + 2);
            relativeRadiusPower[0] = 1.0;
            relativeRadiusPower[1] = EARTH_REFERENCE_RADIUS_KM / mGcRadiusKm;
            for (var i:int = 2; i < relativeRadiusPower.length; ++i) {
                relativeRadiusPower[i] = relativeRadiusPower[i - 1] *
                    relativeRadiusPower[1];
            }
            
            var sinMLon:Array = new Array(MAX_N);
            var cosMLon:Array = new Array(MAX_N);
            sinMLon[0] = 0.0;
            cosMLon[0] = 1.0;
            sinMLon[1] = Math.sin(mGcLongitudeRad);
            cosMLon[1] = Math.cos(mGcLongitudeRad);
            
            for (var m:int = 2; m < MAX_N; ++m) {
                var x:int = m >> 1;
                sinMLon[m] = sinMLon[m-x] * cosMLon[x] + cosMLon[m-x] * sinMLon[x];
                cosMLon[m] = cosMLon[m-x] * cosMLon[x] - sinMLon[m-x] * sinMLon[x];
            }
            
            var inverseCosLatitude:Number = 1.0 / Math.cos(mGcLatitudeRad);
            var yearsSinceBase:Number =
                (timeMillis - BASE_TIME) / (365 * 24 * 60 * 60 * 1000);
            
            var gcX:Number = 0.0;
            var gcY:Number = 0.0;
            var gcZ:Number = 0.0;
            
            for (var n:int = 1; n < MAX_N; n++) {
                for (m = 0; m <= n; m++) {
                    var g:Number = G_COEFF[n][m] + yearsSinceBase * DELTA_G[n][m];
                    var h:Number = H_COEFF[n][m] + yearsSinceBase * DELTA_H[n][m];
                    
                    gcX += relativeRadiusPower[n+2]
                        * (g * cosMLon[m] + h * sinMLon[m])
                        * legendre.mPDeriv[n][m]
                        * SCHMIDT_QUASI_NORM_FACTORS[n][m];
                    
                    gcY += relativeRadiusPower[n+2] * m
                        * (g * sinMLon[m] - h * cosMLon[m])
                        * legendre.mP[n][m]
                        * SCHMIDT_QUASI_NORM_FACTORS[n][m]
                        * inverseCosLatitude;
                    
                    gcZ -= (n + 1) * relativeRadiusPower[n+2]
                        * (g * cosMLon[m] + h * sinMLon[m])
                        * legendre.mP[n][m]
                        * SCHMIDT_QUASI_NORM_FACTORS[n][m];
                }
            }
            
            var latDiffRad:Number = gdLatitudeDeg * Math.PI / 180 - mGcLatitudeRad;
            mX = (gcX * Math.cos(latDiffRad)
                + gcZ * Math.sin(latDiffRad));
            mY = gcY;
            mZ = (- gcX * Math.sin(latDiffRad)
                + gcZ * Math.cos(latDiffRad));
        }
        
        public function get x():Number
        {
            return mX;
        }
        
        public function get y():Number
        {
            return mY;
        }
        
        public function get z():Number
        {
            return mZ;
        }
        
        public function get declination():Number
        {
            return Math.atan2(mY, mX) * 180 / Math.PI;
        }
        
        private function computeGeocentricCoordinates(gdLatitudeDeg:Number,
            gdLongitudeDeg:Number, altitudeMeters:Number):void
        {
                var altitudeKm:Number = altitudeMeters / 1000.0;
                var a2:Number = EARTH_SEMI_MAJOR_AXIS_KM * EARTH_SEMI_MAJOR_AXIS_KM;
                var b2:Number = EARTH_SEMI_MINOR_AXIS_KM * EARTH_SEMI_MINOR_AXIS_KM;
                var gdLatRad:Number = gdLatitudeDeg * 180 / Math.PI;
                var clat:Number = Math.cos(gdLatRad);
                var slat:Number = Math.sin(gdLatRad);
                var tlat:Number = slat / clat;
                var latRad:Number =
                    Math.sqrt(a2 * clat * clat + b2 * slat * slat);
                
                mGcLatitudeRad = Math.atan(tlat * (latRad * altitudeKm + b2)
                    / (latRad * altitudeKm + a2));
                
                mGcLongitudeRad = gdLongitudeDeg * 180 / Math.PI;
                
                var radSq:Number = altitudeKm * altitudeKm
                    + 2 * altitudeKm * Math.sqrt(a2 * clat * clat +
                        b2 * slat * slat)
                        + (a2 * a2 * clat * clat + b2 * b2 * slat * slat)
                        / (a2 * clat * clat + b2 * slat * slat);
                mGcRadiusKm = Math.sqrt(radSq);
            }
        
        private static function computeSchmidtQuasiNormFactors(maxN:int):Array
        {
            var schmidtQuasiNorm:Array = new Array(maxN + 1);
            schmidtQuasiNorm[0] = [ 1.0 ];
            for (var n:int = 1; n <= maxN; n++) {
                schmidtQuasiNorm[n] = new Array(n + 1);
                schmidtQuasiNorm[n][0] =
                    schmidtQuasiNorm[n - 1][0] * (2 * n - 1) / n;
                for (var m:int = 1; m <= n; m++) {
                    schmidtQuasiNorm[n][m] = schmidtQuasiNorm[n][m - 1]
                        * Math.sqrt((n - m + 1) * (m == 1 ? 2 : 1)
                            / (n + m));
                }
            }
            return schmidtQuasiNorm;
        }
    }
}

class LegendreTable {
    // These are the Gauss-normalized associated Legendre functions -- that
    // is, they are normal Legendre functions multiplied by
    // (n-m)!/(2n-1)!! (where (2n-1)!! = 1*3*5*...*2n-1)
    public var mP:Array = [];
    
    // Derivative of mP, with respect to theta.
    public var mPDeriv:Array = [];
    
    /**
     * @param maxN
     *            The maximum n- and m-values to support
     * @param thetaRad
     *            Returned functions will be Gauss-normalized
     *            P_n^m(cos(thetaRad)), with thetaRad in radians.
     */
    public function LegendreTable(maxN:int, thetaRad:Number)
    {
        // Compute the table of Gauss-normalized associated Legendre
        // functions using standard recursion relations. Also compute the
        // table of derivatives using the derivative of the recursion
        // relations.
        var cos:Number = Math.cos(thetaRad);
        var sin:Number = Math.sin(thetaRad);
        
        mP = new Array(maxN + 1);
        mPDeriv =  new Array(maxN + 1);
        mP[0] = [ 1.0 ];
        mPDeriv[0] = [ 0.0 ];
        for (var n:int = 1; n <= maxN; n++) {
            mP[n] = new Array(n + 1);
            mPDeriv[n] = new Array(n + 1);
            for (var m:int = 0; m <= n; m++) {
                if (n == m) {
                    mP[n][m] = sin * mP[n - 1][m - 1];
                    mPDeriv[n][m] = cos * mP[n - 1][m - 1]
                        + sin * mPDeriv[n - 1][m - 1];
                } else if (n == 1 || m == n - 1) {
                    mP[n][m] = cos * mP[n - 1][m];
                    mPDeriv[n][m] = -sin * mP[n - 1][m]
                        + cos * mPDeriv[n - 1][m];
                } else {
                    //assert n > 1 && m < n - 1;
                    var k:Number = ((n - 1) * (n - 1) - m * m)
                        / ((2 * n - 1) * (2 * n - 3));
                    mP[n][m] = cos * mP[n - 1][m] - k * mP[n - 2][m];
                    mPDeriv[n][m] = -sin * mP[n - 1][m]
                        + cos * mPDeriv[n - 1][m] - k * mPDeriv[n - 2][m];
                }
            }
        }
    }
}