-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ground and airborne synthetic surveys (#120)
Add functions to create synthetic ground and airborne surveys based on the gravity dataset from South Africa and the total-field magnetic anomaly dataset from Great Britain. The synthetic surveys are samples from the original datasets that are rescaled to the desired region defined in geodetic coordinates.
- Loading branch information
1 parent
a780750
commit 4460807
Showing
5 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# pylint: disable=missing-docstring | ||
from .surveys import ground_survey, airborne_survey |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
""" | ||
Create synthetic surveys for gravity and magnetic observations | ||
""" | ||
from verde import get_region, inside | ||
from verde.coordinates import check_region | ||
|
||
from ..datasets import fetch_britain_magnetic, fetch_south_africa_gravity | ||
|
||
|
||
def airborne_survey(region=None, data_region=(-5.0, -4.0, 56.0, 56.5)): | ||
""" | ||
Create measurement locations for a synthetic airborne survey | ||
The observation points are sampled from the Great Britain total-field | ||
magnetic anomaly dataset (see | ||
:func:`harmonica.datasets.fetch_britain_magnetic`). A portion of the | ||
original survey is cut (*data_region*) and the coordinates may be scaled to | ||
the given *region*. | ||
Parameters | ||
---------- | ||
region : tuple or list (optional) | ||
Survey horizontal coordinates will be scaled to span this area. The | ||
boundaries must be passed in the following order: (``east``, ``west``, | ||
``south``, ``north``, ...), defined on a geodetic coordinate system and | ||
in degrees. Only the 4 horizontal boundaries are used. Subsequent | ||
boundaries will be ignored. If ``None``, the survey points won't be | ||
scaled. Default ``None``. | ||
data_region : tuple or list (optional) | ||
Subsection of the original Great Britain magnetic dataset that will be | ||
sampled. The boundaries must be passed in the following order: | ||
(``east``, ``west``, ``south``, ``north``, ...), defined on a geodetic | ||
coordinate system and in degrees. All subsequent boundaries will be | ||
ignored. | ||
Returns | ||
------- | ||
survey : :class:`pandas.DataFrame` | ||
Dataframe containing the coordinates of the observation points on | ||
a geodetic coordinate system. The :class:`pandas.DataFrame` will have | ||
the following columns: ``longitude``, ``latitude``, ``height``. | ||
Longitudes and latitudes are in degrees, and heights in meters. | ||
See also | ||
-------- | ||
datasets.fetch_britain_magnetic: | ||
Fetch total-field magnetic anomaly data of Great Britain. | ||
""" | ||
# Sanity checks for region and data_region | ||
if region is not None: | ||
check_region(region[:4]) | ||
check_region(data_region) | ||
# Fetch airborne magnetic survey from Great Britain | ||
survey = fetch_britain_magnetic() | ||
# Rename the "elevation" column to "height" and | ||
# keep only the longitude, latitude and height | ||
survey = survey.rename(columns={"altitude_m": "height"}).filter( | ||
["longitude", "latitude", "height"] | ||
) | ||
# Cut the survey into the data_region and scale it to the passed region | ||
survey = _cut_and_scale(survey, region, data_region) | ||
return survey | ||
|
||
|
||
def ground_survey(region=None, data_region=(13.60, 20.30, -24.20, -17.5)): | ||
""" | ||
Create measurement locations for a synthetic ground survey | ||
The observation points are sampled from the South Africa gravity dataset | ||
(see :func:`harmonica.datasets.fetch_south_africa_gravity`). Only a portion | ||
of the original survey is sampled and its region may be scaled to the | ||
passed ``region``. | ||
Parameters | ||
---------- | ||
region : tuple or list (optional) | ||
Region at which the survey points coordinates will be scaled. The | ||
boundaries must be passed in the following order: (``east``, ``west``, | ||
``south``, ``north``, ...), defined on a geodetic coordinate system and | ||
in degrees. All subsequent boundaries will be ignored. If ``None``, the | ||
survey points won't be scaled. Default ``None``. | ||
data_region : tuple or list (optional) | ||
Region where the original Great Britain magnetic dataset will be | ||
sampled. The boundaries must be passed in the following order: | ||
(``east``, ``west``, ``south``, ``north``, ...), defined on a geodetic | ||
coordinate system and in degrees. All subsequent boundaries will be | ||
ignored. | ||
Returns | ||
------- | ||
survey : :class:`pandas.DataFrame` | ||
Dataframe containing the coordinates of the observation points on | ||
a geodetic coordinate system. The :class:`pandas.DataFrame` will have | ||
the following columns: ``longitude``, ``latitude``, ``height``. | ||
Longitudes and latitudes are in degrees, and heights in meters. | ||
See also | ||
-------- | ||
datasets.fetch_south_africa_gravity | ||
""" | ||
# Sanity checks for region and data_region | ||
if region is not None: | ||
check_region(region[:4]) | ||
check_region(data_region) | ||
# Fetch ground gravity survey from South Africa | ||
survey = fetch_south_africa_gravity() | ||
# Rename the "elevation" column to "height" and | ||
# keep only the longitude, latitude and height | ||
survey = survey.rename(columns={"elevation": "height"}).filter( | ||
["longitude", "latitude", "height"] | ||
) | ||
# Cut the survey into the data_region and scale it to the passed region | ||
survey = _cut_and_scale(survey, region, data_region) | ||
return survey | ||
|
||
|
||
def _cut_and_scale(survey, region, data_region): | ||
""" | ||
Cut a subsection from the original survey and scale it to the given region | ||
Parameters | ||
---------- | ||
survey : :class:`pandas.DataFrame` | ||
Original survey as a :class:`pandas.DataFrame` containing the following | ||
columns: ``longitude``, ``latitude`` and ``height``. | ||
region : tuple or list (optional) | ||
Region to which the survey points coordinates will be scaled. The | ||
boundaries must be passed in the following order: (``east``, ``west``, | ||
``south``, ``north``, ...), defined on a geodetic coordinate system and | ||
in degrees. All subsequent boundaries will be ignored. If ``None``, the | ||
survey points won't be scaled. | ||
data_region : tuple or list (optional) | ||
Region where the original Great Britain magnetic dataset will be | ||
sampled. The boundaries must be passed in the following order: | ||
(``east``, ``west``, ``south``, ``north``, ...), defined on a geodetic | ||
coordinate system and in degrees. All subsequent boundaries will be | ||
ignored. | ||
Returns | ||
------- | ||
survey : :class:`pandas.DataFrame` | ||
Dataframe containing the coordinates of the observation points on | ||
a geodetic coordinate system. Longitudes and latitudes are in degrees, | ||
and heights in meters. | ||
""" | ||
# Cut the data into the data_region | ||
inside_points = inside((survey.longitude, survey.latitude), data_region) | ||
survey = survey[inside_points].copy() | ||
# Scale survey coordinates to the passed region | ||
if region is not None: | ||
w, e, s, n = region[:4] | ||
longitude_min, longitude_max, latitude_min, latitude_max = get_region( | ||
(survey.longitude, survey.latitude) | ||
) | ||
survey["longitude"] = (e - w) / (longitude_max - longitude_min) * ( | ||
survey.longitude - longitude_min | ||
) + w | ||
survey["latitude"] = (n - s) / (latitude_max - latitude_min) * ( | ||
survey.latitude - latitude_min | ||
) + s | ||
return survey |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
""" | ||
Test functions to create synthetic surveys | ||
""" | ||
import numpy.testing as npt | ||
from ..synthetic import airborne_survey, ground_survey | ||
|
||
|
||
def test_ground_survey(): | ||
""" | ||
Test if the sythetic ground survey returns the expected survey | ||
""" | ||
# Expected region for the default data_region | ||
expected_region = (13.60833, 20.28333, -24.2, -17.50333) | ||
survey = ground_survey() | ||
assert set(survey.columns) == set(["longitude", "latitude", "height"]) | ||
assert survey.longitude.size == 963 | ||
npt.assert_allclose(survey.longitude.min(), expected_region[0]) | ||
npt.assert_allclose(survey.longitude.max(), expected_region[1]) | ||
npt.assert_allclose(survey.latitude.min(), expected_region[2]) | ||
npt.assert_allclose(survey.latitude.max(), expected_region[3]) | ||
npt.assert_allclose(survey.height.min(), 0.0) | ||
npt.assert_allclose(survey.height.max(), 2052.2) | ||
|
||
|
||
def test_scale_ground_survey(): | ||
""" | ||
Test if the synthetic ground survey returns the expected survey after scaled | ||
""" | ||
region = (-10.1, 9.7, -20.3, -10.5) # a random region to scale the survey | ||
survey = ground_survey(region=region) | ||
assert set(survey.columns) == set(["longitude", "latitude", "height"]) | ||
assert survey.longitude.size == 963 | ||
npt.assert_allclose(survey.longitude.min(), region[0]) | ||
npt.assert_allclose(survey.longitude.max(), region[1]) | ||
npt.assert_allclose(survey.latitude.min(), region[2]) | ||
npt.assert_allclose(survey.latitude.max(), region[3]) | ||
npt.assert_allclose(survey.height.min(), 0.0) | ||
npt.assert_allclose(survey.height.max(), 2052.2) | ||
|
||
|
||
def test_airborne_survey(): | ||
""" | ||
Test if the synthetic airborne survey returns the expected survey | ||
""" | ||
# Expected region for the default data_region | ||
expected_region = (-4.99975, -4.00003, 56.00011, 56.49997) | ||
survey = airborne_survey() | ||
assert set(survey.columns) == set(["longitude", "latitude", "height"]) | ||
assert survey.longitude.size == 5673 | ||
npt.assert_allclose(survey.longitude.min(), expected_region[0]) | ||
npt.assert_allclose(survey.longitude.max(), expected_region[1]) | ||
npt.assert_allclose(survey.latitude.min(), expected_region[2]) | ||
npt.assert_allclose(survey.latitude.max(), expected_region[3]) | ||
npt.assert_allclose(survey.height.min(), 359.0) | ||
npt.assert_allclose(survey.height.max(), 1255.0) | ||
|
||
|
||
def test_scale_airborne_survey(): | ||
""" | ||
Test if the synthetic airborne survey returns the expected survey after scaled | ||
""" | ||
region = (-10.1, 9.7, -20.3, -10.5) # a random region to scale the survey | ||
survey = airborne_survey(region=region) | ||
assert set(survey.columns) == set(["longitude", "latitude", "height"]) | ||
assert survey.longitude.size == 5673 | ||
npt.assert_allclose(survey.longitude.min(), region[0]) | ||
npt.assert_allclose(survey.longitude.max(), region[1]) | ||
npt.assert_allclose(survey.latitude.min(), region[2]) | ||
npt.assert_allclose(survey.latitude.max(), region[3]) | ||
npt.assert_allclose(survey.height.min(), 359.0) | ||
npt.assert_allclose(survey.height.max(), 1255.0) | ||
|
||
|
||
def test_data_region_ground_survey(): | ||
""" | ||
Test if ground survey is changed against a different data_region | ||
""" | ||
data_region = (10, 30, -30, -12) # a bigger data_region than the default one | ||
region = (-10.1, 9.7, -20.3, -10.5) # a random region to scale the survey | ||
survey = ground_survey(region=region, data_region=data_region) | ||
assert survey.longitude.size > 963 | ||
npt.assert_allclose(survey.longitude.min(), region[0]) | ||
npt.assert_allclose(survey.longitude.max(), region[1]) | ||
npt.assert_allclose(survey.latitude.min(), region[2]) | ||
npt.assert_allclose(survey.latitude.max(), region[3]) | ||
assert survey.height.min() <= 0.0 | ||
assert survey.height.max() >= 2052.2 | ||
|
||
|
||
def test_data_region_airborne_survey(): | ||
""" | ||
Test if a different cut_region produces a different airborne survey | ||
""" | ||
data_region = (-7, -2, 53, 58) # a bigger data_region than the default one | ||
region = (-10.1, 9.7, -20.3, -10.5) # a random region to scale the survey | ||
survey = airborne_survey(region=region, data_region=data_region) | ||
assert survey.longitude.size > 5673 | ||
npt.assert_allclose(survey.longitude.min(), region[0]) | ||
npt.assert_allclose(survey.longitude.max(), region[1]) | ||
npt.assert_allclose(survey.latitude.min(), region[2]) | ||
npt.assert_allclose(survey.latitude.max(), region[3]) | ||
assert survey.height.min() <= 359.0 | ||
assert survey.height.max() >= 1255.0 |