{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Miscellaneous Utilities\n",
"The follow demonstrates some miscellaneous utilities included in ObsPlus.\n",
"\n",
"## Geodetics\n",
"It is often necessary to calculate geometric parameters (distance, azimuth, etc.) for pairs of entities in two different groups. For example, distance from each event in a catalog to each receiver in an inventory. ObsPlus provides a simple class for completing this task."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:27.430385Z",
"iopub.status.busy": "2024-02-28T22:20:27.429876Z",
"iopub.status.idle": "2024-02-28T22:20:30.385886Z",
"shell.execute_reply": "2024-02-28T22:20:30.385372Z"
}
},
"outputs": [],
"source": [
"# Load the catalog and inventory from the crandall dataset\n",
"import obsplus\n",
"from obsplus.utils.geodetics import SpatialCalculator\n",
"\n",
"\n",
"crandall = obsplus.load_dataset('crandall_test')\n",
"cat = crandall.event_client.get_events()\n",
"inv = crandall.station_client.get_stations()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.388472Z",
"iopub.status.busy": "2024-02-28T22:20:30.388263Z",
"iopub.status.idle": "2024-02-28T22:20:30.390937Z",
"shell.execute_reply": "2024-02-28T22:20:30.390474Z"
}
},
"outputs": [],
"source": [
"# init a SpatialCalculator instance (defaults to Earth's params)\n",
"spatial_calc = SpatialCalculator()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.393026Z",
"iopub.status.busy": "2024-02-28T22:20:30.392685Z",
"iopub.status.idle": "2024-02-28T22:20:30.529086Z",
"shell.execute_reply": "2024-02-28T22:20:30.528515Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/usr/share/miniconda3/envs/test/lib/python3.10/site-packages/obspy/taup/taup_geo.py:105: UserWarning: Assuming spherical planet when calculating epicentral distance. Install the Python module 'geographiclib' to solve this.\n",
" warnings.warn(msg)\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" | \n",
" distance_m | \n",
" azimuth | \n",
" back_azimuth | \n",
" distance_degrees | \n",
" vertical_distance_m | \n",
"
\n",
" \n",
" id1 | \n",
" id2 | \n",
" | \n",
" | \n",
" | \n",
" | \n",
" | \n",
"
\n",
" \n",
" \n",
" \n",
" smi:local/248839 | \n",
" UU.CTU..HHE | \n",
" 143559.550849 | \n",
" 161.740301 | \n",
" 342.076813 | \n",
" 1.289617 | \n",
" 2141.0 | \n",
"
\n",
" \n",
" smi:local/248883 | \n",
" UU.CTU..HHE | \n",
" 143716.194014 | \n",
" 161.590945 | \n",
" 341.930482 | \n",
" 1.291025 | \n",
" 5911.0 | \n",
"
\n",
" \n",
" smi:local/248887 | \n",
" UU.CTU..HHE | \n",
" 143999.566314 | \n",
" 161.346595 | \n",
" 341.691152 | \n",
" 1.293570 | \n",
" 5891.0 | \n",
"
\n",
" \n",
" smi:local/248891 | \n",
" UU.CTU..HHE | \n",
" 143969.268983 | \n",
" 161.667028 | \n",
" 342.005783 | \n",
" 1.293298 | \n",
" 4971.0 | \n",
"
\n",
" \n",
" smi:local/248882 | \n",
" UU.CTU..HHE | \n",
" 143480.660281 | \n",
" 161.650623 | \n",
" 341.988556 | \n",
" 1.288909 | \n",
" 3521.0 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" distance_m azimuth back_azimuth \\\n",
"id1 id2 \n",
"smi:local/248839 UU.CTU..HHE 143559.550849 161.740301 342.076813 \n",
"smi:local/248883 UU.CTU..HHE 143716.194014 161.590945 341.930482 \n",
"smi:local/248887 UU.CTU..HHE 143999.566314 161.346595 341.691152 \n",
"smi:local/248891 UU.CTU..HHE 143969.268983 161.667028 342.005783 \n",
"smi:local/248882 UU.CTU..HHE 143480.660281 161.650623 341.988556 \n",
"\n",
" distance_degrees vertical_distance_m \n",
"id1 id2 \n",
"smi:local/248839 UU.CTU..HHE 1.289617 2141.0 \n",
"smi:local/248883 UU.CTU..HHE 1.291025 5911.0 \n",
"smi:local/248887 UU.CTU..HHE 1.293570 5891.0 \n",
"smi:local/248891 UU.CTU..HHE 1.293298 4971.0 \n",
"smi:local/248882 UU.CTU..HHE 1.288909 3521.0 "
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# create distance dataframe\n",
"df = spatial_calc(entity_1=cat, entity_2=inv)\n",
"df.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since a multi-index is used it provides a fairly intuitive way to look up particular event-channel pairs using a tuple of (event_id, seed_id) in conjunction with the `.loc` DataFrame attribute like so:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.556660Z",
"iopub.status.busy": "2024-02-28T22:20:30.556237Z",
"iopub.status.idle": "2024-02-28T22:20:30.560315Z",
"shell.execute_reply": "2024-02-28T22:20:30.559846Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"distance_m 70463.858017\n",
"azimuth 150.303413\n",
"back_azimuth 330.562735\n",
"distance_degrees 0.632988\n",
"vertical_distance_m 2319.000000\n",
"Name: (smi:local/248839, UU.MPU..HHZ), dtype: float64\n"
]
}
],
"source": [
"event_id = str(cat[0].resource_id)\n",
"seed_id = 'UU.MPU..HHZ'\n",
"\n",
"print(df.loc[(event_id, seed_id)])"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.562561Z",
"iopub.status.busy": "2024-02-28T22:20:30.562228Z",
"iopub.status.idle": "2024-02-28T22:20:30.565165Z",
"shell.execute_reply": "2024-02-28T22:20:30.564598Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"150.30341278123922\n"
]
}
],
"source": [
"# or just get a particular parameter\n",
"print(df.loc[(event_id, seed_id), 'azimuth'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The distances can be converted to km and the distrtibutions can be described."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.567420Z",
"iopub.status.busy": "2024-02-28T22:20:30.567071Z",
"iopub.status.idle": "2024-02-28T22:20:30.582492Z",
"shell.execute_reply": "2024-02-28T22:20:30.582005Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" azimuth | \n",
" back_azimuth | \n",
" distance_degrees | \n",
" distance_km | \n",
" vertical_distance_km | \n",
"
\n",
" \n",
" \n",
" \n",
" count | \n",
" 408.00 | \n",
" 408.00 | \n",
" 408.00 | \n",
" 408.00 | \n",
" 408.00 | \n",
"
\n",
" \n",
" mean | \n",
" 201.06 | \n",
" 211.69 | \n",
" 0.82 | \n",
" 91.24 | \n",
" 5.22 | \n",
"
\n",
" \n",
" std | \n",
" 104.37 | \n",
" 98.06 | \n",
" 0.33 | \n",
" 36.55 | \n",
" 1.85 | \n",
"
\n",
" \n",
" min | \n",
" 9.16 | \n",
" 48.81 | \n",
" 0.16 | \n",
" 18.26 | \n",
" 1.70 | \n",
"
\n",
" \n",
" 25% | \n",
" 126.47 | \n",
" 122.45 | \n",
" 0.63 | \n",
" 70.46 | \n",
" 3.69 | \n",
"
\n",
" \n",
" 50% | \n",
" 163.87 | \n",
" 189.79 | \n",
" 0.82 | \n",
" 90.85 | \n",
" 5.50 | \n",
"
\n",
" \n",
" 75% | \n",
" 293.44 | \n",
" 307.27 | \n",
" 1.12 | \n",
" 124.36 | \n",
" 6.25 | \n",
"
\n",
" \n",
" max | \n",
" 358.12 | \n",
" 344.77 | \n",
" 1.29 | \n",
" 144.00 | \n",
" 9.31 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" azimuth back_azimuth distance_degrees distance_km \\\n",
"count 408.00 408.00 408.00 408.00 \n",
"mean 201.06 211.69 0.82 91.24 \n",
"std 104.37 98.06 0.33 36.55 \n",
"min 9.16 48.81 0.16 18.26 \n",
"25% 126.47 122.45 0.63 70.46 \n",
"50% 163.87 189.79 0.82 90.85 \n",
"75% 293.44 307.27 1.12 124.36 \n",
"max 358.12 344.77 1.29 144.00 \n",
"\n",
" vertical_distance_km \n",
"count 408.00 \n",
"mean 5.22 \n",
"std 1.85 \n",
"min 1.70 \n",
"25% 3.69 \n",
"50% 5.50 \n",
"75% 6.25 \n",
"max 9.31 "
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Convert add km columns and delete m columns\n",
"m_columns = [x for x in df.columns if x.endswith('_m')]\n",
"km_columns = [x.replace('_m', '_km') for x in m_columns]\n",
"\n",
"df_km = (\n",
" df.assign(**{x: df[y] / 1000. for x,y in zip(km_columns, m_columns)}).\n",
" drop(columns=m_columns)\n",
")\n",
"\n",
"# Calculate stats for source reseiver distances\n",
"df_km.describe().round(decimals=2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Time\n",
"\n",
"Working with dates/times can be frustrating, especially since (as of 2020) ObsPy, numpy, and pandas all use slightly different methods for working with time. ObsPlus provides some utilities to make things a little easier."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.584874Z",
"iopub.status.busy": "2024-02-28T22:20:30.584496Z",
"iopub.status.idle": "2024-02-28T22:20:30.587406Z",
"shell.execute_reply": "2024-02-28T22:20:30.586840Z"
}
},
"outputs": [],
"source": [
"from contextlib import suppress\n",
"\n",
"import numpy as np\n",
"import obspy\n",
"import obsplus\n",
"from obsplus.utils import to_datetime64, to_timedelta64, to_utc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Note:** All ObsPlus datafames use numpy/pandas datatypes. "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.589619Z",
"iopub.status.busy": "2024-02-28T22:20:30.589292Z",
"iopub.status.idle": "2024-02-28T22:20:30.612607Z",
"shell.execute_reply": "2024-02-28T22:20:30.612082Z"
}
},
"outputs": [],
"source": [
"df = obsplus.events_to_df(obspy.read_events())"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.614810Z",
"iopub.status.busy": "2024-02-28T22:20:30.614460Z",
"iopub.status.idle": "2024-02-28T22:20:30.618887Z",
"shell.execute_reply": "2024-02-28T22:20:30.618359Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0 2012-04-04 14:21:42.300\n",
"1 2012-04-04 14:18:37.000\n",
"2 2012-04-04 14:08:46.000\n",
"Name: time, dtype: datetime64[ns]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df['time']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Numpy and ObsPy time differences"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"One difference between numpy's `datatime64` and ObsPy's `UTCDateTime` is how offsets are applied. For ObsPy, numbers are simply taken as seconds, but numpy requires explicitly using `timedeta64` instances."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.621199Z",
"iopub.status.busy": "2024-02-28T22:20:30.620868Z",
"iopub.status.idle": "2024-02-28T22:20:30.623461Z",
"shell.execute_reply": "2024-02-28T22:20:30.622933Z"
}
},
"outputs": [],
"source": [
"time_str = '2020-01-03T11:00:00'\n",
"utc = obspy.UTCDateTime(time_str)\n",
"dt64 = np.datetime64(time_str, 'ns')"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.625681Z",
"iopub.status.busy": "2024-02-28T22:20:30.625349Z",
"iopub.status.idle": "2024-02-28T22:20:30.628062Z",
"shell.execute_reply": "2024-02-28T22:20:30.627550Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2020-01-03T11:00:00.500000Z\n"
]
}
],
"source": [
"# add 1/2 second to UTCDateTime\n",
"utc2 = utc + 0.5\n",
"print(utc2)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.630264Z",
"iopub.status.busy": "2024-02-28T22:20:30.629936Z",
"iopub.status.idle": "2024-02-28T22:20:30.633226Z",
"shell.execute_reply": "2024-02-28T22:20:30.632646Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ufunc 'add' cannot use operands with types dtype('\n",
"\n",
"**Note**: `datetime64` arrays are much more efficient in terms of memory usage and computational efficiency than arrays of `UTCDateTime` *objects*.\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you prefer not to manually define `timedelta64` to perform offsets, `to_timedelta64` simply converts a real number to an offset in seconds."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2024-02-28T22:20:30.656909Z",
"iopub.status.busy": "2024-02-28T22:20:30.656732Z",
"iopub.status.idle": "2024-02-28T22:20:30.660560Z",
"shell.execute_reply": "2024-02-28T22:20:30.660018Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"numpy.timedelta64(3255000000,'ns')"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"to_timedelta64(3.255)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 4
}