This release is a pre-release and may not be stable for production use.
rsplot
rsplot is an Agent-oriented command-line tool for plotting remote-sensing,
reconstructed, and air-quality data. It provides a small, stable CLI surface for
tianpu-agent.
Installation
Run directly with uvx:
uvx rsplot --help
Or install it in a project environment:
uv add rsplot
uv run rsplot --help
Development releases
Every push to main publishes a PEP 440 development release such as
0.3.1.dev4. Stable installs continue to ignore these releases. To follow the
latest development build, opt in once:
uv tool install rsplot --prerelease allow
uv tool upgrade rsplot
The prerelease setting is retained by later uv tool upgrade commands.
For local development:
git clone https://github.com/yaoyhu/rsplot.git
cd rsplot
uv sync
uv run rsplot --help
Configuration
rsplot does not ship site-specific data paths. Configure local satellite,
station, boundary, and font paths with a TOML file:
mkdir -p ~/.config/rsplot
cp config.example.toml ~/.config/rsplot/config.toml
Then edit ~/.config/rsplot/config.toml:
[paths]
geojson_dir = "/path/to/geojson"
font_dir = "/path/to/fonts"
# hcho_cmap_file = "/path/to/256x3_rgb_table.txt"
[paths.data_dirs]
no2 = "/path/to/NO2"
o3 = "/path/to/O3"
o3pr = "/path/to/O3PR"
hcho = "/path/to/HCHO"
guokong = "/path/to/guokong_combine"
era5 = "/path/to/era5"
forecast = "/path/to/forecast"
reconstruct = "/path/to/reconstruct"
# Optional lower-level boundary override for a direct-administered city.
# [subdivision_overrides."中山市"]
# path = "/path/to/中山市镇街界线.shp"
# name_field = "镇街名称"
# level = "township"
# Optional satellite basemap key. Leave commented unless you have a real key.
# [tianditu]
# api_key = "your-real-key"
You can also point to a different config file:
export RSPLOT_CONFIG="/path/to/rsplot.toml"
Environment variables override TOML values:
export TIANDITU_API_KEY="your-key"
export RSPLOT_GEOJSON_DIR="/path/to/geojson"
export RSPLOT_FONT_DIR="/path/to/fonts"
export RSPLOT_DATA_DIR_NO2="/path/to/NO2"
export RSPLOT_DATA_DIR_O3="/path/to/O3"
export RSPLOT_DATA_DIR_O3PR="/path/to/O3PR"
export RSPLOT_DATA_DIR_HCHO="/path/to/HCHO"
export RSPLOT_DATA_DIR_GUOKONG="/path/to/guokong_combine"
export RSPLOT_DATA_DIR_ERA5="/path/to/era5"
export RSPLOT_DATA_DIR_FORECAST="/path/to/forecast"
export RSPLOT_DATA_DIR_RECONSTRUCT="/path/to/reconstruct"
export RSPLOT_HCHO_CMAP_FILE="/path/to/256x3_rgb_table.txt"
Command-line directory options, such as --data-dir, take precedence for the
command being run. If no TianDiTu API key is configured, use --basemap none
to avoid requesting satellite tiles.
City subdivision overrides replace the county layer only for the configured
city. Their source CRS is converted to the CRS of the standard city boundary.
This lets direct-administered towns or streets appear in maps and spatial
summaries, and makes qualified inputs such as 中山/板芙 resolvable.
Usage
The examples below assume data paths are configured and readable.
# Show available commands and options.
rsplot --help
# Plot a single-day TROPOMI raster.
rsplot raster 合肥 20260115 -p no2 -o ./logs/hcho/raster_city_jiujiang_hcho.png
# Plot a 30-day TROPOMI window mean.
rsplot raster 安徽 20260131 -p hcho --days 30 -o hcho_anhui_202601.png
# Plot a TROPOMI ozone-profile tropospheric column raster.
rsplot raster 安徽 20260227 -p o3pr -o o3pr_anhui_20260227.png
# Plot an explicit TROPOMI date range.
rsplot raster 长三角 20260101-20260131 -p no2 -o no2_yrd_202601.png
# Reconstructed-grid input is China standard time; rsplot converts it to the
# matching UTC archive filename. This reads 2026051100.nc.
rsplot reconstruct 安徽 2026051108 --var PM2.5 \
-o reconstruct_anhui_pm25.png
# Date-only input selects the latest complete hour in that China-time day.
rsplot reconstruct 合肥 20260511 --var O3 --res 0.01 \
-o reconstruct_hefei_o3.png
# Forecast input is China standard time; rsplot converts it
# to the matching UTC archive initialization hour before reading.
rsplot forecast 安徽 2026063000 --lead 24 --var O3 -o forecast_anhui_o3.png
# Date-only input selects the latest initialization within that China-time day.
rsplot forecast 合肥 20260630 --lead 72 --var PM2.5 -o forecast_hefei_pm25.png
# Overlay station observations on the forecast valid time.
rsplot forecast 合肥 2026041409 --lead 6 --var O3 --station-overlay -o forecast_station_hefei_o3.png
# Plot ERA5 2 m temperature. Input hours are China standard time; rsplot
# converts them to the matching UTC archive day and hour automatically.
rsplot meteo 南京 2026051115 --temp-level 2m -o meteo_nanjing_t2m.png
# Plot black 850 hPa wind arrows; length represents speed and no wind
# colorbar is drawn. The reference arrow gives the speed scale.
rsplot meteo 南京 2026051115 --wind-level 850 -o meteo_nanjing_wind850.png
# Overlay black wind arrows on a temperature background. Only the temperature
# colorbar is drawn.
rsplot meteo 南京 2026051115 --temp-level 850 \
--wind-level 850 -o meteo_nanjing_t850_wind850.png
# Overlay station observations with black wind arrows.
rsplot meteo 南京 2026051115 --station-var O3 \
--wind-level 850 -o meteo_nanjing_o3_wind850.png
# Temperature, stations, and wind can be combined. The two colorbars describe
# temperature and station concentration; wind magnitude is encoded by length.
rsplot meteo 南京 2026051115 --temp-level 850 --station-var O3 \
--wind-level 850 -o meteo_nanjing_t850_o3_wind850.png
# Plot national monitoring station observations.
rsplot station 合肥 2026041415 --var O3 -o station_hefei_o3.png
# Plot one station's hourly observations over the latest seven days.
rsplot trend 3347A 20260520 --days 7 --var O3 -o trend_3347A_o3_7d.png
# A single day and an explicit date range are also supported.
rsplot trend 3347A 20260520 --days 1 --var O3 -o trend_3347A_o3_day.png
rsplot trend 3347A 20260501-20260507 --var PM2.5 -o trend_3347A_pm25.png
# Plot all regional stations as grey lines plus the hourly station mean.
rsplot trend 重庆 20260520 --days 7 --var O3 --region -o trend_chongqing_o3.png
# Plot only the regional mean and highlight one or more explicit stations.
rsplot trend 重庆 20260520 --days 7 --var O3 --region --view mean \
--focus-station 3347A --threshold 200 -o trend_chongqing_focus.png
# Overlay satellite raster data and station observations.
rsplot overlay 合肥 2026041415 -p no2 --var NO2 -o overlay_hefei_no2.png
# Plot an FNR regime map with the default 7-day window.
rsplot fnr 合肥 20260114 -o fnr_hefei.png
# Plot an FNR regime map with an explicit date range.
rsplot fnr 长三角 20260101-20260130 -o fnr_yrd_202601.png
# Override FNR regime thresholds for agent-driven sensitivity tests.
rsplot fnr 合肥 20260114 --voc-threshold 0.8 --nox-threshold 1.8 --display-max 5 -o fnr_hefei_sensitivity.png
When -o/--output is provided, rsplot writes the image to that path and also
writes a JSON sidecar with the same base name. Reconstructed-grid JSON records
both the China-time input and matched UTC archive hour, source file, native and
output resolution, regional statistics, administrative summaries, and hotspots.
Select multiple regions without configuration
raster and overlay accept a quoted, comma-separated region argument.
Use English or Chinese commas; surrounding whitespace and embedded line
breaks are ignored in a list. Province, city, and qualified county/township
names can be mixed. County and township names still require their city,
e.g. 中山市/东区街道. Other commands retain their single-region input.
# One map covering three selected townships.
uv run rsplot raster \
"中山市/东区街道,中山市/南区街道,中山市/石岐街道" 20260907 \
--sensor gems -p o3pr --cf 1 --res 0.075 --smooth 0 \
--basemap none -o ./logs/zhongshan/three_streets_raster.png
# The same daily satellite field with observations at 19:00 China time.
uv run rsplot overlay \
"中山市/东区街道,中山市/南区街道,中山市/石岐街道" 20260907 \
--sensor gems -p o3pr --station-datetime 2026090719 \
--cf 1 --res 0.075 --smooth 0 --basemap none \
-o ./logs/zhongshan/three_streets_overlay.png
These examples use your existing product/station data paths. Optional
--data-dir for raster or --raster-dir for overlay overrides the satellite
root; no named region group needs to be added to configuration.
Every member is resolved before reading data. Repeated aliases are removed in first-occurrence order; empty items and unknown names fail with an explicit error instead of silently omitting regions. The map reads and processes the combined extent once, but colors only the union of the selected geometries, preserving holes and gaps between disconnected regions. Each member retains its boundary and label. Defaults use the coarsest selected level; combined maps use automatic geographic ticks. Explicit plot options still take precedence. Far-apart selections share one map with intervening whitespace.
Combined maps use 联合区域(N个) as their default name. The JSON adds
region.members with canonical qualified names, administrative levels and
extents. Existing overall statistics operate on the geometric union, without
double-counting parent/child overlaps. member_stats summarizes each selected
member using that same statistical grid; for overlay these lists are under
raster.member_stats and station.member_stats. A member with no valid data
remains in the list with zero valid counts and null numeric summaries.
Members may overlap, so their counts must not be added to recover the union
count. Grid centers exactly on a member boundary are excluded from that
member's statistics, even if they lie inside the combined region. Station
selection retains the existing boundary-tolerance rule. spatial_summary
continues to describe subdivisions, independently of selected-member stats.
Single-region inputs retain their existing output contract.
Run the isolated multi-region regressions:
uv run python -m unittest discover -s tests -p 'test_multi_regions.py' -v
Raster boundaries and regional statistics
raster, overlay, forecast (including station overlays), and
reconstruct prepare separate display and statistics grids:
- Display: retain valid grid cells that intersect the administrative geometry, even when their centers are outside. Clip the colored layer to the actual boundary, including holes and disconnected islands. Display smoothing uses surrounding values but preserves the input valid-cell mask.
- Statistics: select grid centers strictly inside the geometry; centers exactly on the boundary are excluded. Each valid selected cell has equal weight. Coverage is the valid selected-cell count divided by the total selected-cell count, not the percentage of colored land area. Existing product-specific gap-filling and statistical smoothing behavior remains unchanged; GEMS statistics remain unfilled and unsmoothed.
The PNG sidecar adds boundary_policy with the selection and weighting
rules, the displayed intersecting-cell count, and the count whose centers
are outside. For overlay, this block is nested under raster.
A small region can display overlapping cells while having no interior grid
centers: its statistics then report zero valid cells and null summaries.
This change does not increase the satellite's native resolution or fill
missing observations.
Run boundary-selection and actual rendering regressions (including holes and both PlateCarree and Mercator projections):
uv run python -m unittest discover -s tests -p 'test_boundary_display.py' -v
GEMS satellite products
Use --sensor gems with raster, recent, and overlay. Omitting
--sensor retains TROPOMI behavior. GEMS FNR and satellite trend curves are
not included in this release.
Configure the three archive roots in [paths.data_dirs]:
gems_no2 = "/path/to/GEMS/NO2"
gems_hcho = "/path/to/GEMS/HCHO"
gems_o3 = "/path/to/GEMS/O3"
Alternatively set RSPLOT_DATA_DIR_GEMS_NO2, RSPLOT_DATA_DIR_GEMS_HCHO,
and RSPLOT_DATA_DIR_GEMS_O3. Each root contains YYYYMM/DD/*.nc.
--data-dir (--raster-dir for overlay) overrides the configured root.
| Product | GEMS variable | Display unit |
|---|---|---|
no2 |
Data Fields/ColumnAmountNO2Trop |
10¹⁵ molecules/cm² |
hcho |
Data Fields/ColumnAmount |
10¹⁵ molecules/cm² |
o3 |
product/total_ozone_column |
DU |
o3pr |
product/troposphere_ozone_column |
DU |
Both ozone quantities use the O3P files in gems_o3; o3pr reads the
provided tropospheric column without reintegrating the profile. Neither
quantity represents surface ozone concentration.
GEMS dates and scan requests use Asia/Shanghai. YYYYMMDDHHMM selects
an exact nominal scan; YYYYMMDD computes the day's valid-scan mean.
For example, 202609010845 selects the 20260901_0045 UTC archive scan.
The filename is the nominal scan time, not a simultaneous observation time
for every pixel: GEMS scans begin at :45 UTC and take about 30 minutes
(NESC operation schedule).
Missing scan requests report available local times instead of selecting a
replacement. Duplicate files for one nominal scan are rejected; use a root
containing one archive version.
# Exact scan, daily mean, and multi-day mean.
rsplot raster 安徽 202609011145 --sensor gems -p no2 \
--basemap none --output /tmp/gems_no2_scan.png
rsplot raster 安徽 20260901 --sensor gems -p hcho \
--basemap none --output /tmp/gems_hcho_daily.png
rsplot raster 安徽 20260901-20260902 --sensor gems -p o3pr \
--basemap none --output /tmp/gems_o3pr_window.png
# Recent daily regional statistics (JSON only).
rsplot recent 安徽 20260902 --days 2 --source raster --sensor gems -p no2 \
--output /tmp/gems_no2_recent.json
# 11:45 China-time scan with observations at the nearest hour, 12:00.
rsplot overlay 安徽 202609011145 --sensor gems -p no2 \
--basemap none --output /tmp/gems_no2_overlay.png
# Use --station-datetime YYYYMMDDHH for an intentional time override.
Each scan is first remapped by pixel/target overlap area in the equal-area
EPSG:6933 projection, onto a globally aligned 0.05° analysis grid. This is
a numerical analysis spacing, not native satellite resolution. Daily means
weight valid scans equally at each grid cell; multi-day means then weight
valid days equally. A daily cell needs at least one valid scan. Window --n-min
continues to count days, using the existing product defaults, and is not
available for a single scan. These are means of available daytime scans,
not full 24-hour means. Empty scans and failed files do not contribute zeros.
For GEMS raster and overlay, --res controls only the display grid.
Display cells receive area-weighted analysis values and are clipped to the
fixed analysis grid's valid-cell union and the administrative boundary.
Changing --res can change displayed averages, but cannot move the valid
support or change the scientific statistics. --smooth is applied on the
fixed analysis grid for display only, preserving its valid mask. GEMS
recent uses the same 0.05° analysis spacing and ignores --res.
TROPOMI keeps its existing resolution behavior.
When available, GEMS pixel boundaries use file CornerLongitude and
CornerLatitude arrays. The checked O3P and NO2 files have centers only;
their quadrilateral boundaries are estimates from adjacent unfiltered
native centers, with linear extrapolation at the outer scan edge. Missing
geolocation is not interpolated, and rejected retrievals do not contribute
values. This is not a recovery of official footprints or extra observations.
Analysis cells need positive-area overlap with valid footprints; their
coverage percentage counts valid analysis centers, not precisely observed
land area. Subcell gaps remain below the 0.05° analysis spacing.
Sidecars record footprint_source per scan, spatial_aggregation,
area_crs, analysis_resolution_deg, display_resolution_deg, and
display_support. The compatibility swath.resolution_deg describes the
analysis grid. Statistics differ from the previous center-point-binning
implementation, but remain identical across display resolutions.
The initial GEMS filter retains final quality flag 0, finite coordinates
and columns, SZA/VZA below 70°, and a nonnegative cloud measure no greater
than --cf (default 0.5). The cloud measures differ: NO2 uses CloudFraction,
HCHO uses CloudRadianceFraction, and O3P uses effective_cloud_fraction.
--qa applies only to TROPOMI and is rejected for GEMS. Quality-filtered
negative columns are retained; HCHO gap-filled flag classes are excluded.
This explicit filter is recorded with each file's version and variable in
JSON; quality flag meanings must not be transferred between products.
GEMS JSON statistics, spatial summaries, and hotspots use the unfilled,
unsmoothed regional grid. Smoothing affects the map only and cannot create
new valid cells. Sidecars include sensor, quantity, the requested local
time, nominal UTC/local scan times, actual observation time ranges, filter
settings, file failures, and scan-count summaries. Window metadata retains
per-day diagnostics. Existing TROPOMI fields remain compatible; the boundary
policy block described above is additive.
GEMS overlay accepts an exact scan or a daily mean. A daily mean requires
an explicit --station-datetime YYYYMMDDHH; its title and JSON distinguish
the daily satellite mean from the hourly station observation, without a
fictitious scan-time offset. Exact scans match the nearest station hour
(at most 30 minutes, ties forward). A missing station hour is an error.
--station-datetime records a manual override and its time difference.
NO2 pairs with NO2; O3/O3PR pair with O3; HCHO requires an explicit --var.
Satellite columns and station concentrations retain independent colorbars
and units; the map shows spatial juxtaposition, not a column-to-surface
conversion.
Run the self-contained GEMS regressions without adding pytest:
uv run python -m unittest discover -s tests -p 'test_gems.py' -v
License
This project is distributed under the license in LICENSE.
Publishing
The package version is derived from Git by hatch-vcs; do not add a static
project.version or make version-bump commits. The latest vX.Y.Z tag is the
stable version. Commits after it build as the next patch development series:
v0.3.0 -> 0.3.0
first commit -> 0.3.1.dev1
second commit -> 0.3.1.dev2
v0.3.1 -> 0.3.1
GitHub Actions publishes every main push through the pypi-dev environment
and every v* tag through pypi. Configure two PyPI Trusted Publishers with
the same owner, repository, and workflow, differing only by environment:
Owner: yaoyhu
Repository: rsplot
Workflow: publish.yml
Environment: pypi-dev
Environment: pypi
Leave pypi-dev automatic for continuous development uploads. Protect the
pypi GitHub environment with required reviewers so stable releases need an
explicit approval.
To make a stable release after the desired commit is on GitHub:
git tag -a v0.3.1 -m v0.3.1
git push github v0.3.1
Metadata
Release files for rsplot 0.3.2.dev4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rsplot-0.3.2.dev4.tar.gz | 111.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rsplot-0.3.2.dev4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 258.5 kB
Release files / rsplot-0.3.2.dev4.tar.gz
| Download URL | rsplot-0.3.2.dev4.tar.gz |
|---|---|
| Size | 111.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
30343e6848da98bf1aed005b6fc7490c2bed128ab1bb205bebb281fac417ff21
|
|
BLAKE2b-256 checksum How to use checksums |
21df77aacb871c533b907b1222966485b43bc56c8bfbb129f5ad7a251aa42b69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / rsplot-0.3.2.dev4-py3-none-any.whl
| Download URL | rsplot-0.3.2.dev4-py3-none-any.whl |
|---|---|
| Size | 146.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
603a04839b5305406c595a6ddd400646ad199fa3bd8959e45ae61c2dee024799
|
|
BLAKE2b-256 checksum How to use checksums |
1d468371d8af7d63f4c3014e3ea55d50e644746d6c8cc57ea46f6fcd75fdea5b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|