Skip to content

Commit 08e9201

Browse files
author
Zach Glueckert
committed
Corrects the interpolation point calculated for latitude within the EGM96 data grid points. The point is now calculated from the minimum latitude in agreement with the bilinear interpolation method used.
1 parent 7c57ba5 commit 08e9201

File tree

2 files changed

+262
-2
lines changed

2 files changed

+262
-2
lines changed

src/gov/nasa/worldwind/util/EGM96.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public double getOffset(Angle latitude, Angle longitude)
129129
rightCol = 0;
130130
}
131131

132-
double latTop = 90 - topRow * INTERVAL.degrees;
132+
double latBottom = 90 - bottomRow * INTERVAL.degrees;
133133
double lonLeft = leftCol * INTERVAL.degrees;
134134

135135
double ul = this.gePostOffset(topRow, leftCol);
@@ -138,7 +138,7 @@ public double getOffset(Angle latitude, Angle longitude)
138138
double ur = this.gePostOffset(topRow, rightCol);
139139

140140
double u = (lon - lonLeft) / INTERVAL.degrees;
141-
double v = (latTop - lat) / INTERVAL.degrees;
141+
double v = (lat - latBottom) / INTERVAL.degrees;
142142

143143
double pll = (1.0 - u) * (1.0 - v);
144144
double plr = u * (1.0 - v);
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright (C) 2016 United States Government as represented by the Administrator of the
3+
* National Aeronautics and Space Administration.
4+
* All Rights Reserved.
5+
*/
6+
7+
package gov.nasa.worldwind.util;
8+
9+
import gov.nasa.worldwind.geom.Angle;
10+
import org.junit.Test;
11+
12+
import java.io.IOException;
13+
14+
import static junit.framework.Assert.assertEquals;
15+
16+
/**
17+
* Created by zach on 7/6/16.
18+
*/
19+
public class EGM96Test
20+
{
21+
22+
/**
23+
* The acceptable difference two double values may have and still satisfy an {@code assertEquals} method.
24+
*/
25+
public static final double DELTA = 1e-6;
26+
27+
/**
28+
* The EGM96 data path.
29+
*/
30+
public static final String OFFSETS_FILE_PATH = "config/EGM96.dat";
31+
32+
/**
33+
* Tests the determination of the EGM offset value using a latitude value that should match a grid point.
34+
*/
35+
@Test
36+
public void testGetOffset_VerticalInterpolationTopGridPoint() throws IOException
37+
{
38+
39+
// New EGM96 instance with EGM96 dataset
40+
EGM96 egm96 = new EGM96(OFFSETS_FILE_PATH);
41+
// The EGM96 data has an interval of 0.25 degrees in both the horizontal and vertical dimensions. To test and
42+
// demonstrate the fractional value being determined is correct, this setup will isolate a vertical
43+
// interpolation by landing on the horizontal grid points (e.g. longitudes ending with 0.0, 0.25, 0.5, 0.75)
44+
Angle longitude = Angle.fromDegrees(-105.0);
45+
// This is a non-interpolated baseline latitude for the top grid point of our testing points
46+
Angle latitude = Angle.fromDegrees(38.75);
47+
48+
// Find the row and column values using the identical method the getOffset method uses
49+
// Code below is directly copied from the getOffset method accept where static class references were added
50+
double lat = latitude.degrees;
51+
double lon = longitude.degrees >= 0 ? longitude.degrees : longitude.degrees + 360;
52+
int topRow = (int) ((90 - lat) / EGM96.INTERVAL.degrees);
53+
if (lat <= -90)
54+
topRow = EGM96.NUM_ROWS - 2;
55+
int bottomRow = topRow + 1;
56+
// Note that the number of columns does not repeat the column at 0 longitude, so we must force the right
57+
// column to 0 for any longitude that's less than one interval from 360, and force the left column to the
58+
// last column of the grid.
59+
int leftCol = (int) (lon / EGM96.INTERVAL.degrees);
60+
int rightCol = leftCol + 1;
61+
if (lon >= 360 - EGM96.INTERVAL.degrees)
62+
{
63+
leftCol = EGM96.NUM_COLS - 1;
64+
rightCol = 0;
65+
}
66+
// Determine the functions determination of the top lat and left lon
67+
double latTop = 90 - topRow * EGM96.INTERVAL.degrees;
68+
double lonLeft = leftCol * EGM96.INTERVAL.degrees;
69+
70+
// Ensure the top latitude matches our expected latitude
71+
// This shows that the method has determined a row and column to query the dataset that corresponds with our
72+
// latitude value
73+
assertEquals("latitude matches after index conversion", latitude.degrees, latTop, DELTA);
74+
75+
// Using the confirmed latitude value from above (via the topRow and leftCol values), find the actual node value
76+
double latGridPointOffset = egm96.gePostOffset(topRow, leftCol) / 100d; // the other method converts to meters
77+
// Use the interpolation method to determine the offset value
78+
double latOffset = egm96.getOffset(latitude, longitude);
79+
80+
// Ensure that they are equal
81+
assertEquals("interpolated matches actual latitude", latGridPointOffset, latOffset, DELTA);
82+
}
83+
84+
/**
85+
* Tests the determination of the EGM offset value using a latitude value between grid points. This method will use
86+
* the bilinear interpolation method to calculate the offset value.
87+
*/
88+
@Test
89+
public void testGetOffset_VerticalInterpolationPoint() throws IOException
90+
{
91+
92+
// New EGM96 instance
93+
EGM96 egm96 = new EGM96(OFFSETS_FILE_PATH);
94+
// The EGM96 data has an interval of 0.25 degrees in both the horizontal and vertical dimensions. To test and
95+
// demonstrate the fractional value being determined is correct, this setup will isolate a vertical
96+
// interpolation by landing on the horizontal grid points (e.g. longitudes ending with .0, 0.25, 0.5, 0.75)
97+
Angle longitude = Angle.fromDegrees(-105.0);
98+
// This is a non-interpolated baseline latitude for the top grid point of our testing points, it is closer to
99+
// the top grid point
100+
Angle latitude = Angle.fromDegrees(38.72);
101+
102+
// Find the row and column values using the identical method the getOffset method uses
103+
// Code below is directly copied from the getOffset method accept where static class references were added
104+
double lat = latitude.degrees;
105+
double lon = longitude.degrees >= 0 ? longitude.degrees : longitude.degrees + 360;
106+
int topRow = (int) ((90 - lat) / EGM96.INTERVAL.degrees);
107+
if (lat <= -90)
108+
topRow = EGM96.NUM_ROWS - 2;
109+
int bottomRow = topRow + 1;
110+
// Note that the number of columns does not repeat the column at 0 longitude, so we must force the right
111+
// column to 0 for any longitude that's less than one interval from 360, and force the left column to the
112+
// last column of the grid.
113+
int leftCol = (int) (lon / EGM96.INTERVAL.degrees);
114+
int rightCol = leftCol + 1;
115+
if (lon >= 360 - EGM96.INTERVAL.degrees)
116+
{
117+
leftCol = EGM96.NUM_COLS - 1;
118+
rightCol = 0;
119+
}
120+
// Determine the functions determination of the top lat and left lon
121+
double latTop = 90 - topRow * EGM96.INTERVAL.degrees;
122+
// Need the bottom grid value for our own linear interpolation determination
123+
double latBottom = 90 - bottomRow * EGM96.INTERVAL.degrees;
124+
double lonLeft = leftCol * EGM96.INTERVAL.degrees;
125+
126+
// Find the offset values of the top and bottom grid points
127+
double bottomOffsetValue = egm96.gePostOffset(bottomRow, leftCol) / 100d;
128+
double topOffsetValue = egm96.gePostOffset(topRow, leftCol) / 100d;
129+
130+
// Ensure the top latitude matches our expected latitude
131+
// This shows that the method has determined a row and column to query the dataset that corresponds with our
132+
// latitude value
133+
assertEquals("top latitude matches after index conversion", 38.75, latTop, DELTA);
134+
assertEquals("bottom latitude matches after index conversion", 38.5, latBottom, DELTA);
135+
136+
// The calculated EGM96 offset
137+
double latOffset = egm96.getOffset(latitude, longitude);
138+
double manuallyCalculatedV = (lat - latBottom) / (latTop - latBottom);
139+
double manuallyCalculatedInterpolationValue = (topOffsetValue - bottomOffsetValue) * manuallyCalculatedV
140+
+ bottomOffsetValue;
141+
142+
// Ensure that they are equal
143+
assertEquals("interpolated matches actual latitude", manuallyCalculatedInterpolationValue, latOffset, DELTA);
144+
}
145+
146+
/**
147+
* Tests the determination of the EGM offset value using a longitude value that should match a grid point.
148+
*/
149+
@Test
150+
public void testGetOffset_HorizontalInterpolationLeftGridPoint() throws IOException
151+
{
152+
153+
// New EGM96 instance
154+
EGM96 egm96 = new EGM96(OFFSETS_FILE_PATH);
155+
// The EGM96 data has an interval of 0.25 degrees in both the horizontal and vertical dimensions. To test and
156+
// demonstrate the fractional value being determined is correct, this setup will isolate a horizontal
157+
// interpolation by landing on the vertical grid points (e.g. latitudes ending with .0, 0.25, 0.5, 0.75)
158+
Angle latitude = Angle.fromDegrees(38.75);
159+
// This is a non-interpolated baseline latitude for the left grid point of our testing points
160+
Angle longitude = Angle.fromDegrees(-105.0);
161+
162+
// Find the row and column values using the identical method the getOffset method uses
163+
// Code below is directly copied from the getOffset method accept where static class references were added
164+
double lat = latitude.degrees;
165+
double lon = longitude.degrees >= 0 ? longitude.degrees : longitude.degrees + 360;
166+
int topRow = (int) ((90 - lat) / EGM96.INTERVAL.degrees);
167+
if (lat <= -90)
168+
topRow = EGM96.NUM_ROWS - 2;
169+
int bottomRow = topRow + 1;
170+
// Note that the number of columns does not repeat the column at 0 longitude, so we must force the right
171+
// column to 0 for any longitude that's less than one interval from 360, and force the left column to the
172+
// last column of the grid.
173+
int leftCol = (int) (lon / EGM96.INTERVAL.degrees);
174+
int rightCol = leftCol + 1;
175+
if (lon >= 360 - EGM96.INTERVAL.degrees)
176+
{
177+
leftCol = EGM96.NUM_COLS - 1;
178+
rightCol = 0;
179+
}
180+
// Determine the functions determination of the top lat and left lon
181+
double latTop = 90 - topRow * EGM96.INTERVAL.degrees;
182+
double lonLeft = leftCol * EGM96.INTERVAL.degrees;
183+
184+
// Ensure the top latitude matches our expected latitude
185+
// This shows that the method has determined a row and column to query the dataset that corresponds with our
186+
// latitude value
187+
assertEquals("longitude matches after index conversion", longitude.degrees + 360d, lonLeft, DELTA);
188+
189+
// Using the confirmed longitude value from above (via the topRow and leftCol values), find the actual node
190+
// value
191+
double lonGridPointOffset = egm96.gePostOffset(topRow, leftCol) / 100d; // the other method converts to meters
192+
// Use the interpolation method to determine the offset value
193+
double lonOffset = egm96.getOffset(latitude, longitude);
194+
195+
// Ensure that they are equal
196+
assertEquals("interpolated matches actual longitude", lonGridPointOffset, lonOffset, DELTA);
197+
}
198+
199+
/**
200+
* Tests the determination of the EGM offset value using a longitude value between grid points. This method will use
201+
* the bilinear interpolation method to calculate the offset value.
202+
*/
203+
@Test
204+
public void testGetOffset_HorizontalInterpolationPoint() throws IOException
205+
{
206+
207+
// New EGM96 instance
208+
EGM96 egm96 = new EGM96(OFFSETS_FILE_PATH);
209+
// The EGM96 data has an interval of 0.25 degrees in both the horizontal and vertical dimensions. To test and
210+
// demonstrate the fractional value being determined is correct, this setup will isolate a horizontal
211+
// interpolation by landing on the vertical grid points (e.g. latitudes ending with .0, 0.25, 0.5, 0.75)
212+
Angle latitude = Angle.fromDegrees(38.75);
213+
// This is a baseline longitude for the left grid point of our testing points, it is closer to the left grid
214+
// point
215+
Angle longitude = Angle.fromDegrees(-104.9);
216+
217+
// Find the row and column values using the identical method the getOffset method uses
218+
// Code below is directly copied from the getOffset method accept where static class references were added
219+
double lat = latitude.degrees;
220+
double lon = longitude.degrees >= 0 ? longitude.degrees : longitude.degrees + 360;
221+
int topRow = (int) ((90 - lat) / EGM96.INTERVAL.degrees);
222+
if (lat <= -90)
223+
topRow = EGM96.NUM_ROWS - 2;
224+
int bottomRow = topRow + 1;
225+
// Note that the number of columns does not repeat the column at 0 longitude, so we must force the right
226+
// column to 0 for any longitude that's less than one interval from 360, and force the left column to the
227+
// last column of the grid.
228+
int leftCol = (int) (lon / EGM96.INTERVAL.degrees);
229+
int rightCol = leftCol + 1;
230+
if (lon >= 360 - EGM96.INTERVAL.degrees)
231+
{
232+
leftCol = EGM96.NUM_COLS - 1;
233+
rightCol = 0;
234+
}
235+
// Determine the functions determination of the top lat and left lon
236+
double latTop = 90 - topRow * EGM96.INTERVAL.degrees;
237+
double lonLeft = leftCol * EGM96.INTERVAL.degrees;
238+
// Need the right longitude for our own interpolation testing
239+
double lonRight = rightCol * EGM96.INTERVAL.degrees;
240+
241+
// Find the offset values of the top and bottom grid points
242+
double leftOffsetValue = egm96.gePostOffset(topRow, leftCol) / 100d;
243+
double rightOffsetValue = egm96.gePostOffset(topRow, rightCol) / 100d;
244+
245+
// Ensure the left longitude matches our expected longitude
246+
// This shows that the method has determined a row and column to query the dataset that corresponds with our
247+
// longitude value
248+
assertEquals("left longitude matches after index conversion", -105d + 360d, lonLeft, DELTA);
249+
assertEquals("right longitude matches after index conversion", -104.75 + 360d, lonRight, DELTA);
250+
251+
// The calculated EGM96 offset
252+
double lonOffset = egm96.getOffset(latitude, longitude);
253+
double manuallyCalculatedH = (lon - lonLeft) / (lonRight - lonLeft);
254+
double manuallyCalculatedInterpolationValue = (rightOffsetValue - leftOffsetValue) * manuallyCalculatedH
255+
+ leftOffsetValue;
256+
257+
// Ensure that they are equal
258+
assertEquals("interpolated matches actual longitude", manuallyCalculatedInterpolationValue, lonOffset, DELTA);
259+
}
260+
}

0 commit comments

Comments
 (0)