Skip to content

Commit 76ad9f6

Browse files
authored
Add example for EQLHarmonicSpherical (fatiando#152)
Add example for `EQLHarmonicSpherical` using South Africa gravity dataset. Original data is downsampled to speed-up computations and then converted to spherical coordinates to fit the gridder. Then the data is predicted on a regular grid defined on geodetic coordinates by passing `boule.Ellipsoid.geodetic_to_spherical` as the `projection` argument.
1 parent 3e4e446 commit 76ad9f6

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

examples/eql/harmonic_spherical.py

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Gridding in spherical coordinates
3+
=================================
4+
5+
The curvature of the Earth must be taken into account when gridding and
6+
processing magnetic or gravity data on large regions. In these cases,
7+
projecting the data may introduce errors due to the distortions caused by the
8+
projection.
9+
10+
:class:`harmonica.EQLHarmonicSpherical` implements the equivalent layer
11+
technique in spherical coordinates. It has the same advantages as the Cartesian
12+
equivalent layer (:class:`harmonica.EQLHarmonic`) while taking into account the
13+
curvature of the Earth.
14+
"""
15+
import numpy as np
16+
import cartopy.crs as ccrs
17+
import matplotlib.pyplot as plt
18+
import boule as bl
19+
import verde as vd
20+
import harmonica as hm
21+
22+
23+
# Fetch the sample gravity data from South Africa
24+
data = hm.datasets.fetch_south_africa_gravity()
25+
26+
# Downsample the data using a blocked mean to speed-up the computations
27+
# for this example. This is preferred over simply discarding points to avoid
28+
# aliasing effects.
29+
blocked_mean = vd.BlockReduce(np.mean, spacing=0.2, drop_coords=False)
30+
(longitude, latitude, elevation), gravity_data = blocked_mean.filter(
31+
(data.longitude, data.latitude, data.elevation), data.gravity,
32+
)
33+
34+
# Compute gravity disturbance by removing the gravity of normal Earth
35+
ellipsoid = bl.WGS84
36+
gamma = ellipsoid.normal_gravity(latitude, height=elevation)
37+
gravity_disturbance = gravity_data - gamma
38+
39+
# Convert data coordinates from geodetic (longitude, latitude, height) to
40+
# spherical (longitude, spherical_latitude, radius).
41+
coordinates = ellipsoid.geodetic_to_spherical(longitude, latitude, elevation)
42+
43+
# Create the equivalent layer
44+
eql = hm.EQLHarmonicSpherical(damping=1e-3, relative_depth=10000)
45+
46+
# Fit the layer coefficients to the observed magnetic anomaly
47+
eql.fit(coordinates, gravity_disturbance)
48+
49+
# Evaluate the data fit by calculating an R² score against the observed data.
50+
# This is a measure of how well layer the fits the data NOT how good the
51+
# interpolation will be.
52+
print("R² score:", eql.score(coordinates, gravity_disturbance))
53+
54+
# Interpolate data on a regular grid with 0.2 degrees spacing defined on
55+
# geodetic coordinates. To do so we need to specify that we want coordinates to
56+
# be converted to spherical geocentric coordinates before the prediction is
57+
# carried out. This can be done though the "projection" argument.
58+
# The interpolation requires an extra coordinate (upward height). By passing in
59+
# 2500 m above the ellipsoid, we're effectively
60+
# upward-continuing the data (maximum height of observation points is 2400 m).
61+
# All the parameters passed to build the grid (region, spacing and
62+
# extra_coords) are in geodetic coordinates.
63+
region = vd.get_region((longitude, latitude))
64+
grid = eql.grid(
65+
region=region,
66+
spacing=0.2,
67+
extra_coords=2500,
68+
dims=["latitude", "longitude"],
69+
data_names=["gravity_disturbance"],
70+
projection=ellipsoid.geodetic_to_spherical,
71+
)
72+
73+
# Mask grid points too far from data points
74+
grid = vd.distance_mask(data_coordinates=coordinates, maxdist=0.5, grid=grid)
75+
76+
# Get the maximum absolute value between the original and gridded data so we
77+
# can use the same color scale for both plots and have 0 centered at the white
78+
# color.
79+
maxabs = vd.maxabs(gravity_disturbance, grid.gravity_disturbance.values)
80+
81+
# Plot observed and gridded gravity disturbance
82+
fig, (ax1, ax2) = plt.subplots(
83+
nrows=1,
84+
ncols=2,
85+
figsize=(10, 5),
86+
sharey=True,
87+
subplot_kw={"projection": ccrs.PlateCarree()},
88+
)
89+
ax1.coastlines()
90+
ax2.coastlines()
91+
gl = ax1.gridlines(crs=ccrs.PlateCarree(), draw_labels=True)
92+
gl.xlabels_top = False
93+
gl.ylabels_right = False
94+
gl = ax2.gridlines(crs=ccrs.PlateCarree(), draw_labels=True)
95+
gl.xlabels_top = False
96+
gl.ylabels_left = False
97+
gl.ylabels_right = False
98+
99+
tmp = ax1.scatter(
100+
longitude,
101+
latitude,
102+
c=gravity_disturbance,
103+
s=3,
104+
vmin=-maxabs,
105+
vmax=maxabs,
106+
cmap="seismic",
107+
)
108+
plt.colorbar(tmp, ax=ax1, label="mGal", pad=0.07, aspect=40, orientation="horizontal")
109+
ax1.set_extent(region, crs=ccrs.PlateCarree())
110+
111+
tmp = grid.gravity_disturbance.plot.pcolormesh(
112+
ax=ax2,
113+
vmin=-maxabs,
114+
vmax=maxabs,
115+
cmap="seismic",
116+
add_colorbar=False,
117+
add_labels=False,
118+
)
119+
plt.colorbar(tmp, ax=ax2, label="mGal", pad=0.07, aspect=40, orientation="horizontal")
120+
ax2.set_extent(region, crs=ccrs.PlateCarree())
121+
122+
plt.subplots_adjust(wspace=0.05, top=1, bottom=0, left=0.05, right=0.95)
123+
plt.show()

0 commit comments

Comments
 (0)