{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
distance_mazimuthback_azimuthdistance_degreesvertical_distance_m
id1id2
smi:local/248839UU.CTU..HHE143559.550849161.740301342.0768131.2896172141.0
smi:local/248883UU.CTU..HHE143716.194014161.590945341.9304821.2910255911.0
smi:local/248887UU.CTU..HHE143999.566314161.346595341.6911521.2935705891.0
smi:local/248891UU.CTU..HHE143969.268983161.667028342.0057831.2932984971.0
smi:local/248882UU.CTU..HHE143480.660281161.650623341.9885561.2889093521.0
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
azimuthback_azimuthdistance_degreesdistance_kmvertical_distance_km
count408.00408.00408.00408.00408.00
mean201.06211.690.8291.245.22
std104.3798.060.3336.551.85
min9.1648.810.1618.261.70
25%126.47122.450.6370.463.69
50%163.87189.790.8290.855.50
75%293.44307.271.12124.366.25
max358.12344.771.29144.009.31
\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 }