pyspectra: A CLI for Engineers to Request Seismic Data
Motivation
Constructing another command-line interface (CLI), but this time it’s in Python.
I wanted to pick up more CLI development experience with languages other than
Go. The argparse
module is a CLI framework that ships with Python’s standard
library and is the framework I selected to build this CLI.
Similar to the last CLI I wrote, my goals were simple:
- Formatting the JSON server response into a readable table to be output directly to the terminal.
- Take advantage of piping (
|
) and redirection (>
) functionality where available for further processing or writing to a file. In other words, it should be able to preserve the response data in its JSON format to be inspected with CLI tools likejq
or be able to emit portions of data in another format such as CSV.
An Aside: A Short (the Shortest…) Segue about Seismic Design
Earthquake engineering is a subset within structural engineering. Structures, especially ones along the west coast of the United States, experience earthquakes and are a design consideration for new construction as well as existing construction (e.g. when seismic retrofits are required by building code). A common task structural engineers regularly need to know is an estimate of a structure’s lateral load demand known as the base shear. Using a procedure such as the Equivalent Lateral Force method, the base shear is calculated as a proportion of the building’s weight and without going into too much theory, is an estimate of the lateral load that a building may experience during a design event. For example, the simplest representation of base shear for a short-period building can be reduced to the following equation:
$$ V = \frac{S_{DS}}{\frac{R}{I_e}}W $$
where:
$$ \begin{align*} V &= \text{Base shear} \newline S_{DS} &= \text{Design spectral acceleration} \newline R &= \text{Response modification factor} \newline I_e &= \text{Importance factor} \newline W &= \text{Building weight} \end{align*} $$
While $R$, $I_e$, and $W$ are fixed quantities defined in the ASCE 7 documents, the value of $S_{DS}$ (along with other site specific data) is the value that I want programmatic access from my terminal. There’s more theory behind how these values are developed in a field called Probabilistic Seismic Hazard Analysis with its origins rooted in a paper written by C. Allin Cornell.
API Observations
The USGS provides an API to access seismic design data (e.g. $S_{DS}$) programmatically and is the basis for browser-based tools such as the ASCE Hazard Tool. With each new release of a design standard, comes an expansion of USGS’s JSON server response and an increase in the number of seismic data parameters reported from a client’s request. For fun, I wanted to understand how many more parameters are returned from all similar standards so I wrote a Node.js script that recursively searches through the JSON responses and builds a composition JavaScript object of the properties that are common to all design standards (a set intersection) as well as the set union of all properties for each of the standards.
First Iteration
The first iteration of the CLI was a barebones use of argparse
and some
of its basic features. How the positional and optional arguments work, help and
usage messages, and etc. With argparse
, a help/usage message comes free with the
instantiation of an ArgumentParser
object:
$ python3 webspectra.py -h
usage: webspectra.py [-h] [-o {json,table}] std latitude longitude risk_category site_class
A command utility to obtain USGS seismic accelerations.
positional arguments:
std Design Standard
latitude Site Latitude
longitude Site Longitude
risk_category Risk Category
site_class Site Class
options:
-h, --help show this help message and exit
-o {json,table}, --output {json,table}
output format
Here’s an example run:
$ python3 webspectra.py asce7-22 47.61 -122.33 II D
------------------------------------------------
| Input | Values |
------------------------------------------------
| date | 2024-09-20T05:39:23.850Z |
| referenceDocument | ASCE7-22 |
| latitude | 47.61 |
| longitude | -122.33 |
| riskCategory | II |
| siteClass | D |
| title | Example |
------------------------------------------------
-----------------------------------------------------------------
| Parameters | Values | Description |
-----------------------------------------------------------------
| pgam | 0.68 | Site Mod. PGA |
| sms | 1.72 | Site-Modified SS (Sms) |
| sm1 | 1.37 | Site-Modified S1 (Sm1) |
| sds | 1.14 | Design Spectra (Sds) | # <- here's S_ds
| sd1 | 0.92 | Design Spectra (Sd1) |
| sdc | D | Seismic Design Category via Sds and Sd1 |
| ss | 1.56 | Short Spectra (SS) MCEr |
| s1 | 0.65 | S1 MCEr |
| tl | 6 | Long-period Transition, Tl (seconds) |
| cv | None | Vertical Coefficient (Cv) |
-----------------------------------------------------------------
-----------------------------------------------------------------------------------------------
| Metadata | Values | Description |
-----------------------------------------------------------------------------------------------
| vs30 | 260 | Shear Wave Velocity (m/s) |
| modelVersion | v5.0.x | Version of USGS Hazard Model |
| pgadFloor | 0.53 | Det. Lower Limit Peak Ground Accel. (g) |
| pgadPercentileFactor | 1 | Factor To Achieve Target Ground Motion (PGA) |
| spatialInterpolationMethod | linearloglinear | Interpolation Method Used |
-----------------------------------------------------------------------------------------------
This first pass is still limited. At the time, I didn’t spend much time on building functionality for returning series data to users with the first iteration. Also, this is probably more than enough output information to display for someone that wants a snapshot of seismic hazard data at a specific site and should probably be reduced for viewing on narrower terminal widths and for brevity. The functionality of the CLI works, though I haven’t done extensive testing though to see where things might break.
Take Two: Adding Functionality - Creating subcommands with argparse
Turning the design standards into subcommands using subparsers was intended to address the following issues:
- Some design standards have differing ranges of choices for Site Classes such as the differences between the ASCE 7-05 and ASCE 7-22 design standards. ASCE 7-05 ranges from A through E, while ASCE 7-22 also ranged from classes A through E with more granularity of Site Class with hybrid values such as AB, BC, DE.
- Each design standard has different available choices for series spectrum data (i.e. design spectrum, MCEr spectrum, multi-period spectrum, etc.). For example, the ASCE 7-05 has only two kinds of spectra available while the ASCE 7-22 can have as many as eight for a site.
Fundamentally, each subcommand for a standard is going to look pretty similar between each other. The general structure of the subcommands can be reduced to the following form:
# ...having previously instantiated ArgumentParser and a subparser object...
asce722_parser = subparsers.add_parser(
'asce7-22',
aliases=['asce'],
description="Seismic design data from the ASCE 7-22 design standard.",
help='ASCE 7-22 help'
)
asce722_parser.add_argument(**latitude_arg_template)
asce722_parser.add_argument(**longitude_arg_template)
asce722_parser.add_argument(**risk_category_arg_template)
asce722_parser.add_argument(**site_class_arg_template_three)
asce722_parser.add_argument(
'-s', '--spectrum',
help='request spectrum only',
choices=['two-design', 'two-mcer',
'vert-design', 'vert-mcer',
'multi-design', 'multi-mcer',
'risk-targeted', '84th',
],
)
asce722_parser.set_defaults(std='asce7-22', fn=make_request)
# some more design standard subparsers here...
args = parser.parse_args()
args.fn(args) # calls the make_request function
The output format still functions the same as the first iteration for querying data at a particular site.
When addressing how best to retrieve spectrum data, I defaulted the output to
emit CSV that way if an engineer was using the CLI, they could redirect it to a
file and then use that file to import it into Excel or literally copy and paste
the output from the terminal into Excel (I mean… let’s face it, knowing the
engineering profession, it’s going end up in Excel one way or another…) and
perform some ’text-to-column’ manipulation to make the series data usable. It
also outputs the series data into JSON in case I’m interested in further
manipulating the data with jq
or another JSON utility.
Below, is an example run for retrieving a design spectrum formatted into JSON to the terminal:
$ python3 webspectra.py -o json asce7-22 -s two-design 47.61 -122.33 II D
{"periods": [0, 0.025, 0.05, 0.1, 0.15, # ... 134 points omitted for brevity ...
6.8, 6.85, 6.9, 6.95, 7],
"ordinates": [0.46, 0.56, 0.67, 0.89, 1.1, # ... 134 points omitted for brevity ...
0.12, 0.12, 0.12, 0.11, 0.11]}
The same call, but emitting CSV:
$ python3 webspectra.py asce7-22 -s two-design 47.61 -122.33 II D
0,0.46
0.025,0.56
0.05,0.67
0.1,0.89
0.15,1.1
# ... 134 points omitted for brevity ...
6.8,0.12
6.85,0.12
6.9,0.12
6.95,0.11
7,0.11
Future Directions
There’s more room for improvement with this CLI. The CLI currently handles seismic design data only. Adding more hazard load cases such as snow load and wind speeds are certainly a possibility. Also, creating a more compact way to display queried data is another option. For now, I think this is a fine stopping point.