19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2009 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.hardware; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.GregorianCalendar; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 228edad6f16fc1d60a163e0f63153ff4f8a95e6c0eScott Main * Estimates magnetic field at a given point on 239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Earth, and in particular, to compute the magnetic declination from true 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * north. 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <p>This uses the World Magnetic Model produced by the United States National 279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Geospatial-Intelligence Agency. More details about the model can be found at 289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <a href="http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml">http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml</a>. 299119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp * This class currently uses WMM-2010 which is valid until 2015, but should 309119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp * produce acceptable results for several years after that. Future versions of 319119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp * Android may use a newer version of the model. 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class GeomagneticField { 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // The magnetic field at a given point, in nonoteslas in geodetic 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // coordinates. 369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mX; 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mY; 389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mZ; 399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Geocentric coordinates -- set by computeGeocentricCoordinates. 419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mGcLatitudeRad; 429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mGcLongitudeRad; 439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private float mGcRadiusKm; 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Constants from WGS84 (the coordinate system used by GPS) 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float EARTH_SEMI_MAJOR_AXIS_KM = 6378.137f; 479119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp static private final float EARTH_SEMI_MINOR_AXIS_KM = 6356.7523142f; 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float EARTH_REFERENCE_RADIUS_KM = 6371.2f; 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // These coefficients and the formulae used below are from: 519119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp // NOAA Technical Report: The US/UK World Magnetic Model for 2010-2015 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float[][] G_COEFF = new float[][] { 539119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f }, 549119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -29496.6f, -1586.3f }, 559119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -2396.6f, 3026.1f, 1668.6f }, 569119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 1340.1f, -2326.2f, 1231.9f, 634.0f }, 579119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 912.6f, 808.9f, 166.7f, -357.1f, 89.4f }, 589119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -230.9f, 357.2f, 200.3f, -141.1f, -163.0f, -7.8f }, 599119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 72.8f, 68.6f, 76.0f, -141.4f, -22.8f, 13.2f, -77.9f }, 609119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 80.5f, -75.1f, -4.7f, 45.3f, 13.9f, 10.4f, 1.7f, 4.9f }, 619119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 24.4f, 8.1f, -14.5f, -5.6f, -19.3f, 11.5f, 10.9f, -14.1f, -3.7f }, 629119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 5.4f, 9.4f, 3.4f, -5.2f, 3.1f, -12.4f, -0.7f, 8.4f, -8.5f, -10.1f }, 639119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -2.0f, -6.3f, 0.9f, -1.1f, -0.2f, 2.5f, -0.3f, 2.2f, 3.1f, -1.0f, -2.8f }, 649119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 3.0f, -1.5f, -2.1f, 1.7f, -0.5f, 0.5f, -0.8f, 0.4f, 1.8f, 0.1f, 0.7f, 3.8f }, 659119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -2.2f, -0.2f, 0.3f, 1.0f, -0.6f, 0.9f, -0.1f, 0.5f, -0.4f, -0.4f, 0.2f, -0.8f, 0.0f } }; 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float[][] H_COEFF = new float[][] { 689119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f }, 699119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 4944.4f }, 709119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -2707.7f, -576.1f }, 719119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -160.2f, 251.9f, -536.6f }, 729119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 286.4f, -211.2f, 164.3f, -309.1f }, 739119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 44.6f, 188.9f, -118.2f, 0.0f, 100.9f }, 749119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -20.8f, 44.1f, 61.5f, -66.3f, 3.1f, 55.0f }, 759119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -57.9f, -21.1f, 6.5f, 24.9f, 7.0f, -27.7f, -3.3f }, 769119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 11.0f, -20.0f, 11.9f, -17.4f, 16.7f, 7.0f, -10.8f, 1.7f }, 779119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -20.5f, 11.5f, 12.8f, -7.2f, -7.4f, 8.0f, 2.1f, -6.1f, 7.0f }, 789119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 2.8f, -0.1f, 4.7f, 4.4f, -7.2f, -1.0f, -3.9f, -2.0f, -2.0f, -8.3f }, 799119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.2f, 1.7f, -0.6f, -1.8f, 0.9f, -0.4f, -2.5f, -1.3f, -2.1f, -1.9f, -1.8f }, 809119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -0.9f, 0.3f, 2.1f, -2.5f, 0.5f, 0.6f, 0.0f, 0.1f, 0.3f, -0.9f, -0.2f, 0.9f } }; 819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float[][] DELTA_G = new float[][] { 839119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f }, 849119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 11.6f, 16.5f }, 859119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -12.1f, -4.4f, 1.9f }, 869119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.4f, -4.1f, -2.9f, -7.7f }, 879119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -1.8f, 2.3f, -8.7f, 4.6f, -2.1f }, 889119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -1.0f, 0.6f, -1.8f, -1.0f, 0.9f, 1.0f }, 899119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -0.2f, -0.2f, -0.1f, 2.0f, -1.7f, -0.3f, 1.7f }, 909119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.1f, -0.1f, -0.6f, 1.3f, 0.4f, 0.3f, -0.7f, 0.6f }, 919119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { -0.1f, 0.1f, -0.6f, 0.2f, -0.2f, 0.3f, 0.3f, -0.6f, 0.2f }, 929119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -0.1f, 0.0f, 0.3f, -0.4f, -0.3f, 0.1f, -0.1f, -0.4f, -0.2f }, 939119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, -0.1f, 0.2f, 0.0f, -0.1f, -0.2f, 0.0f, -0.1f, -0.2f, -0.2f }, 949119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, 0.0f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.1f, 0.0f }, 959119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, 0.1f, 0.1f, -0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.1f, 0.1f } }; 969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float[][] DELTA_H = new float[][] { 989119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f }, 999119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -25.9f }, 1009119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -22.5f, -11.8f }, 1019119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 7.3f, -3.9f, -2.6f }, 1029119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 1.1f, 2.7f, 3.9f, -0.8f }, 1039119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.4f, 1.8f, 1.2f, 4.0f, -0.6f }, 1049119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -0.2f, -2.1f, -0.4f, -0.6f, 0.5f, 0.9f }, 1059119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.7f, 0.3f, -0.1f, -0.1f, -0.8f, -0.3f, 0.3f }, 1069119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, -0.1f, 0.2f, 0.4f, 0.4f, 0.1f, -0.1f, 0.4f, 0.3f }, 1079119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, -0.2f, 0.0f, -0.1f, 0.1f, 0.0f, -0.2f, 0.3f, 0.2f }, 1089119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.1f, -0.1f, 0.0f, -0.1f, -0.1f, 0.0f, -0.1f, -0.2f, 0.0f, -0.1f }, 1099119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, 0.1f, 0.0f, 0.1f, 0.0f, 0.1f, 0.0f, -0.1f, -0.1f, 0.0f, -0.1f }, 1109119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f } }; 1119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final long BASE_TIME = 1139119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp new GregorianCalendar(2010, 1, 1).getTimeInMillis(); 1149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // The ratio between the Gauss-normalized associated Legendre functions and 1169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // the Schmid quasi-normalized ones. Compute these once staticly since they 1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // don't depend on input variables at all. 1189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private final float[][] SCHMIDT_QUASI_NORM_FACTORS = 1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project computeSchmidtQuasiNormFactors(G_COEFF.length); 1209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Estimate the magnetic field at a given point and time. 1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 1249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param gdLatitudeDeg 1259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Latitude in WGS84 geodetic coordinates -- positive is east. 1269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param gdLongitudeDeg 1279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Longitude in WGS84 geodetic coordinates -- positive is north. 1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param altitudeMeters 1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Altitude in WGS84 geodetic coordinates, in meters. 1309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param timeMillis 1319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Time at which to evaluate the declination, in milliseconds 1329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * since January 1, 1970. (approximate is fine -- the declination 1339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * changes very slowly). 1349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public GeomagneticField(float gdLatitudeDeg, 1369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float gdLongitudeDeg, 1379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float altitudeMeters, 1389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project long timeMillis) { 1399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project final int MAX_N = G_COEFF.length; // Maximum degree of the coefficients. 1409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // We don't handle the north and south poles correctly -- pretend that 1429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // we're not quite at them to avoid crashing. 1439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project gdLatitudeDeg = Math.min(90.0f - 1e-5f, 1449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Math.max(-90.0f + 1e-5f, gdLatitudeDeg)); 1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project computeGeocentricCoordinates(gdLatitudeDeg, 1469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project gdLongitudeDeg, 1479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project altitudeMeters); 1489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project assert G_COEFF.length == H_COEFF.length; 1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Note: LegendreTable computes associated Legendre functions for 1529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // cos(theta). We want the associated Legendre functions for 1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // sin(latitude), which is the same as cos(PI/2 - latitude), except the 1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // derivate will be negated. 1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project LegendreTable legendre = 1569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project new LegendreTable(MAX_N - 1, 1579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (float) (Math.PI / 2.0 - mGcLatitudeRad)); 1589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Compute a table of (EARTH_REFERENCE_RADIUS_KM / radius)^n for i in 1609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // 0..MAX_N-2 (this is much faster than calling Math.pow MAX_N+1 times). 1619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float[] relativeRadiusPower = new float[MAX_N + 2]; 1629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project relativeRadiusPower[0] = 1.0f; 1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project relativeRadiusPower[1] = EARTH_REFERENCE_RADIUS_KM / mGcRadiusKm; 1649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 2; i < relativeRadiusPower.length; ++i) { 1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project relativeRadiusPower[i] = relativeRadiusPower[i - 1] * 1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project relativeRadiusPower[1]; 1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Compute tables of sin(lon * m) and cos(lon * m) for m = 0..MAX_N -- 1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // this is much faster than calling Math.sin and Math.com MAX_N+1 times. 1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float[] sinMLon = new float[MAX_N]; 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float[] cosMLon = new float[MAX_N]; 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sinMLon[0] = 0.0f; 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project cosMLon[0] = 1.0f; 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sinMLon[1] = (float) Math.sin(mGcLongitudeRad); 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project cosMLon[1] = (float) Math.cos(mGcLongitudeRad); 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int m = 2; m < MAX_N; ++m) { 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Standard expansions for sin((m-x)*theta + x*theta) and 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // cos((m-x)*theta + x*theta). 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int x = m >> 1; 1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project sinMLon[m] = sinMLon[m-x] * cosMLon[x] + cosMLon[m-x] * sinMLon[x]; 1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project cosMLon[m] = cosMLon[m-x] * cosMLon[x] - sinMLon[m-x] * sinMLon[x]; 1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float inverseCosLatitude = 1.0f / (float) Math.cos(mGcLatitudeRad); 1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float yearsSinceBase = 1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (timeMillis - BASE_TIME) / (365f * 24f * 60f * 60f * 1000f); 1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // We now compute the magnetic field strength given the geocentric 1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // location. The magnetic field is the derivative of the potential 1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // function defined by the model. See NOAA Technical Report: The US/UK 1939119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp // World Magnetic Model for 2010-2015 for the derivation. 1949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float gcX = 0.0f; // Geocentric northwards component. 1959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float gcY = 0.0f; // Geocentric eastwards component. 1969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float gcZ = 0.0f; // Geocentric downwards component. 1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int n = 1; n < MAX_N; n++) { 1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int m = 0; m <= n; m++) { 2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Adjust the coefficients for the current date. 2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float g = G_COEFF[n][m] + yearsSinceBase * DELTA_G[n][m]; 2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float h = H_COEFF[n][m] + yearsSinceBase * DELTA_H[n][m]; 2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Negative derivative with respect to latitude, divided by 2059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // radius. This looks like the negation of the version in the 2069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // NOAA Techincal report because that report used 2079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // P_n^m(sin(theta)) and we use P_n^m(cos(90 - theta)), so the 2089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // derivative with respect to theta is negated. 2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project gcX += relativeRadiusPower[n+2] 2109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * (g * cosMLon[m] + h * sinMLon[m]) 2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * legendre.mPDeriv[n][m] 2129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * SCHMIDT_QUASI_NORM_FACTORS[n][m]; 2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Negative derivative with respect to longitude, divided by 2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // radius. 2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project gcY += relativeRadiusPower[n+2] * m 2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * (g * sinMLon[m] - h * cosMLon[m]) 2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * legendre.mP[n][m] 2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * SCHMIDT_QUASI_NORM_FACTORS[n][m] 2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * inverseCosLatitude; 2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Negative derivative with respect to radius. 2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project gcZ -= (n + 1) * relativeRadiusPower[n+2] 2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * (g * cosMLon[m] + h * sinMLon[m]) 2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * legendre.mP[n][m] 2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * SCHMIDT_QUASI_NORM_FACTORS[n][m]; 2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Convert back to geodetic coordinates. This is basically just a 2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // rotation around the Y-axis by the difference in latitudes between the 2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // geocentric frame and the geodetic frame. 2339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project double latDiffRad = Math.toRadians(gdLatitudeDeg) - mGcLatitudeRad; 2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mX = (float) (gcX * Math.cos(latDiffRad) 2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + gcZ * Math.sin(latDiffRad)); 2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mY = gcY; 2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mZ = (float) (- gcX * Math.sin(latDiffRad) 2389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + gcZ * Math.cos(latDiffRad)); 2399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The X (northward) component of the magnetic field in nanoteslas. 2439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getX() { 2459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mX; 2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The Y (eastward) component of the magnetic field in nanoteslas. 2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getY() { 2529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mY; 2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The Z (downward) component of the magnetic field in nanoteslas. 2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getZ() { 2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mZ; 2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The declination of the horizontal component of the magnetic 2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * field from true north, in degrees (i.e. positive means the 2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * magnetic field is rotated east that much from true north). 2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getDeclination() { 2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return (float) Math.toDegrees(Math.atan2(mY, mX)); 2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return The inclination of the magnetic field in degrees -- positive 2739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * means the magnetic field is rotated downwards. 2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getInclination() { 2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return (float) Math.toDegrees(Math.atan2(mZ, 2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project getHorizontalStrength())); 2789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return Horizontal component of the field strength in nonoteslas. 2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getHorizontalStrength() { 2849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return (float) Math.sqrt(mX * mX + mY * mY); 2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return Total field strength in nanoteslas. 2899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public float getFieldStrength() { 2919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return (float) Math.sqrt(mX * mX + mY * mY + mZ * mZ); 2929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param gdLatitudeDeg 2969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Latitude in WGS84 geodetic coordinates. 2979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param gdLongitudeDeg 2989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Longitude in WGS84 geodetic coordinates. 2999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param altitudeMeters 3009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Altitude above sea level in WGS84 geodetic coordinates. 3019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return Geocentric latitude (i.e. angle between closest point on the 3029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * equator and this point, at the center of the earth. 3039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private void computeGeocentricCoordinates(float gdLatitudeDeg, 3059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float gdLongitudeDeg, 3069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float altitudeMeters) { 3079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float altitudeKm = altitudeMeters / 1000.0f; 3089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float a2 = EARTH_SEMI_MAJOR_AXIS_KM * EARTH_SEMI_MAJOR_AXIS_KM; 3099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float b2 = EARTH_SEMI_MINOR_AXIS_KM * EARTH_SEMI_MINOR_AXIS_KM; 3109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project double gdLatRad = Math.toRadians(gdLatitudeDeg); 3119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float clat = (float) Math.cos(gdLatRad); 3129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float slat = (float) Math.sin(gdLatRad); 3139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float tlat = slat / clat; 3149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float latRad = 3159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project (float) Math.sqrt(a2 * clat * clat + b2 * slat * slat); 3169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mGcLatitudeRad = (float) Math.atan(tlat * (latRad * altitudeKm + b2) 3189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project / (latRad * altitudeKm + a2)); 3199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mGcLongitudeRad = (float) Math.toRadians(gdLongitudeDeg); 3219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float radSq = altitudeKm * altitudeKm 3239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + 2 * altitudeKm * (float) Math.sqrt(a2 * clat * clat + 3249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project b2 * slat * slat) 3259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + (a2 * a2 * clat * clat + b2 * b2 * slat * slat) 3269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project / (a2 * clat * clat + b2 * slat * slat); 3279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mGcRadiusKm = (float) Math.sqrt(radSq); 3289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Utility class to compute a table of Gauss-normalized associated Legendre 3339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * functions P_n^m(cos(theta)) 3349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project static private class LegendreTable { 3369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // These are the Gauss-normalized associated Legendre functions -- that 3379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // is, they are normal Legendre functions multiplied by 3389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // (n-m)!/(2n-1)!! (where (2n-1)!! = 1*3*5*...*2n-1) 3399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public final float[][] mP; 3409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Derivative of mP, with respect to theta. 3429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public final float[][] mPDeriv; 3439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param maxN 3469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * The maximum n- and m-values to support 3479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param thetaRad 3489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Returned functions will be Gauss-normalized 3499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * P_n^m(cos(thetaRad)), with thetaRad in radians. 3509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public LegendreTable(int maxN, float thetaRad) { 3529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Compute the table of Gauss-normalized associated Legendre 3539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // functions using standard recursion relations. Also compute the 3549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // table of derivatives using the derivative of the recursion 3559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // relations. 3569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float cos = (float) Math.cos(thetaRad); 3579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float sin = (float) Math.sin(thetaRad); 3589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP = new float[maxN + 1][]; 3609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv = new float[maxN + 1][]; 3619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP[0] = new float[] { 1.0f }; 3629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv[0] = new float[] { 0.0f }; 3639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int n = 1; n <= maxN; n++) { 3649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP[n] = new float[n + 1]; 3659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv[n] = new float[n + 1]; 3669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int m = 0; m <= n; m++) { 3679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (n == m) { 3689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP[n][m] = sin * mP[n - 1][m - 1]; 3699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv[n][m] = cos * mP[n - 1][m - 1] 3709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + sin * mPDeriv[n - 1][m - 1]; 3719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else if (n == 1 || m == n - 1) { 3729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP[n][m] = cos * mP[n - 1][m]; 3739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv[n][m] = -sin * mP[n - 1][m] 3749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + cos * mPDeriv[n - 1][m]; 3759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 3769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project assert n > 1 && m < n - 1; 3779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float k = ((n - 1) * (n - 1) - m * m) 3789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project / (float) ((2 * n - 1) * (2 * n - 3)); 3799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mP[n][m] = cos * mP[n - 1][m] - k * mP[n - 2][m]; 3809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mPDeriv[n][m] = -sin * mP[n - 1][m] 3819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project + cos * mPDeriv[n - 1][m] - k * mPDeriv[n - 2][m]; 3829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 3879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 3889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 3899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Compute the ration between the Gauss-normalized associated Legendre 3909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * functions and the Schmidt quasi-normalized version. This is equivalent to 3919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * sqrt((m==0?1:2)*(n-m)!/(n+m!))*(2n-1)!!/(n-m)! 3929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 3939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static float[][] computeSchmidtQuasiNormFactors(int maxN) { 3949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project float[][] schmidtQuasiNorm = new float[maxN + 1][]; 3959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project schmidtQuasiNorm[0] = new float[] { 1.0f }; 3969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int n = 1; n <= maxN; n++) { 3979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project schmidtQuasiNorm[n] = new float[n + 1]; 3989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project schmidtQuasiNorm[n][0] = 3999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project schmidtQuasiNorm[n - 1][0] * (2 * n - 1) / (float) n; 4009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int m = 1; m <= n; m++) { 4019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project schmidtQuasiNorm[n][m] = schmidtQuasiNorm[n][m - 1] 4029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * (float) Math.sqrt((n - m + 1) * (m == 1 ? 2 : 1) 4039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project / (float) (n + m)); 4049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return schmidtQuasiNorm; 4079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 4089119caa144c8eafdba986002003521af3aedfe67Rodrigo Damazio Bovendorp} 409