Skip to content

Commit bfed63d

Browse files
gshiromagmgunter
authored andcommitted
Update the DEMInterpolator to handle geographic dateline crossing (lon +/- 180) (#810)
* update the DEMInterpolator to handle geographic dateline crossing * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * add geoid (EGM96) files for DEMInterp test * update DEMInterpolator for DEM with longitude values greater than 180; add docstrings * update DEMInterpolator to test for dateline crossing * update DEMInterpolator to test for dateline crossing (2) * update check for maxX greater than maxY * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * add include <cmath> to DEMInterpolator test * add more comments about the DEMInterpolator dateline crossing test * declare variables as soon as needed * update DEMInterpolator dateline crossing test docstrings * add description of the new geoid raster (EGM96) datasets * update DEMInterpolator to handle longitudes outside of the [-180, 360] range; update DEMInterpolator test to test longitude cases outside of the [-180, 360] range * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * compare geoid -180/180 values to geoid 0/360 values * remove DEMInterpolator test header * add check for longitude range smaller or equal to 360 deg + 2 pixels * add more comments explaining flag_dateline in DEMInterpolator * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * update DEMInterpolator comments * update checking for DEMInterpolator user coordinates minX and maxX outside of the [-180, 360] longitude range * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * rename some variables to snake case convention * add new tests to DEMInterpolator dateline crossing tests * add more comments explaining handling of the dateline crossing by the DEMInterpolator; remove useless condition within if statement * remove unnecessary variable next_pixel_idx and improve comments * add check for DEM position differences before and after dateline less than a threshold (1e-5) * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update cxx/isce3/geometry/DEMInterpolator.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * update header file DEMInterpolator.h variable names to match DEMInterpolator.cpp * fix if statement in interpolateXY() * update comment for wrapped_next_pixel_idx_ideal * use int rather than size_t for lat/lon pixel indexing * fix duplicated auto assignment to ret * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * Update tests/cxx/isce3/geometry/dem/dem.cpp Co-authored-by: Geoffrey M Gunter <[email protected]> * update checks for `lon_idx` and `lon_idx_0_to_360` within array dimensions and add comments * address case in which user-provided min_x and max_x coincide with DEM longitude range and add unitests to test for these cases * substitute dateline crossing with DEM file discontinuity * add check for positive DEM pixel spacing in the X-/longitude direction Co-authored-by: Geoffrey M Gunter <[email protected]>
1 parent 76660df commit bfed63d

File tree

8 files changed

+656
-53
lines changed

8 files changed

+656
-53
lines changed

cxx/isce3/error/ErrorCode.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ std::string getErrorString(ErrorCode status)
1616
case ErrorCode::OrbitInterpUnknownMethod:
1717
return "unexpected orbit interpolation method";
1818
case ErrorCode::OutOfBoundsDem: return "out of bounds DEM";
19+
case ErrorCode::InvalidDem: return "invalid DEM";
1920
case ErrorCode::FailedToConverge:
2021
return "optimization routine failed to converge within the maximum "
2122
"number of iterations";

cxx/isce3/error/ErrorCode.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum class ErrorCode {
1111
OrbitInterpDomainError,
1212
OrbitInterpUnknownMethod,
1313
OutOfBoundsDem,
14+
InvalidDem,
1415
FailedToConverge,
1516
WrongLookSide,
1617
OutOfBoundsLookup,

cxx/isce3/geometry/DEMInterpolator.cpp

Lines changed: 380 additions & 47 deletions
Large diffs are not rendered by default.

cxx/isce3/geometry/DEMInterpolator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class isce3::geometry::DEMInterpolator {
5757

5858
/** Read in subset of data from a DEM with a supported projection */
5959
isce3::error::ErrorCode loadDEM(isce3::io::Raster& demRaster,
60-
double minX, double maxX, double minY, double maxY);
60+
double min_x, double max_x, double min_y, double max_y);
6161

6262
/** Read in entire DEM with a supported projection */
6363
void loadDEM(isce3::io::Raster &demRaster);

tests/cxx/isce3/geometry/dem/dem.cpp

Lines changed: 259 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
//-*- C++ -*-
2-
//-*- coding: utf-8 -*-
3-
//
4-
// Author: Bryan Riel
5-
// Copyright 2018
6-
//
72

83
#include <iostream>
94
#include <string>
5+
#include <cmath>
106
#include <gtest/gtest.h>
117

128
// isce3::core
@@ -49,6 +45,264 @@ TEST(DEMTest, MethodConstruct) {
4945
}
5046
}
5147

48+
49+
void test_dateline(double x0, double xf, double y0, double yf,
50+
double sampling_factor) {
51+
/*
52+
This function uses two geoid rasters (EGM96) to test the DEM
53+
interpolator for dateline crossing. The geoid raster "egm96_15.gtx"
54+
is geolocated over geographic coordinates with longitude range
55+
varying from -180 to 180 deg whereas the geoid raster
56+
"egm96_15_lon_0_360.gtx" is the "shifted" version of "egm96_15.gtx"
57+
geolocated over geographic coordinates with longitude from 0 to 360 deg.
58+
We compare the interpolated values from DEMInterpolator methods
59+
interpolateLonLat() and interpolateXY().
60+
The `sampling_factor` determines the lat/lon step w.r.t. the geoid
61+
rasters pixel size.
62+
We compare the values from the four sources:
63+
1 - Non-interpolated "egm96_15.gtx" values;
64+
2 - Non-interpolated "egm96_15_lon_0_360.gtx" values;
65+
3 - DEMInterpolator "egm96_15.gtx" values;
66+
4 - DEMInterpolator "egm96_15_lon_0_360.gtx" values.
67+
The non-interpolated values 1 and 2 are only compared to the
68+
DEMInterpolator values 3 and 4 for the points located in the
69+
center of the pixel (where no interpolation is required).
70+
*/
71+
72+
std::cout << "testing DEM interpolator with bbox:" << std::endl;
73+
std::cout << " start lon: " << x0 << std::endl;
74+
std::cout << " end lon: " << xf << std::endl;
75+
std::cout << " start lat: " << y0 << std::endl;
76+
std::cout << " end lat: " << yf << std::endl;
77+
78+
// create geoid raster objects
79+
isce3::io::Raster raster_geoid(TESTDATA_DIR "egm96_15.gtx");
80+
isce3::io::Raster raster_geoid_0_to_360(TESTDATA_DIR "egm96_15_lon_0_360.gtx");
81+
82+
// setup geoid DEM interpolators
83+
isce3::geometry::DEMInterpolator dem_interp_geoid(0,
84+
isce3::core::dataInterpMethod::BIQUINTIC_METHOD);
85+
auto ret_1 = dem_interp_geoid.loadDEM(raster_geoid, x0, xf, y0, yf);
86+
if (ret_1 != isce3::error::ErrorCode::Success) {
87+
throw std::runtime_error("loadDEM failed");
88+
}
89+
90+
isce3::geometry::DEMInterpolator dem_interp_geoid_0_to_360(0,
91+
isce3::core::dataInterpMethod::BIQUINTIC_METHOD);
92+
auto ret_2 = dem_interp_geoid_0_to_360.loadDEM(raster_geoid_0_to_360, x0, xf, y0,
93+
yf);
94+
if (ret_2 != isce3::error::ErrorCode::Success) {
95+
throw std::runtime_error("loadDEM failed");
96+
}
97+
98+
// read geoid raster
99+
auto width = raster_geoid.width();
100+
auto length = raster_geoid.length();
101+
auto dx = raster_geoid.dx();
102+
auto dy = raster_geoid.dy();
103+
ASSERT_GT(dx, 0);
104+
ASSERT_LT(dy, 0);
105+
106+
isce3::core::Matrix<float> geoid_array(length, width);
107+
raster_geoid.getBlock(geoid_array.data(), 0, 0, width, length, 1);
108+
109+
isce3::core::Matrix<float> geoid_array_0_to_360(length, width);
110+
raster_geoid_0_to_360.getBlock(geoid_array_0_to_360.data(), 0, 0,
111+
width, length, 1);
112+
113+
const int interpolation_margin = 5;
114+
const double err_tolerance = 1e-6;
115+
116+
// Fix `xf` to be used by for loop
117+
if (xf < x0) {
118+
xf += 360;
119+
}
120+
121+
for (double lat = yf + interpolation_margin * dy;
122+
lat > y0 - interpolation_margin * dy;
123+
lat += sampling_factor * dy) {
124+
125+
int lat_idx = (lat - 90) / dy;
126+
127+
for (double lon = x0 + interpolation_margin * dx;
128+
lon < xf - interpolation_margin * dx;
129+
lon += sampling_factor * dx) {
130+
131+
/*
132+
The non-interpolated values `geoid_array` and
133+
`geoid_array_0_to_360` are only compared to the DEMInterpolator
134+
values 3 and 4 for the points located in the center of the pixel
135+
(where no interpolation is required). This condition is
136+
represented by the flag `flag_check_arrays`.
137+
*/
138+
bool flag_check_arrays =
139+
(std::fmod(lat, dy) == 0.0) &&
140+
(std::fmod(lon, dx) == 0.0) &&
141+
lat_idx < length;
142+
143+
int lon_idx;
144+
if (flag_check_arrays) {
145+
146+
// Wrap `lon` to longitude range [-180, 360]
147+
double lon_wrapped = lon;
148+
if (lon > 360 || lon < -360) {
149+
lon_wrapped = std::fmod(lon, 360);
150+
}
151+
152+
if (lon < -180 - dx) {
153+
lon_wrapped += 360;
154+
}
155+
156+
if (lon_wrapped < 180) {
157+
lon_idx = (lon_wrapped + 180) / dx;
158+
} else {
159+
lon_idx = (lon_wrapped - 180) / dx;
160+
}
161+
162+
int lon_idx_0_to_360;
163+
if (lon_wrapped >= 0) {
164+
lon_idx_0_to_360 = lon_wrapped / dx;
165+
} else {
166+
lon_idx_0_to_360 = (lon_wrapped + 360) / dx;
167+
}
168+
169+
/* Check if indexes `lon_idx` and `lon_idx_0_to_360`
170+
are within arrays' dimensions. If not, set
171+
`flag_check_arrays` to false.
172+
*/
173+
if (lon_idx >= width || lon_idx_0_to_360 >= width) {
174+
flag_check_arrays = false;
175+
} else {
176+
// compare raster values (without interpolation)
177+
ASSERT_NEAR(geoid_array(lat_idx, lon_idx),
178+
geoid_array_0_to_360(lat_idx, lon_idx_0_to_360),
179+
err_tolerance);
180+
}
181+
}
182+
183+
// test interpolateLonLat()
184+
double deg_to_rad_factor = M_PI / 180.0;
185+
double geoid_value = dem_interp_geoid.interpolateLonLat(
186+
lon * deg_to_rad_factor, lat * deg_to_rad_factor);
187+
188+
double geoid_0_to_360_value =
189+
dem_interp_geoid_0_to_360.interpolateLonLat(
190+
lon * deg_to_rad_factor, lat * deg_to_rad_factor);
191+
192+
if (flag_check_arrays) {
193+
ASSERT_NEAR(geoid_array(lat_idx, lon_idx),
194+
geoid_value, err_tolerance);
195+
ASSERT_NEAR(geoid_array(
196+
lat_idx, lon_idx), geoid_0_to_360_value,
197+
err_tolerance);
198+
}
199+
200+
// test interpolateXY()
201+
double geoid_value_xy = dem_interp_geoid.interpolateXY(lon, lat);
202+
double geoid_0_to_360_value_xy =
203+
dem_interp_geoid_0_to_360.interpolateXY(lon, lat);
204+
205+
ASSERT_NEAR(geoid_value, geoid_0_to_360_value, err_tolerance);
206+
ASSERT_NEAR(
207+
geoid_value_xy, geoid_0_to_360_value_xy, err_tolerance);
208+
}
209+
}
210+
}
211+
212+
213+
TEST(DEMTest, DatelineCrossing) {
214+
215+
std::cout << "dateline crossing test" << std::endl;
216+
217+
double y0 = -90, yf = 90;
218+
219+
// offset test wrapping of longitude coordinates around 360 degrees
220+
for (int offset = -360; offset <= 360; offset += 360) {
221+
std::cout << "offset:" << offset << std::endl;
222+
223+
// global with longitude range [-180, 180]
224+
double sampling_factor = 4;
225+
double x0 = -180 + offset;
226+
double xf = 180 + offset;
227+
test_dateline(x0, xf, y0, yf, sampling_factor);
228+
229+
// global with longitude range [0, 360]
230+
x0 = 0 + offset;
231+
xf = 360 + offset;
232+
test_dateline(x0, xf, y0, yf, sampling_factor);
233+
234+
// longitude values within ]-180, 0[
235+
x0 = -170 + offset;
236+
xf = -10 + offset;
237+
test_dateline(x0, xf, y0, yf, sampling_factor);
238+
239+
// longitude values within ]0, 180[
240+
x0 = 10 + offset;
241+
xf = 170 + offset;
242+
test_dateline(x0, xf, y0, yf, sampling_factor);
243+
244+
// longitude values within ]180, 360[
245+
x0 = 190 + offset;
246+
xf = 350 + offset;
247+
test_dateline(x0, xf, y0, yf, sampling_factor);
248+
249+
// dateline 179.5 to 180.5
250+
sampling_factor = 0.05;
251+
x0 = 179.5 + offset;
252+
xf = 180.5 + offset;
253+
test_dateline(x0, xf, y0, yf, sampling_factor);
254+
255+
// test DEM right edges
256+
x0 = 179 + offset;
257+
xf = 179.875 + offset;
258+
test_dateline(x0, xf, y0, yf, sampling_factor);
259+
260+
x0 = 359 + offset;
261+
xf = 359.875 + offset;
262+
test_dateline(x0, xf, y0, yf, sampling_factor);
263+
264+
// test DEM right edges (global)
265+
sampling_factor = 8;
266+
x0 = -180 + offset;
267+
xf = 179.875 + offset;
268+
test_dateline(x0, xf, y0, yf, sampling_factor);
269+
270+
x0 = 0 + offset;
271+
xf = 359.875 + offset;
272+
test_dateline(x0, xf, y0, yf, sampling_factor);
273+
274+
// test DEM left edges
275+
sampling_factor = 0.05;
276+
x0 = -180.125 + offset;
277+
xf = -179 + offset;
278+
test_dateline(x0, xf, y0, yf, sampling_factor);
279+
280+
x0 = -0.125 + offset;
281+
xf = 1.0 + offset;
282+
test_dateline(x0, xf, y0, yf, sampling_factor);
283+
284+
// test DEM left edges (global)
285+
sampling_factor = 8;
286+
x0 = -180.125 + offset;
287+
xf = 179.875 + offset;
288+
test_dateline(x0, xf, y0, yf, sampling_factor);
289+
290+
x0 = -0.125 + offset;
291+
xf = 359.875 + offset;
292+
test_dateline(x0, xf, y0, yf, sampling_factor);
293+
294+
if (offset == 0) {
295+
296+
// dateline 179.5 to -179.5
297+
x0 = 179.5 + offset;
298+
xf = -179.5 + offset;
299+
test_dateline(x0, xf, y0, yf, sampling_factor);
300+
301+
}
302+
}
303+
}
304+
305+
52306
int main(int argc, char * argv[]) {
53307
testing::InitGoogleTest(&argc, argv);
54308
return RUN_ALL_TESTS();

tests/data/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ and then post-processed and converted into L0B product via its python utility to
116116
The total number of range lines is *70*. The total number of range bins is 28927.
117117
The Tx range lines types are of HPA, LNA, and BYPASS. BYPASS range line interval is 20.
118118

119+
## Geoid EGM96
120+
121+
- **egm96_15.gtx**
122+
The geoid raster "egm96_15.gtx" contains the global geoid EGM96 array
123+
sampled at 0.25 degrees in latitude and longitude. The raster
124+
is geolocated over geographic coordinates with longitude range
125+
varying from -180 to 180 degrees.
126+
127+
- **egm96_15_lon_0_360.gtx**
128+
The geoid raster "egm96_15_lon_0_360.gtx" is the "shifted" version of
129+
"egm96_15.gtx" geolocated over geographic coordinates with longitude
130+
from 0 to 360 deg.
131+
132+
119133
[1]: M. Shimada et al., "PALSAR Radiometric and Geometric Calibration",
120134
*IEEE Trans. Geosci. Remote Sens.*, pp. 3915-3932, December 2009.
121135

tests/data/egm96_15.gtx

3.96 MB
Binary file not shown.

tests/data/egm96_15_lon_0_360.gtx

3.96 MB
Binary file not shown.

0 commit comments

Comments
 (0)