C++23 SDK for the NCEI (National Centers for Environmental Information) climate data APIs.
Access decades of historical weather data — temperature, precipitation, wind, pressure, and more — from 100+ global datasets. Supports both the CDO Web Services v2 API and the newer Access Data Service API.
#include "ncei/ncei.hpp"
#include <iostream>
int main() {
ncei::CDOClient::Config config;
config.token = "YOUR_CDO_TOKEN"; // from ncdc.noaa.gov/cdo-web/token
ncei::CDOClient client(std::move(config));
ncei::GetDataParams params;
params.dataset_id = "GHCND";
params.start_date = "2024-01-01";
params.end_date = "2024-01-07";
params.station_id = "GHCND:USW00013874";
params.data_type_ids = {{"TMAX", "TMIN"}};
ncei::Result<std::vector<ncei::DataRecord>> data = client.get_data_all(params);
if (!data) {
std::cerr << data.error().message << "\n";
return 1;
}
for (const auto& record : *data) {
std::cout << record.date << " " << record.datatype
<< " = " << record.value << "\n";
}
}#include "ncei/ncei.hpp"
#include <iostream>
int main() {
ncei::DataServiceClient::Config config;
ncei::DataServiceClient client(std::move(config));
ncei::DataRequestParams params;
params.dataset = "daily-summaries";
params.stations = {{"USW00013874"}};
params.start_date = "2024-01-01";
params.end_date = "2024-12-31";
params.data_types = {{"TMAX", "TMIN", "PRCP"}};
params.format = ncei::ResponseFormat::JSON;
ncei::Result<ncei::DataPointCollection> data = client.get_data(params);
if (!data) {
std::cerr << data.error().message << "\n";
return 1;
}
for (const auto& point : data->records) {
std::optional<double> tmax = point.get_double("TMAX");
if (tmax) {
std::cout << point.date << " TMAX=" << *tmax << "\n";
}
}
}- Two API clients: CDOClient (legacy, token auth) + DataServiceClient (newer, no auth)
- C++23 with
std::expected<T, Error>— no exceptions - Auto-pagination: CDOClient fetches all pages transparently
- Auto-date-splitting: Multi-year queries automatically split into 1-year chunks (CDO API limit)
- Multi-format parsing: CSV, JSON, SSV parsed into typed
DataPointCollection - Rate limiting: Token bucket (5 req/sec) + daily counter (10K/day) for CDO API
- Retry with backoff: Configurable exponential backoff with jitter
- Null-safe JSON: Custom helpers prevent crashes on null API values
- 166 unit tests with fixture data
- C++23 compiler (GCC 13+, Clang 16+)
- CMake 3.20+
- libcurl
make build # Release build
make test # Run tests
make lint # Check formatting
make format # Format code
make coverage # Code coverage report (requires lcov)| Option | Default | Description |
|---|---|---|
NCEI_BUILD_TESTS |
ON |
Build unit tests |
NCEI_BUILD_EXAMPLES |
ON |
Build examples |
NCEI_ENABLE_LTO |
ON |
Link-time optimization |
NCEI_ENABLE_NETCDF |
OFF |
NetCDF format support (requires libnetcdf) |
NCEI_ENABLE_SANITIZERS |
OFF |
ASan + UBSan |
include(FetchContent)
FetchContent_Declare(ncei-cpp
GIT_REPOSITORY https://github.com/Reddimus/ncei-cpp.git
GIT_TAG v0.3.0
)
FetchContent_MakeAvailable(ncei-cpp)
target_link_libraries(myapp PRIVATE ncei)Or after installation:
find_package(ncei CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE ncei::ncei)graph LR
ncei_core["ncei_core<br/><small>error, rate_limit, retry<br/>date_range, csv_parser</small>"]
ncei_http["ncei_http<br/><small>libcurl HTTP client</small>"]
ncei_models["ncei_models<br/><small>CDO models (7)<br/>Data Service models (3)</small>"]
ncei_cdo["ncei_cdo<br/><small>CDOClient<br/>7 endpoints + auto-pagination</small>"]
ncei_data["ncei_data<br/><small>DataServiceClient<br/>multi-format parsing</small>"]
ncei["ncei<br/><small>INTERFACE</small>"]
ncei_core --> ncei_http
ncei_core --> ncei_models
ncei_http --> ncei_cdo
ncei_http --> ncei_data
ncei_models --> ncei_cdo
ncei_models --> ncei_data
ncei_cdo --> ncei
ncei_data --> ncei
sequenceDiagram
participant App
participant CDOClient
participant RateLimiter
participant HttpClient
participant CDO API
App->>CDOClient: get_data_all(3-year range)
CDOClient->>CDOClient: split_date_range(365 days)
loop Each 1-year chunk
loop Each page (offset 0, 1000, ...)
CDOClient->>RateLimiter: acquire()
RateLimiter-->>CDOClient: token granted
CDOClient->>HttpClient: GET /data?...
HttpClient->>CDO API: HTTP GET + token header
CDO API-->>HttpClient: JSON response
HttpClient-->>CDOClient: HttpResponse
CDOClient->>CDOClient: parse CDOResponse<DataRecord>
end
end
CDOClient-->>App: Result<vector<DataRecord>>
| Endpoint | Methods |
|---|---|
| Datasets | get_datasets(), get_dataset(id) |
| Data Categories | get_data_categories(), get_data_category(id) |
| Data Types | get_data_types(), get_data_type(id) |
| Location Categories | get_location_categories(), get_location_category(id) |
| Locations | get_locations(), get_location(id) |
| Stations | get_stations(), get_station(id) |
| Data | get_data(params), get_data_all(params) |
| Endpoint | Methods |
|---|---|
| Data (data/v1) | get_data(params), get_data_raw(params) |
| Metadata (support/v3) | get_dataset_metadata(id) |
GHCND (daily), GSOM (monthly), GSOY (yearly), daily-summaries, global-hourly, global-summary-of-the-month, local-climatological-data, normals-daily, normals-monthly, and 90+ more.
export NCEI_CDO_TOKEN="your_token_here"
make run-basic_cdo_usage # List datasets
make run-historical_temperature # Daily temps for a station
make run-multi_year_data # Auto-splitting demo
make run-format_comparison # CSV vs JSON (no auth)
make run-data_service_search # Dataset metadata (no auth)| Library | Purpose | Integration |
|---|---|---|
| libcurl | HTTP requests | find_package(CURL) |
| Glaze v7.6.0 | JSON parsing (compile-time reflection) | FetchContent |
| GoogleTest | Unit testing | FetchContent |
| libnetcdf | NetCDF support (optional) | find_package(netCDF) |
- CDO Web Services v2 Documentation
- NCEI Access Data Service API Documentation
- NCEI Data Access Portal
- CDO API Token Registration
- NCEI API Guide (Community)
- Available CDO Datasets
Issues and pull requests are welcome. For non-trivial changes please open an issue first to discuss the approach. Local dev loop:
make build # cmake -B build && cmake --build build
make test # ctest --output-on-failure
make lint # clang-format --dry-run -Werror
make format # clang-format -i (call before pushing)CI runs the same lint+build+test on push and PR (Ubuntu 24.04 +
macos-latest); see .github/workflows/ci.yml.
MIT