Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to this library are documented in this file.
- Added crude bounds checking within `pyiem.observation` to prevent out of
reasonable bounds data from going to the database.
- Increased LSR jabber message generation limit to 20 (#1154).
- Support Storm Prediction Center updated `CIG[1-3]` thresholds (#1156).
- Update CI testing to include python=3.14

### Bug Fixes
Expand Down
104 changes: 104 additions & 0 deletions data/product_examples/SPCPTS/PTSDY1_gh1156.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
364
WUUS01 KWNS 141448
PTSDY1

DAY 1 CONVECTIVE OUTLOOK AREAL OUTLINE
NWS STORM PREDICTION CENTER NORMAN OK
0848 AM CDT SAT MAR 15 2025

VALID TIME 141630Z - 151200Z

PROBABILISTIC OUTLOOK POINTS DAY 1

... TORNADO ...

0.02 28639469 29049401 30659379 31819322 33899135 35718973
38088829 41138572 42768421 42878311 39808251 38538136
37288044 34468068 33318102 31958168 28708378
0.05 29079380 30719349 31449307 32749191 35568930 37438790
38098738 38268519 37638366 36548284 35678177 33898186
32788219 31098311 29018447
0.10 29099311 31479208 32299184 33239110 35398904 36228809
36618703 36568480 35828339 34898306 33828293 32108328
30478419 29188520
0.15 29798988 29999091 30139151 30899186 31779173 32349154
32909117 35388819 35768717 35528597 34498470 33358446
32198451 31438483 30498543 30118681 29798988
0.30 32418654 31768781 31208924 31509013 32309031 33009006
34178885 34658781 34468675 33838574 33078578 32418654
CIG1 29238916 29139041 29289157 29519228 29799276 30919234
32299184 33189114 36238809 36638710 36588490 35948348
35668259 35468130 35268086 34978064 34488067 33358103
29778397 29618494 29928677 29238916
CIG2 32939045 34698899 35528712 35498600 34848430 33908374
32768393 31418472 30488971 30559072 30979090 31539088
32939045
CIG3 31569018 32269033 32998999 33608935 33828870 33568796
33168764 32458765 31688819 31218928 31569018
&&

... HAIL ...

0.05 29019401 31009411 32889348 33789294 35229174 37198993
39488770 41368568 41548500 41318423 40798393 39648423
37888376 36958296 36058222 35008196 34048209 33048244
31348303 29478418
0.15 29059379 30729349 31469306 33419131 35518931 36748843
37138793 37158719 36478622 35958534 35268409 34338344
32738351 31278391 29898464 29198519
0.30 35108785 34998673 34138626 32648678 31888755 31188923
30139150 30859187 31619176 32319156 35108785
CIG1 31029232 32269179 33829024 35198848 35398787 35358736
35338682 34918571 34108530 32848554 31898520 30928462
30428473 29738523 29968605 30118776 29558973 29499169
30179255 31029232
&&

... WIND ...

0.05 29029401 30999412 32949345 33809294 38958819 40718718
42358608 43198518 44088394 44098147 42787956 41517890
38757963 35538011 33968065 31808177 28648380
0.15 28829212 29699285 31429213 32379160 35098949 38098734
38908622 38778469 37858274 37028178 35798118 34798136
33388182 31258303 29008447
0.30 33458275 32208310 30528419 29578530 30308666 30218911
30499079 31229152 32279139 34928930 36628647 36588481
35818339 34918304 33458275
0.45 34488468 34388507 34888682 35248726 35748718 36098621
35918499 35368449 34488468
CIG1 36468576 36258430 35808328 35308217 34818135 33378182
31548361 31108406 30538462 29688533 30228628 30988709
31288769 31088904 30959011 30999107 31099138 31279152
32289137 33669033 35058903 36468666 36468576
&&

CATEGORICAL OUTLOOK POINTS DAY 1

... CATEGORICAL ...

HIGH 33009006 34178885 34658781 34468675 33838574 33078578
32418654 31768781 31208924 31509013 32309031 33009006
MDT 31539088 32939045 34698899 35528712 35498600 34498470
33358446 32198451 31388486 30488971 30559072 30979090
31539088
ENH 29508822 29238916 29139041 29289157 29519228 29799276
31479208 32299184 33189114 36228809 36618703 36568486
35828339 34918304 33458275 32208310 31588357 30478419
29628485 29928677
SLGT 29079380 30729349 31469306 35568930 38098738 38908622
38778469 37858274 37028178 35798118 34798136 33388182
31098311 29478417
MRGL 43017990 42787956 41517890 38757963 35538011 33968065
31958168 28888365 99999999 29119402 30999412 32889348
33809294 35229174 39028815 40718718 42358608 43198518
44088394 44098166
TSTM 28089552 30739715 31799693 33319567 35769373 37959023
40348833 42788789 45088805 46808787 47818951 47729178
47099485 47859580 50299450 99999999 49881471 47041110
46121014 42870839 40970645 39660530 38760481 38000478
37080562 36700699 37230836 38721042 40471218 42201323
43571425 44851517 45791598 46491754 46562108 46332314
46412511 99999999 44847676 41857826 38637941 36137899
33367993 30518160 27958338
&&
34 changes: 21 additions & 13 deletions src/pyiem/nws/products/spcpts.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
)
DMATCH = re.compile(r"D(?P<day1>[0-9])\-?(?P<day2>[0-9])?")
THRESHOLD_ORDER = (
"0.02 0.05 0.10 0.15 0.25 0.30 0.35 0.40 0.45 0.60 TSTM MRGL SLGT ENH "
"MDT HIGH IDRT SDRT ELEV CRIT EXTM"
"0.02 0.05 0.10 0.15 0.25 0.30 0.35 0.40 0.45 0.60 0.75 0.90 "
"CIG1 CIG2 CIG3 TSTM MRGL SLGT ENH MDT HIGH IDRT SDRT ELEV CRIT EXTM"
).split()


Expand Down Expand Up @@ -252,21 +252,28 @@ def difference_geometries(self):
for cat in self.get_categories():
outlooks = list(filter(lambda x: x.category == cat, self.outlooks))
for idx in range(0, len(outlooks) - 1):
larger = outlooks[idx]
smaller = outlooks[idx + 1]
this_outlook = outlooks[idx]
next_outlook = outlooks[idx + 1]
# Figure out when the geometry_layers (full polygon) should be
# used.
if (
larger.level is None
or smaller.level is None
or larger.threshold in ["SDRT", "IDRT"] # One Off
this_outlook.level is None
or next_outlook.level is None
or this_outlook.threshold in ["SDRT", "IDRT"]
or next_outlook.threshold == "CIG1"
):
larger.geometry = larger.geometry_layers
this_outlook.geometry = this_outlook.geometry_layers
continue
larger.geometry = larger.geometry_layers.difference(
smaller.geometry_layers
this_outlook.geometry = (
this_outlook.geometry_layers.difference(
next_outlook.geometry_layers
)
)
# Ensure multipolygon
if not isinstance(larger.geometry, MultiPolygon):
larger.geometry = MultiPolygon([larger.geometry])
if not isinstance(this_outlook.geometry, MultiPolygon):
this_outlook.geometry = MultiPolygon(
[this_outlook.geometry]
)
# Last polygon needs duplicated
if outlooks:
outlooks[-1].geometry = outlooks[-1].geometry_layers
Expand Down Expand Up @@ -431,7 +438,8 @@ def find_outlooks(self):
re.match(
(
r"^(D[3-8]\-?[3-8]?|EXTM|MRGL|ENH|SLGT|MDT|ELEV|"
r"HIGH|CRIT|TSTM|SIGN|IDRT|SDRT|0\.[0-9][0-9]) "
r"HIGH|CRIT|TSTM|SIGN|CIG[1-3]|"
r"IDRT|SDRT|0\.[0-9][0-9]) "
),
line,
)
Expand Down
15 changes: 15 additions & 0 deletions tests/nws/products/test_spcpts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@
from pyiem.util import get_test_file, utc


def test_gh1156_cig():
"""Test the newly minted CIG thresholds."""
prod = parser(
get_test_file("SPCPTS/PTSDY1_gh1156.txt"),
utcnow=utc(2025, 3, 15, 15),
)
assert not prod.warnings
outlook = prod.get_outlook("TORNADO", "CIG1", 1)
assert outlook.geometry_layers.area > outlook.geometry.area
outlook = prod.get_outlook("TORNADO", "CIG2", 1)
assert outlook.geometry_layers.area > outlook.geometry.area
outlook = prod.get_outlook("TORNADO", "CIG3", 1)
assert outlook.geometry_layers.area == outlook.geometry.area


def test_d48_crosses_month():
"""Test that the right month is assigned to this."""
prod = parser(
Expand Down