spc-cpp

A C++23 client for NOAA Storm Prediction Center data: convective and
fire-weather outlooks, mesoscale discussions, watches, and storm reports. It
reads NOAA’s ArcGIS services,
SPC’s GeoJSON files, and the
Iowa Environmental Mesonet archive.
None of them needs an API key.
#include "spc/spc.hpp"
#include <iostream>
int main() {
const spc::ArcGISClient client;
const spc::Result<spc::CategoricalOutlookPayload> day1 = client.query_categorical(1);
if (!day1) {
std::cerr << day1.error().message << "\n";
return 1;
}
std::cout << "Day 1 bands: " << day1->features.size() << "\n";
for (const spc::OutlookFeature& band : day1->features) {
if (spc::point_in_feature(-97.34, 37.69, band)) { // lon, lat of Wichita
std::cout << "Wichita is in " << band.label << "\n";
}
}
}
Add it to your project
- CMake 3.31 or newer. Ubuntu 24.04 ships 3.28, so run
sudo snap install cmake --classic.
- GCC 13+, Clang 18+ with libc++ or 19+ with libstdc++, or Apple Clang 17+.
- libcurl 7.85 or newer with its headers:
libcurl4-openssl-dev on Ubuntu,
built into macOS.
CI builds on Linux, macOS for Apple silicon and Intel, and Windows with MSVC.
On Windows, install libcurl through the included vcpkg manifest and configure
with -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake. Build
with --config Release and run tests with ctest --test-dir build -C Release.
Save the example above as main.cpp and this as CMakeLists.txt:
cmake_minimum_required(VERSION 3.31)
project(myapp LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(spc_cpp
GIT_REPOSITORY https://github.com/Reddimus/spc-cpp.git
GIT_TAG v0.4.7
)
FetchContent_MakeAvailable(spc_cpp)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE spc::spc)
Build and run it:
cmake -S . -B build
cmake --build build --parallel
./build/myapp
Day 1 is today’s outlook. The program prints lines like Wichita is in SLGT,
SPC’s code for a slight risk. On a quiet day, Day 1 bands: 0 is a normal
answer, not an error.
On a Mac the build is native to your chip. To run on older Macs, add
-DCMAKE_OSX_DEPLOYMENT_TARGET=13.4 or later. For one binary that also runs
on Intel, add -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"; it needs the macOS
libcurl, because Homebrew’s curl is arm64 only.
If your own installed library links spc::spc, add set(SPC_INSTALL ON)
before FetchContent_MakeAvailable. Otherwise your install(EXPORT) fails.
Or, from a clone of spc-cpp, install it and use find_package:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DSPC_BUILD_TESTS=OFF
cmake --build build --parallel
cmake --install build --prefix /your/prefix
find_package(spc 0.4 REQUIRED)
target_link_libraries(myapp PRIVATE spc::spc)
Configure your project with -DCMAKE_PREFIX_PATH=/your/prefix. Before 1.0,
a minor release can break the API, so find_package(spc 0.4) accepts only
0.4.x.
Which call to use
| Product |
Call |
| Categorical outlook, days 1-3 |
ArcGISClient::query_categorical(day) |
| Probabilistic outlook, days 1-3 |
ArcGISClient::query_probabilistic(day, hazard) |
| Conditional intensity, days 1-3 |
ArcGISClient::query_conditional_intensity(day, hazard) |
| Severe outlook, days 4-8 |
ArcGISClient::query_day4_8(day) |
| Fire weather, days 1-8 |
ArcGISClient::query_fire_weather(day) |
| Active mesoscale discussions |
ArcGISClient::query_active_md() |
| Any other layer on those ArcGIS services, as raw JSON |
ArcGISClient::query_layer(service, layer_id, params) |
| Watches, now or at a past time |
ArchiveClient::watches(timestamp) |
| Storm reports between two times |
ArchiveClient::storm_reports(start, end, office) |
hazard is "tornado", "hail", or "wind" on days 1 and 2, and
"severe" on day 3. include/spc/api.hpp documents
every call’s arguments. As a fallback, StaticFeedClient::day_categorical,
day_probabilistic, and day4_8 read the same outlooks from SPC’s GeoJSON
files.
The Iowa Environmental Mesonet is a free service, so each ArchiveClient
allows a burst of 2 requests, then 1 per second, retries included. Share one
instance so the limit holds across your program.
Handling errors
Client methods return spc::Result<T>, an alias for
std::expected<T, spc::Error>. They do not throw for network or data
problems. Error::code says what went wrong:
| Code |
Meaning |
NetworkError |
Connection, TLS, or timeout failure, after retries. |
RateLimited |
HTTP 429 or 503 after retries, or ArchiveClient’s own limit would wait more than 5 s. |
ServerError |
HTTP 5xx other than 503, after retries, or an ArcGIS query that never finished paging. |
InvalidRequest |
An unsupported day or hazard, HTTP 400, an ArcGIS error such as a bad layer ID, or a response over 64 MiB. |
NotFound |
A URL or ArcGIS service that no longer exists. Report it as a bug. |
ParseError |
Malformed JSON or Esri geometry that cannot be converted. |
FeedUnavailable |
Only StaticFeedClient returns it. SPC has not issued the product, as with day 1 probabilities overnight. Treat it as no data. ArcGISClient returns an empty result instead. |
Unknown |
Any other HTTP status, such as 422 for a malformed ArchiveClient timestamp. Error::http_status holds it. |
The offline parse_* functions, such as spc::parse_categorical(body, day),
throw std::runtime_error on malformed JSON or Esri geometry that cannot be converted.
Coordinates are longitude/latitude in degrees. Esri Web Mercator (3857, 102100,
or 102113) is converted; 4326 and 4269 are preserved. Data without a spatial
reference is assumed to use degrees. Request outSR=4326 for other Esri systems.
Projected edges are subdivided to preserve their boundaries within one metre.
Malformed Esri points and invalid projected coordinates, including discarded
holes, raise a parse error.
A payload needing more than one million added points also raises a parse error.
Settings and threads
spc::ClientConfig sets the user agent, the 15 s timeout, and the 64 MiB
response limit. The default user agent carries spc-cpp’s contact address, so
set one with yours:
const spc::ArcGISClient client{spc::ClientConfig{.user_agent = "myapp/1.0 ([email protected])"}};
Calls block until they finish, retry and rate-limit waits included. Clients
that use the default HttpClient are safe to share between threads. To swap
the network stack or return canned test responses, subclass
spc::HttpTransport. Its one method, get, receives a full URL and must be
thread-safe:
struct MyTransport final : spc::HttpTransport {
spc::Result<spc::HttpResponse> get(std::string_view url) const override;
};
const spc::ArcGISClient client{std::make_shared<MyTransport>()};
Examples
From a clone, make run-<name> builds and runs an example, such
as make run-fire_weather. parse_outlook needs no network.
Contributing
See CONTRIBUTING.md. Report security issues through
SECURITY.md.
License
MIT
评论