rsplot 0.2.4__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rsplot-0.2.4 → rsplot-0.2.5}/PKG-INFO +49 -4
- {rsplot-0.2.4 → rsplot-0.2.5}/README.md +48 -3
- {rsplot-0.2.4 → rsplot-0.2.5}/pyproject.toml +1 -1
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/cli.py +5 -3
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/config.py +100 -32
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/geo/boundaries.py +6 -6
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/colormaps.py +9 -10
- rsplot-0.2.5/src/rsplot/plotting/meteo.py +422 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/overlay.py +23 -8
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/raster.py +2 -1
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/styles.py +9 -2
- rsplot-0.2.5/src/rsplot/readers/era5.py +477 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/guokong.py +7 -7
- {rsplot-0.2.4 → rsplot-0.2.5}/.gitignore +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/LICENSE +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/__init__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/__main__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/fnr.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/geo/__init__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/geo/gridding.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/__init__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/fnr.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/plotting/station.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/__init__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/base.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/tropomi_hcho.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/tropomi_no2.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/tropomi_o3.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/readers/tropomi_o3pr.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/recent.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/results.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/tiles/__init__.py +0 -0
- {rsplot-0.2.4 → rsplot-0.2.5}/src/rsplot/tiles/tianditu.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rsplot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: CLI tool for plotting Remote Sensing data, designed for Agent.
|
|
5
5
|
Project-URL: Repository, https://git.lug.ustc.edu.cn/yaoyhu/rsplot
|
|
6
6
|
Project-URL: GitHub, https://github.com/yaoyhu/rsplot
|
|
@@ -258,16 +258,61 @@ uv run rsplot --help
|
|
|
258
258
|
|
|
259
259
|
## Configuration
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
`rsplot` does not ship site-specific data paths. Configure local satellite,
|
|
262
|
+
station, boundary, and font paths with a TOML file:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
mkdir -p ~/.config/rsplot
|
|
266
|
+
cp config.example.toml ~/.config/rsplot/config.toml
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Then edit `~/.config/rsplot/config.toml`:
|
|
270
|
+
|
|
271
|
+
```toml
|
|
272
|
+
[paths]
|
|
273
|
+
geojson_dir = "/path/to/geojson"
|
|
274
|
+
font_dir = "/path/to/fonts"
|
|
275
|
+
# hcho_cmap_file = "/path/to/256x3_rgb_table.txt"
|
|
276
|
+
|
|
277
|
+
[paths.data_dirs]
|
|
278
|
+
no2 = "/path/to/NO2"
|
|
279
|
+
o3 = "/path/to/O3"
|
|
280
|
+
o3pr = "/path/to/O3PR"
|
|
281
|
+
hcho = "/path/to/HCHO"
|
|
282
|
+
guokong = "/path/to/guokong_combine"
|
|
283
|
+
|
|
284
|
+
# Optional satellite basemap key. Leave commented unless you have a real key.
|
|
285
|
+
# [tianditu]
|
|
286
|
+
# api_key = "your-real-key"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
You can also point to a different config file:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
export RSPLOT_CONFIG="/path/to/rsplot.toml"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Environment variables override TOML values:
|
|
262
296
|
|
|
263
297
|
```bash
|
|
264
298
|
export TIANDITU_API_KEY="your-key"
|
|
299
|
+
export RSPLOT_GEOJSON_DIR="/path/to/geojson"
|
|
300
|
+
export RSPLOT_FONT_DIR="/path/to/fonts"
|
|
301
|
+
export RSPLOT_DATA_DIR_NO2="/path/to/NO2"
|
|
302
|
+
export RSPLOT_DATA_DIR_O3="/path/to/O3"
|
|
303
|
+
export RSPLOT_DATA_DIR_O3PR="/path/to/O3PR"
|
|
304
|
+
export RSPLOT_DATA_DIR_HCHO="/path/to/HCHO"
|
|
305
|
+
export RSPLOT_DATA_DIR_GUOKONG="/path/to/guokong_combine"
|
|
306
|
+
export RSPLOT_HCHO_CMAP_FILE="/path/to/256x3_rgb_table.txt"
|
|
265
307
|
```
|
|
266
308
|
|
|
309
|
+
Command-line directory options, such as `--data-dir`, take precedence for the
|
|
310
|
+
command being run. If no TianDiTu API key is configured, use `--basemap none`
|
|
311
|
+
to avoid requesting satellite tiles.
|
|
312
|
+
|
|
267
313
|
## Usage
|
|
268
314
|
|
|
269
|
-
The examples below assume
|
|
270
|
-
environment with equivalent data paths configured.
|
|
315
|
+
The examples below assume data paths are configured and readable.
|
|
271
316
|
|
|
272
317
|
```bash
|
|
273
318
|
# Show available commands and options.
|
|
@@ -31,16 +31,61 @@ uv run rsplot --help
|
|
|
31
31
|
|
|
32
32
|
## Configuration
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
`rsplot` does not ship site-specific data paths. Configure local satellite,
|
|
35
|
+
station, boundary, and font paths with a TOML file:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
mkdir -p ~/.config/rsplot
|
|
39
|
+
cp config.example.toml ~/.config/rsplot/config.toml
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then edit `~/.config/rsplot/config.toml`:
|
|
43
|
+
|
|
44
|
+
```toml
|
|
45
|
+
[paths]
|
|
46
|
+
geojson_dir = "/path/to/geojson"
|
|
47
|
+
font_dir = "/path/to/fonts"
|
|
48
|
+
# hcho_cmap_file = "/path/to/256x3_rgb_table.txt"
|
|
49
|
+
|
|
50
|
+
[paths.data_dirs]
|
|
51
|
+
no2 = "/path/to/NO2"
|
|
52
|
+
o3 = "/path/to/O3"
|
|
53
|
+
o3pr = "/path/to/O3PR"
|
|
54
|
+
hcho = "/path/to/HCHO"
|
|
55
|
+
guokong = "/path/to/guokong_combine"
|
|
56
|
+
|
|
57
|
+
# Optional satellite basemap key. Leave commented unless you have a real key.
|
|
58
|
+
# [tianditu]
|
|
59
|
+
# api_key = "your-real-key"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You can also point to a different config file:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export RSPLOT_CONFIG="/path/to/rsplot.toml"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Environment variables override TOML values:
|
|
35
69
|
|
|
36
70
|
```bash
|
|
37
71
|
export TIANDITU_API_KEY="your-key"
|
|
72
|
+
export RSPLOT_GEOJSON_DIR="/path/to/geojson"
|
|
73
|
+
export RSPLOT_FONT_DIR="/path/to/fonts"
|
|
74
|
+
export RSPLOT_DATA_DIR_NO2="/path/to/NO2"
|
|
75
|
+
export RSPLOT_DATA_DIR_O3="/path/to/O3"
|
|
76
|
+
export RSPLOT_DATA_DIR_O3PR="/path/to/O3PR"
|
|
77
|
+
export RSPLOT_DATA_DIR_HCHO="/path/to/HCHO"
|
|
78
|
+
export RSPLOT_DATA_DIR_GUOKONG="/path/to/guokong_combine"
|
|
79
|
+
export RSPLOT_HCHO_CMAP_FILE="/path/to/256x3_rgb_table.txt"
|
|
38
80
|
```
|
|
39
81
|
|
|
82
|
+
Command-line directory options, such as `--data-dir`, take precedence for the
|
|
83
|
+
command being run. If no TianDiTu API key is configured, use `--basemap none`
|
|
84
|
+
to avoid requesting satellite tiles.
|
|
85
|
+
|
|
40
86
|
## Usage
|
|
41
87
|
|
|
42
|
-
The examples below assume
|
|
43
|
-
environment with equivalent data paths configured.
|
|
88
|
+
The examples below assume data paths are configured and readable.
|
|
44
89
|
|
|
45
90
|
```bash
|
|
46
91
|
# Show available commands and options.
|
|
@@ -413,6 +413,7 @@ def raster(
|
|
|
413
413
|
vmax=use_vmax,
|
|
414
414
|
basemap=use_basemap,
|
|
415
415
|
tianditu_key=cfg.tianditu_key,
|
|
416
|
+
hcho_cmap_file=cfg.hcho_cmap_file,
|
|
416
417
|
title=title,
|
|
417
418
|
output=output,
|
|
418
419
|
colorbar_label=colorbar_label,
|
|
@@ -589,7 +590,7 @@ def station(
|
|
|
589
590
|
datetime_str=resolved_dt,
|
|
590
591
|
threshold=threshold,
|
|
591
592
|
unit=unit,
|
|
592
|
-
cities_gdf=_load_city(cfg.
|
|
593
|
+
cities_gdf=_load_city(cfg.require_geojson_dir()),
|
|
593
594
|
output=output,
|
|
594
595
|
)
|
|
595
596
|
emit_result(result, output)
|
|
@@ -867,6 +868,7 @@ def overlay(
|
|
|
867
868
|
dpi=use_dpi,
|
|
868
869
|
basemap=use_basemap,
|
|
869
870
|
tianditu_key=cfg.tianditu_key,
|
|
871
|
+
hcho_cmap_file=cfg.hcho_cmap_file,
|
|
870
872
|
title=title,
|
|
871
873
|
output=output,
|
|
872
874
|
)
|
|
@@ -895,7 +897,7 @@ def overlay(
|
|
|
895
897
|
datetime_str=resolved_dt,
|
|
896
898
|
threshold=threshold,
|
|
897
899
|
unit=unit,
|
|
898
|
-
cities_gdf=_load_city(cfg.
|
|
900
|
+
cities_gdf=_load_city(cfg.require_geojson_dir()),
|
|
899
901
|
output=output,
|
|
900
902
|
)
|
|
901
903
|
emit_result(result, output)
|
|
@@ -1255,7 +1257,7 @@ def fnr(
|
|
|
1255
1257
|
if raster_dir_hcho is not None
|
|
1256
1258
|
else cfg.get_data_dir("hcho")
|
|
1257
1259
|
)
|
|
1258
|
-
console.print(
|
|
1260
|
+
console.print("\n[bold]读取数据:[/bold]")
|
|
1259
1261
|
console.print(f" NO2 : {use_no2_dir}")
|
|
1260
1262
|
console.print(f" HCHO: {use_hcho_dir}")
|
|
1261
1263
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Configuration management for rsplot.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Runtime paths are intentionally configured outside the package. The loader
|
|
4
|
+
merges safe built-in defaults, TOML config files, environment variables, and
|
|
5
|
+
finally CLI overrides handled by command functions.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
@@ -19,21 +20,20 @@ else:
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
# ---------------------------------------------------------------------------
|
|
22
|
-
#
|
|
23
|
+
# Safe built-in path defaults
|
|
23
24
|
# ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
DEFAULT_GEOJSON_DIR = "/satellite/d4/yaoyhu/tiandi"
|
|
32
|
-
DEFAULT_FONT_DIR = "/satellite/d4/yaoyhu/fonts"
|
|
25
|
+
KNOWN_DATA_PRODUCTS = ("no2", "o3", "o3pr", "hcho", "guokong")
|
|
26
|
+
|
|
27
|
+
DEFAULT_DATA_DIRS: dict[str, str] = {}
|
|
28
|
+
DEFAULT_GEOJSON_DIR = None
|
|
29
|
+
DEFAULT_FONT_DIR = None
|
|
30
|
+
DEFAULT_HCHO_CMAP_FILE = None
|
|
33
31
|
DEFAULT_TIANDITU_KEY = None
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
CONFIG_PATH = Path.home() / ".config" / "rsplot" / "config.toml"
|
|
35
|
+
CONFIG_ENV_VAR = "RSPLOT_CONFIG"
|
|
36
|
+
HCHO_CMAP_FILE_ENV = "RSPLOT_HCHO_CMAP_FILE"
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
# ---------------------------------------------------------------------------
|
|
@@ -146,15 +146,17 @@ DEFAULT_KEY_REGIONS: dict[str, list[str]] = {
|
|
|
146
146
|
# ---------------------------------------------------------------------------
|
|
147
147
|
@dataclass
|
|
148
148
|
class AppConfig:
|
|
149
|
-
"""Merged configuration
|
|
149
|
+
"""Merged configuration from files and environment variables."""
|
|
150
150
|
|
|
151
151
|
# paths (per-product data directories)
|
|
152
152
|
data_dirs: dict[str, str] = field(
|
|
153
153
|
default_factory=lambda: dict(DEFAULT_DATA_DIRS)
|
|
154
154
|
)
|
|
155
|
-
geojson_dir: str = DEFAULT_GEOJSON_DIR
|
|
156
|
-
font_dir: str = DEFAULT_FONT_DIR
|
|
155
|
+
geojson_dir: str | None = DEFAULT_GEOJSON_DIR
|
|
156
|
+
font_dir: str | None = DEFAULT_FONT_DIR
|
|
157
|
+
hcho_cmap_file: str | None = DEFAULT_HCHO_CMAP_FILE
|
|
157
158
|
tianditu_key: str | None = DEFAULT_TIANDITU_KEY
|
|
159
|
+
config_files: list[str] = field(default_factory=list)
|
|
158
160
|
|
|
159
161
|
# plotting defaults
|
|
160
162
|
dpi: int = 300
|
|
@@ -167,43 +169,82 @@ class AppConfig:
|
|
|
167
169
|
|
|
168
170
|
@classmethod
|
|
169
171
|
def load(cls) -> AppConfig:
|
|
170
|
-
"""Load config from TOML
|
|
172
|
+
"""Load config from TOML files and environment variables.
|
|
173
|
+
|
|
174
|
+
Merge order:
|
|
175
|
+
1. safe built-in defaults
|
|
176
|
+
2. ~/.config/rsplot/config.toml, if present
|
|
177
|
+
3. RSPLOT_CONFIG, if set
|
|
178
|
+
4. environment variable path/API-key overrides
|
|
179
|
+
"""
|
|
171
180
|
cfg = cls()
|
|
172
|
-
|
|
173
|
-
with open(
|
|
181
|
+
for path in _config_paths():
|
|
182
|
+
with open(path, "rb") as f:
|
|
174
183
|
data = tomllib.load(f)
|
|
175
184
|
cfg._apply_toml(data)
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
cfg.config_files.append(str(path))
|
|
186
|
+
|
|
187
|
+
cfg._apply_env()
|
|
188
|
+
return cfg
|
|
189
|
+
|
|
190
|
+
def _apply_env(self) -> None:
|
|
191
|
+
"""Apply environment variable overrides."""
|
|
192
|
+
products = set(KNOWN_DATA_PRODUCTS) | set(self.data_dirs)
|
|
193
|
+
for product in sorted(products):
|
|
178
194
|
env_key = f"RSPLOT_DATA_DIR_{product.upper()}"
|
|
179
195
|
if v := os.environ.get(env_key):
|
|
180
|
-
|
|
196
|
+
self.data_dirs[product] = _normalize_path(v)
|
|
181
197
|
if v := os.environ.get("RSPLOT_GEOJSON_DIR"):
|
|
182
|
-
|
|
198
|
+
self.geojson_dir = _normalize_path(v)
|
|
199
|
+
if v := os.environ.get("RSPLOT_FONT_DIR"):
|
|
200
|
+
self.font_dir = _normalize_path(v)
|
|
201
|
+
if v := os.environ.get(HCHO_CMAP_FILE_ENV):
|
|
202
|
+
self.hcho_cmap_file = _normalize_path(v)
|
|
183
203
|
if v := os.environ.get("TIANDITU_API_KEY"):
|
|
184
|
-
|
|
185
|
-
return cfg
|
|
204
|
+
self.tianditu_key = v
|
|
186
205
|
|
|
187
206
|
def get_data_dir(self, product: str) -> str:
|
|
188
207
|
"""Return data directory for a product, or raise if not configured."""
|
|
189
208
|
key = product.lower()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
209
|
+
dir_path = self.data_dirs.get(key)
|
|
210
|
+
if dir_path:
|
|
211
|
+
return dir_path
|
|
212
|
+
|
|
213
|
+
configured = (
|
|
214
|
+
", ".join(sorted(k for k, v in self.data_dirs.items() if v))
|
|
215
|
+
or "none"
|
|
216
|
+
)
|
|
193
217
|
raise ValueError(
|
|
194
218
|
f"No data directory configured for product '{product}'. "
|
|
195
|
-
f"
|
|
219
|
+
f"Set [paths.data_dirs].{key} in {CONFIG_PATH}, set "
|
|
220
|
+
f"RSPLOT_DATA_DIR_{key.upper()}, point {CONFIG_ENV_VAR} to a "
|
|
221
|
+
"TOML config file, or pass the command-specific data directory "
|
|
222
|
+
f"option. Known products: {', '.join(KNOWN_DATA_PRODUCTS)}. "
|
|
223
|
+
f"Configured products: {configured}."
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def require_geojson_dir(self) -> str:
|
|
227
|
+
"""Return the boundary GeoJSON directory, or raise with guidance."""
|
|
228
|
+
if self.geojson_dir:
|
|
229
|
+
return self.geojson_dir
|
|
230
|
+
raise ValueError(
|
|
231
|
+
"GeoJSON boundary directory is not configured. Set "
|
|
232
|
+
f"[paths].geojson_dir in {CONFIG_PATH}, set RSPLOT_GEOJSON_DIR, "
|
|
233
|
+
f"or point {CONFIG_ENV_VAR} to a TOML config file."
|
|
196
234
|
)
|
|
197
235
|
|
|
198
236
|
def _apply_toml(self, data: dict[str, Any]) -> None:
|
|
199
237
|
paths = data.get("paths", {})
|
|
200
238
|
data_dirs = paths.get("data_dirs", {})
|
|
201
239
|
for product, dir_path in data_dirs.items():
|
|
202
|
-
|
|
240
|
+
if dir_path:
|
|
241
|
+
self.data_dirs[product.lower()] = _normalize_path(dir_path)
|
|
203
242
|
if v := paths.get("geojson_dir"):
|
|
204
|
-
self.geojson_dir = v
|
|
243
|
+
self.geojson_dir = _normalize_path(v)
|
|
205
244
|
if v := paths.get("font_dir"):
|
|
206
|
-
self.font_dir = v
|
|
245
|
+
self.font_dir = _normalize_path(v)
|
|
246
|
+
if v := paths.get("hcho_cmap_file"):
|
|
247
|
+
self.hcho_cmap_file = _normalize_path(v)
|
|
207
248
|
|
|
208
249
|
td = data.get("tianditu", {})
|
|
209
250
|
if v := td.get("api_key"):
|
|
@@ -220,6 +261,33 @@ class AppConfig:
|
|
|
220
261
|
self.key_regions[name] = provinces
|
|
221
262
|
|
|
222
263
|
|
|
264
|
+
def _config_paths() -> list[Path]:
|
|
265
|
+
"""Return config files to load in priority order."""
|
|
266
|
+
paths: list[Path] = []
|
|
267
|
+
if CONFIG_PATH.exists():
|
|
268
|
+
paths.append(CONFIG_PATH)
|
|
269
|
+
|
|
270
|
+
if raw := os.environ.get(CONFIG_ENV_VAR):
|
|
271
|
+
path = Path(_normalize_path(raw))
|
|
272
|
+
if not path.exists():
|
|
273
|
+
raise FileNotFoundError(
|
|
274
|
+
f"{CONFIG_ENV_VAR} points to a missing config file: {path}"
|
|
275
|
+
)
|
|
276
|
+
try:
|
|
277
|
+
already_loaded = path.resolve() in {p.resolve() for p in paths}
|
|
278
|
+
except OSError:
|
|
279
|
+
already_loaded = path in paths
|
|
280
|
+
if not already_loaded:
|
|
281
|
+
paths.append(path)
|
|
282
|
+
|
|
283
|
+
return paths
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _normalize_path(value: Any) -> str:
|
|
287
|
+
"""Expand ~ and environment variables in configured path values."""
|
|
288
|
+
return os.path.expanduser(os.path.expandvars(str(value)))
|
|
289
|
+
|
|
290
|
+
|
|
223
291
|
# ---------------------------------------------------------------------------
|
|
224
292
|
# GB 3095-2026 Air Quality Limits (transitional phase, grade II)
|
|
225
293
|
# ---------------------------------------------------------------------------
|
|
@@ -241,7 +309,7 @@ AQ_LIMITS_2026: dict[str, dict[str, float]] = {
|
|
|
241
309
|
# ---------------------------------------------------------------------------
|
|
242
310
|
AQI_COLORS: list[tuple[int, str]] = [
|
|
243
311
|
# (upper bound, hex color)
|
|
244
|
-
(50, "#00e400"),
|
|
312
|
+
(50, "#00e400"), # 优 / green
|
|
245
313
|
(100, "#ffff00"), # 良 / yellow
|
|
246
314
|
(150, "#ff7e00"), # 轻度污染 / orange
|
|
247
315
|
(200, "#ff0000"), # 中度污染 / red
|
|
@@ -47,7 +47,9 @@ class RegionInfo:
|
|
|
47
47
|
# sub-boundary GeoDataFrames for overlay
|
|
48
48
|
main_boundary_gdf: gpd.GeoDataFrame | None = None # red border
|
|
49
49
|
sub_boundary_gdf: gpd.GeoDataFrame | None = None # grey sub-divisions
|
|
50
|
-
detail_boundary_gdf: gpd.GeoDataFrame | None =
|
|
50
|
+
detail_boundary_gdf: gpd.GeoDataFrame | None = (
|
|
51
|
+
None # finer sub-divisions (counties in province view)
|
|
52
|
+
)
|
|
51
53
|
sub_name_field: str = NAME_FIELD
|
|
52
54
|
|
|
53
55
|
|
|
@@ -108,7 +110,7 @@ def resolve_region(
|
|
|
108
110
|
Cascade order: key_region → province → city → county.
|
|
109
111
|
If level_override is given, skip cascade and match only that level.
|
|
110
112
|
"""
|
|
111
|
-
gj = cfg.
|
|
113
|
+
gj = cfg.require_geojson_dir()
|
|
112
114
|
gdf_prov = _load_province(gj)
|
|
113
115
|
gdf_city = _load_city(gj)
|
|
114
116
|
gdf_county = _load_county(gj)
|
|
@@ -209,9 +211,7 @@ def resolve_region(
|
|
|
209
211
|
)
|
|
210
212
|
else:
|
|
211
213
|
# Regular province: cities + counties within each city
|
|
212
|
-
sub_cities = gdf_city[
|
|
213
|
-
gdf_city.geometry.intersects(union_geom)
|
|
214
|
-
]
|
|
214
|
+
sub_cities = gdf_city[gdf_city.geometry.intersects(union_geom)]
|
|
215
215
|
detail_counties = gdf_county[
|
|
216
216
|
gdf_county.geometry.intersects(union_geom)
|
|
217
217
|
]
|
|
@@ -244,7 +244,7 @@ def resolve_region(
|
|
|
244
244
|
if level_override == "county":
|
|
245
245
|
# "/" case already handled above; bare name with --level county is ambiguous
|
|
246
246
|
raise ValueError(
|
|
247
|
-
|
|
247
|
+
"县/区级别需要指定所属城市,格式: 城市/县区,如 合肥/蜀山、成都/高新"
|
|
248
248
|
)
|
|
249
249
|
|
|
250
250
|
raise ValueError(
|
|
@@ -12,15 +12,10 @@ import numpy as np
|
|
|
12
12
|
HCHO_CMAP = "rsplot_hcho"
|
|
13
13
|
STATION_CONCENTRATION_CMAP = "rsplot_station_concentration"
|
|
14
14
|
|
|
15
|
-
DEFAULT_HCHO_CMAP_FILE = (
|
|
16
|
-
"/exports/XCZ/python/tmp/pycharm_project_24/colormap_22.txt"
|
|
17
|
-
)
|
|
18
|
-
HCHO_CMAP_FILE_ENV = "RSPLOT_HCHO_CMAP_FILE"
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
def register_rsplot_colormaps() -> None:
|
|
16
|
+
def register_rsplot_colormaps(hcho_cmap_file: str | None = None) -> None:
|
|
22
17
|
"""Register custom colormaps used by rsplot plots."""
|
|
23
|
-
_register(_build_hcho_cmap())
|
|
18
|
+
_register(_build_hcho_cmap(hcho_cmap_file))
|
|
24
19
|
_register(
|
|
25
20
|
mcolors.LinearSegmentedColormap.from_list(
|
|
26
21
|
STATION_CONCENTRATION_CMAP,
|
|
@@ -36,9 +31,13 @@ def register_rsplot_colormaps() -> None:
|
|
|
36
31
|
)
|
|
37
32
|
|
|
38
33
|
|
|
39
|
-
def _build_hcho_cmap() -> mcolors.Colormap:
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
def _build_hcho_cmap(hcho_cmap_file: str | None) -> mcolors.Colormap:
|
|
35
|
+
if hcho_cmap_file:
|
|
36
|
+
cmap_file = Path(os.path.expandvars(hcho_cmap_file)).expanduser()
|
|
37
|
+
if not cmap_file.exists():
|
|
38
|
+
raise FileNotFoundError(
|
|
39
|
+
f"Configured HCHO colormap file does not exist: {cmap_file}"
|
|
40
|
+
)
|
|
42
41
|
data = np.loadtxt(cmap_file)
|
|
43
42
|
if data.shape != (256, 3):
|
|
44
43
|
raise ValueError(
|