Skip to content

Add terrain correction in spherical coordinates to the User Guide - #700

Open
manduinca wants to merge 2 commits into
fatiando:mainfrom
manduinca:docs/spherical-terrain-correction
Open

Add terrain correction in spherical coordinates to the User Guide#700
manduinca wants to merge 2 commits into
fatiando:mainfrom
manduinca:docs/spherical-terrain-correction

Conversation

@manduinca

@manduinca manduinca commented Jul 30, 2026

Copy link
Copy Markdown

Closes #353

Adds a section to the topographic correction guide showing how to forward model the topographic masses with a tesseroid layer in geocentric spherical coordinates, mirroring the existing Cartesian workflow: build the layer from the topography grid using radii, express the observation points consistently with the layer (geodetic latitude + geocentric_radius(latitude) + height), compute the topography-free gravity disturbance, and compare the terrain effects of both models.

Since the observations sit on the terrain, 619 of the 3877 stations fall below the top of their tesseroid (the DEM averages each cell; median deficit 15 m, worst 392 m), and tesseroid_gravity requires computation points outside the tesseroids. Following the review, the section keeps the measured heights and lowers the top of the tesseroids that contain those stations instead of moving the stations. A note explains the geodetic vs. geocentric latitude assumption.

The comparison shows the tesseroids give a terrain effect about 4.5 mGal (3-4%) larger than the prisms, uniformly across the region. A check with constant topography (details in the comments) confirms it's the curvature of the Earth, not the projection: the difference grows linearly with the extent of the grid and matches the Bullard B correction.

I couldn't build the docs locally (missing GMT binary for PyGMT), but the code of the whole page runs end-to-end (~1 min, tesseroid forward included).

Add a section to the topographic correction guide showing how to forward
model the topographic masses with a tesseroid layer in geocentric
spherical coordinates: build the layer from the topography grid using
radii, express the observation points consistently with the layer, clamp
their radii so every computation point falls outside the tesseroids, and
compute the topography-free gravity disturbance.

@santisoler santisoler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @manduinca for taking the time to work on this. This tutorial is looking really good!

On one side I'm thinking if it would be better to move this section into its own .rst document, as a subsection of the Topographic Correction. But on the other side, that would require repeating some of the code in the Topographic Correction (like downloading the data and the DEM, etc). Also, I think it might be nice to include a plot showing the differences between the terrain correction performed with prisms and tesseroids.

For this particular case where the region spans only a few degrees, I'm not sure we'll be able to see a significant difference between them. That would lead me to consider if it is better to use a different example with a larger region, where the curvature of the Earth introduces a significant difference in the results.

I guess I'm thinking while I type... But in summary, could you add an extra plot with the differences between terrain correction using prisms and tesseroids?

I left a few comments below as well. Let me know what do you think. And thanks again for the effort!

Comment on lines +396 to +406
top = (
tesseroids.top.rolling(longitude=3, latitude=3, center=True, min_periods=1)
.max()
.sel(
longitude=xr.DataArray(data.longitude),
latitude=xr.DataArray(data.latitude),
method="nearest",
)
.values
)
radius = np.maximum(radius, top)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused about this piece of code. I'm not fully understanding why it's necessary to perform the windowing operation. Getting the closest top for each observation point should be enough to build the top array, right?

I'm also thinking if isn't better to adjust the top surface of the tesseroids rather than shifting the observation points. In gravity surveys the height measurements are usually very accurate, carried out through differential GNSS. While the DEMs are models that average the terrain elevation within a region given by the resolution of the model. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I see your point on observation points that fall in the boundaries of tesseroids. My guess is that in those cases the points will never be internal to the tesseroid: they'll always lie on a face or an edge. So I'm not sure if those are going to be problematic.

We can obtain the radii of the surface of the reference ellipsoid at each
latitude with :meth:`boule.Ellipsoid.geocentric_radius` and add the
topographic heights to them:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add an admonition here explaining that by assigning the ellipsoidal latitude as coordinates for the tesseroids, we are assuming that there's not significant difference between them and the geocentric spherical latitude.

One alternative way is to convert the ellipsoidal coordinates into geocentric spherical and build the tesseroids from them. For this particular tutorial I'd avoid doing so, because that introduces issues like regridding the topography in geocentric spherical, since a regular grid in ellipsoidal coordinates does not translate into a regular grid in geocentric spherical coordinates.

Let me know if you need any help with this.

@manduinca

Copy link
Copy Markdown
Author

Thanks for the detailed review @santisoler. All three points addressed; here is what I found while doing it.

Comparison plot. Added, and the difference is visible even in this region: the tesseroids give a terrain effect about 4.5 mGal larger than the prisms, quite uniformly (rms 5.2 mGal over effects of 12 to 242 mGal, so 3-4%). To make sure it was the curvature and not the Mercator projection, I ran a check with constant topography of 1500 m on the same 1 arc-minute grid, one station at the center, growing the extent of the grid, with prisms projected both with Mercator and with a local azimuthal equidistant projection:

half-width of the grid tesseroids prisms (Mercator) prisms (equidistant) difference
0.5° (~56 km) 166.60 165.84 165.77 -0.8
1.0° (~111 km) 168.43 166.89 166.88 -1.5
2.0° (~222 km) 170.52 167.42 167.42 -3.1
3.0° (~333 km) 172.27 167.60 167.60 -4.7
4.5° (~500 km) 174.73 167.71 167.71 -7.0

(mGal; the infinite Bouguer slab is 167.95.) The prisms converge to the slab, the tesseroids grow past it linearly with the extent of the grid, and the projection makes no difference. Interpolated to the Hayford radius (166.7 km) the difference is +1.55 mGal, which is LaFehr's Bullard B value for 1500 m, so I mention that in a hint. Given that, I kept this region rather than a larger example: the effect is already there and it avoids downloading a second dataset.

Latitude note. Added as a .. note:: right after building the layer, along the lines you suggested (why we assume it and why regridding would be needed to avoid it).

Clamp. You were right to question it. The nearest top is enough for almost all stations, but not for the ones sitting exactly on a cell boundary, and this dataset has many of those: 40 stations in longitude and 36 in latitude have coordinates that are round fractions of a degree, right on the edges of the 1 arc-minute grid. The problem is floating point rather than geometry: the boundaries of neighboring tesseroids differ by ~7e-15 degrees, so a station at longitude 31.575 is inside the tesseroid whose east edge is 31.574999999999996 while sel(method="nearest") assigns it to the neighbor, and the check in tesseroid_gravity (which uses strict inequalities, so a point on a face is fine) raises. The 3x3 window solved that by brute force and, as you noticed, moved far too much: 2586 stations lifted, up to 670 m.

I went with your suggestion instead: the measured heights stay, and the top of every tesseroid containing a station below it is lowered to the radius of that station (629 tesseroids, median 15 m, worst 392 m). To cover the boundary stations I look up the cell on either side of each station, shifting its coordinates by 1e-9 degrees, and lower both. No station is moved and the forward runs without errors. A note points out that the prism model has the same stations below the DEM, just without complaining.

On splitting the section into its own page: I left it here because of the duplication you mentioned (download, cropping, projection), but happy to move it if you prefer.

The whole page runs end-to-end for me (with PyGMT stubbed out, since I don't have GMT locally), about a minute in total.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include a terrain correction in spherical coordinates in the User Guide

2 participants