|
| 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