-
Notifications
You must be signed in to change notification settings - Fork 91
Add terrain correction in spherical coordinates to the User Guide #700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -319,6 +319,134 @@ topography. | |
| fig.colorbar(cmap=True, frame=["af", "x+lTopography", "y+lmeters"]) | ||
| fig.show() | ||
|
|
||
|
|
||
| Terrain correction in spherical coordinates | ||
| ------------------------------------------- | ||
|
|
||
| So far we computed the terrain effect by projecting the topography grid and | ||
| the observation points to plain Cartesian coordinates and approximating the | ||
| topographic masses with rectangular prisms. | ||
| On regional to global scales the curvature of the Earth cannot be neglected: | ||
| the projection distorts the geometry of the topographic masses and the | ||
| computed terrain effect accumulates errors. | ||
| In such cases we can forward model the topographic masses directly in | ||
| geocentric spherical coordinates using tesseroids (spherical prisms), which | ||
| take the curvature of the Earth into account. | ||
|
|
||
| We can build a model of the topographic masses through the | ||
| :func:`harmonica.tesseroid_layer` function. | ||
| Unlike :func:`harmonica.prism_layer`, its ``surface`` and ``reference`` | ||
| arguments must be passed as **radii** measured from the center of the Earth, | ||
| not as heights above a reference level. | ||
| 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: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| import boule as bl | ||
|
|
||
| ellipsoid = bl.WGS84 | ||
|
|
||
| longitude, latitude = np.meshgrid(topography.longitude, topography.latitude) | ||
| reference = ellipsoid.geocentric_radius(latitude) | ||
| surface = reference + topography.values | ||
|
|
||
| We will assign the same densities we used for the layer of prisms and define | ||
| the layer of tesseroids: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| density = np.where(topography.values >= 0, 2670, 1040 - 2670) | ||
|
|
||
| tesseroids = hm.tesseroid_layer( | ||
| coordinates=(topography.longitude, topography.latitude), | ||
| surface=surface, | ||
| reference=reference, | ||
| properties={"density": density}, | ||
| ) | ||
| tesseroids | ||
|
|
||
| The radial coordinate of the observation points must be expressed in the same | ||
| way as the boundaries of the layer: as radii from the center of the Earth. | ||
| We will compute them the same way we defined the ``surface`` of the layer, by | ||
| adding the observation heights to the geocentric radius of the ellipsoid at | ||
| each latitude. | ||
| This keeps the observation points consistent with the model of the topographic | ||
| masses: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| radius = ellipsoid.geocentric_radius(data.latitude) + data.height_geometric_m | ||
|
|
||
| Tesseroid forward modelling requires every computation point to be located | ||
| outside of the tesseroids. | ||
| Since our observations were taken on the terrain surface, some of them can | ||
| fall slightly below the top boundary of the tesseroid that contains them: the | ||
| tops of the tesseroids are given by the topography grid, whose values don't | ||
| exactly coincide with the observation heights. | ||
| We can make sure every observation point is located on or above the top of its | ||
| tesseroid by clamping their radii. | ||
| We will clamp against the highest top among the neighboring tesseroids, so | ||
| that observation points falling exactly on the boundary between two tesseroids | ||
| are safely lifted as well: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| 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) | ||
|
Comment on lines
+396
to
+406
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
|
||
| Now we can compute the terrain effect through the | ||
| :meth:`harmonica.DatasetAccessorTesseroidLayer.gravity` method: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| coordinates_sph = (data.longitude, data.latitude, radius) | ||
| terrain_effect_spherical = tesseroids.tesseroid_layer.gravity( | ||
| coordinates_sph, field="g_z" | ||
| ) | ||
|
|
||
| And obtain a topography-free gravity disturbance that takes the curvature of | ||
| the Earth into account: | ||
|
|
||
| .. jupyter-execute:: | ||
|
|
||
| topo_free_disturbance_spherical = ( | ||
| data.gravity_disturbance_mgal - terrain_effect_spherical | ||
| ) | ||
|
|
||
| cpt_lims = vd.minmax(topo_free_disturbance_spherical) | ||
|
|
||
| fig = pygmt.Figure() | ||
| pygmt.makecpt(cmap="viridis", series=cpt_lims) | ||
| fig.plot( | ||
| x=data.longitude, | ||
| y=data.latitude, | ||
| fill=topo_free_disturbance_spherical, | ||
| cmap=True, | ||
| style="c3p", | ||
| projection="M15c", | ||
| frame=['ag', 'WSen+ggray'], | ||
| ) | ||
| fig.colorbar( | ||
| cmap=True, | ||
| frame=[ | ||
| "a50f25", | ||
| "x+lTopography-free gravity disturbance (tesseroids)", | ||
| "y+lmGal", | ||
| ], | ||
| ) | ||
| fig.show() | ||
|
|
||
| ---- | ||
|
|
||
| .. grid:: 2 | ||
|
|
||
There was a problem hiding this comment.
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.