diff --git a/CountyCodes.md b/CountyCodes.md
new file mode 100644
index 0000000..7ee8860
--- /dev/null
+++ b/CountyCodes.md
@@ -0,0 +1,3404 @@
+# County Codes
+
+## ⚠️ WARNING ⚠️
+This file is integral to the functioning of SkywarnPlus.
+Modifying this file without proper knowledge can result in unexpected behavior or system malfunctions.
+Unless you are absolutely certain about the changes you're making, DO NOT MODIFY THIS FILE.
+
+## AL
+
+| County | Code |
+|--------|------|
+| Autauga | ALC001 |
+| Baldwin | ALC003 |
+| Barbour | ALC005 |
+| Bibb | ALC007 |
+| Blount | ALC009 |
+| Bullock | ALC011 |
+| Butler | ALC013 |
+| Calhoun | ALC015 |
+| Chambers | ALC017 |
+| Cherokee | ALC019 |
+| Chilton | ALC021 |
+| Choctaw | ALC023 |
+| Clarke | ALC025 |
+| Clay | ALC027 |
+| Cleburne | ALC029 |
+| Coffee | ALC031 |
+| Colbert | ALC033 |
+| Conecuh | ALC035 |
+| Coosa | ALC037 |
+| Covington | ALC039 |
+| Crenshaw | ALC041 |
+| Cullman | ALC043 |
+| Dale | ALC045 |
+| Dallas | ALC047 |
+| DeKalb | ALC049 |
+| Elmore | ALC051 |
+| Escambia | ALC053 |
+| Etowah | ALC055 |
+| Fayette | ALC057 |
+| Franklin | ALC059 |
+| Geneva | ALC061 |
+| Greene | ALC063 |
+| Hale | ALC065 |
+| Henry | ALC067 |
+| Houston | ALC069 |
+| Jackson | ALC071 |
+| Jefferson | ALC073 |
+| Lamar | ALC075 |
+| Lauderdale | ALC077 |
+| Lawrence | ALC079 |
+| Lee | ALC081 |
+| Limestone | ALC083 |
+| Lowndes | ALC085 |
+| Macon | ALC087 |
+| Madison | ALC089 |
+| Marengo | ALC091 |
+| Marion | ALC093 |
+| Marshall | ALC095 |
+| Mobile | ALC097 |
+| Monroe | ALC099 |
+| Montgomery | ALC101 |
+| Morgan | ALC103 |
+| Perry | ALC105 |
+| Pickens | ALC107 |
+| Pike | ALC109 |
+| Randolph | ALC111 |
+| Russell | ALC113 |
+| St. Clair | ALC115 |
+| Shelby | ALC117 |
+| Sumter | ALC119 |
+| Talladega | ALC121 |
+| Tallapoosa | ALC123 |
+| Tuscaloosa | ALC125 |
+| Walker | ALC127 |
+| Washington | ALC129 |
+| Wilcox | ALC131 |
+| Winston | ALC133 |
+
+## AK
+
+| County | Code |
+|--------|------|
+| Aleutians East | AKC013 |
+| Aleutians West | AKC016 |
+| Anchorage | AKC020 |
+| Bethel | AKC050 |
+| Bristol Bay | AKC060 |
+| Denali | AKC068 |
+| Dillingham | AKC070 |
+| Fairbanks North Star | AKC090 |
+| Haines | AKC100 |
+| Juneau | AKC110 |
+| Kenai Peninsula | AKC122 |
+| Ketchikan Gateway | AKC130 |
+| Kodiak Island | AKC150 |
+| Lake and Peninsula | AKC164 |
+| Matanuska-Susitna | AKC170 |
+| Nome | AKC180 |
+| North Slope | AKC185 |
+| Northwest Arctic | AKC188 |
+| Prince of Wales-Outer Ketchikan | AKC201 |
+| Sitka | AKC220 |
+| Skagway-Hoonah-Angoon | AKC232 |
+| Southeast Fairbanks | AKC240 |
+| Valdez-Cordova | AKC261 |
+| Wade Hampton | AKC270 |
+| Wrangell-Petersburg | AKC280 |
+| Yakutat | AKC282 |
+| Yukon-Koyukuk | AKC290 |
+
+## AZ
+
+| County | Code |
+|--------|------|
+| Apache | AZC001 |
+| Cochise | AZC003 |
+| Coconino | AZC005 |
+| Gila | AZC007 |
+| Graham | AZC009 |
+| Greenlee | AZC011 |
+| La Paz | AZC012 |
+| Maricopa | AZC013 |
+| Mohave | AZC015 |
+| Navajo | AZC017 |
+| Pima | AZC019 |
+| Pinal | AZC021 |
+| Santa Cruz | AZC023 |
+| Yavapai | AZC025 |
+| Yuma | AZC027 |
+
+## AR
+
+| County | Code |
+|--------|------|
+| Arkansas | ARC001 |
+| Ashley | ARC003 |
+| Baxter | ARC005 |
+| Benton | ARC007 |
+| Boone | ARC009 |
+| Bradley | ARC011 |
+| Calhoun | ARC013 |
+| Carroll | ARC015 |
+| Chicot | ARC017 |
+| Clark | ARC019 |
+| Clay | ARC021 |
+| Cleburne | ARC023 |
+| Cleveland | ARC025 |
+| Columbia | ARC027 |
+| Conway | ARC029 |
+| Craighead | ARC031 |
+| Crawford | ARC033 |
+| Crittenden | ARC035 |
+| Cross | ARC037 |
+| Dallas | ARC039 |
+| Desha | ARC041 |
+| Drew | ARC043 |
+| Faulkner | ARC045 |
+| Franklin | ARC047 |
+| Fulton | ARC049 |
+| Garland | ARC051 |
+| Grant | ARC053 |
+| Greene | ARC055 |
+| Hempstead | ARC057 |
+| Hot Spring | ARC059 |
+| Howard | ARC061 |
+| Independence | ARC063 |
+| Izard | ARC065 |
+| Jackson | ARC067 |
+| Jefferson | ARC069 |
+| Johnson | ARC071 |
+| Lafayette | ARC073 |
+| Lawrence | ARC075 |
+| Lee | ARC077 |
+| Lincoln | ARC079 |
+| Little River | ARC081 |
+| Logan | ARC083 |
+| Lonoke | ARC085 |
+| Madison | ARC087 |
+| Marion | ARC089 |
+| Miller | ARC091 |
+| Mississippi | ARC093 |
+| Monroe | ARC095 |
+| Montgomery | ARC097 |
+| Nevada | ARC099 |
+| Newton | ARC101 |
+| Ouachita | ARC103 |
+| Perry | ARC105 |
+| Phillips | ARC107 |
+| Pike | ARC109 |
+| Poinsett | ARC111 |
+| Polk | ARC113 |
+| Pope | ARC115 |
+| Prairie | ARC117 |
+| Pulaski | ARC119 |
+| Randolph | ARC121 |
+| St. Francis | ARC123 |
+| Saline | ARC125 |
+| Scott | ARC127 |
+| Searcy | ARC129 |
+| Sebastian | ARC131 |
+| Sevier | ARC133 |
+| Sharp | ARC135 |
+| Stone | ARC137 |
+| Union | ARC139 |
+| Van Buren | ARC141 |
+| Washington | ARC143 |
+| White | ARC145 |
+| Woodruff | ARC147 |
+| Yell | ARC149 |
+
+## CA
+
+| County | Code |
+|--------|------|
+| Alameda | CAC001 |
+| Alpine | CAC003 |
+| Amador | CAC005 |
+| Butte | CAC007 |
+| Calaveras | CAC009 |
+| Colusa | CAC011 |
+| Contra Costa | CAC013 |
+| Del Norte | CAC015 |
+| El Dorado | CAC017 |
+| Fresno | CAC019 |
+| Glenn | CAC021 |
+| Humboldt | CAC023 |
+| Imperial | CAC025 |
+| Inyo | CAC027 |
+| Kern | CAC029 |
+| Kings | CAC031 |
+| Lake | CAC033 |
+| Lassen | CAC035 |
+| Los Angeles | CAC037 |
+| Madera | CAC039 |
+| Marin | CAC041 |
+| Mariposa | CAC043 |
+| Mendocino | CAC045 |
+| Merced | CAC047 |
+| Modoc | CAC049 |
+| Mono | CAC051 |
+| Monterey | CAC053 |
+| Napa | CAC055 |
+| Nevada | CAC057 |
+| Orange | CAC059 |
+| Placer | CAC061 |
+| Plumas | CAC063 |
+| Riverside | CAC065 |
+| Sacramento | CAC067 |
+| San Benito | CAC069 |
+| San Bernardino | CAC071 |
+| San Diego | CAC073 |
+| San Francisco | CAC075 |
+| San Joaquin | CAC077 |
+| San Luis Obispo | CAC079 |
+| San Mateo | CAC081 |
+| Santa Barbara | CAC083 |
+| Santa Clara | CAC085 |
+| Santa Cruz | CAC087 |
+| Shasta | CAC089 |
+| Sierra | CAC091 |
+| Siskiyou | CAC093 |
+| Solano | CAC095 |
+| Sonoma | CAC097 |
+| Stanislaus | CAC099 |
+| Sutter | CAC101 |
+| Tehama | CAC103 |
+| Trinity | CAC105 |
+| Tulare | CAC107 |
+| Tuolumne | CAC109 |
+| Ventura | CAC111 |
+| Yolo | CAC113 |
+| Yuba | CAC115 |
+
+## CO
+
+| County | Code |
+|--------|------|
+| Adams | COC001 |
+| Alamosa | COC003 |
+| Arapahoe | COC005 |
+| Archuleta | COC007 |
+| Baca | COC009 |
+| Bent | COC011 |
+| Boulder | COC013 |
+| Broomfield | COC014 |
+| Chaffee | COC015 |
+| Cheyenne | COC017 |
+| Clear Creek | COC019 |
+| Conejos | COC021 |
+| Costilla | COC023 |
+| Crowley | COC025 |
+| Custer | COC027 |
+| Delta | COC029 |
+| Denver | COC031 |
+| Dolores | COC033 |
+| Douglas | COC035 |
+| Eagle | COC037 |
+| Elbert | COC039 |
+| El Paso | COC041 |
+| Fremont | COC043 |
+| Garfield | COC045 |
+| Gilpin | COC047 |
+| Grand | COC049 |
+| Gunnison | COC051 |
+| Hinsdale | COC053 |
+| Huerfano | COC055 |
+| Jackson | COC057 |
+| Jefferson | COC059 |
+| Kiowa | COC061 |
+| Kit Carson | COC063 |
+| Lake | COC065 |
+| La Plata | COC067 |
+| Larimer | COC069 |
+| Las Animas | COC071 |
+| Lincoln | COC073 |
+| Logan | COC075 |
+| Mesa | COC077 |
+| Mineral | COC079 |
+| Moffat | COC081 |
+| Montezuma | COC083 |
+| Montrose | COC085 |
+| Morgan | COC087 |
+| Otero | COC089 |
+| Ouray | COC091 |
+| Park | COC093 |
+| Phillips | COC095 |
+| Pitkin | COC097 |
+| Prowers | COC099 |
+| Pueblo | COC101 |
+| Rio Blanco | COC103 |
+| Rio Grande | COC105 |
+| Routt | COC107 |
+| Saguache | COC109 |
+| San Juan | COC111 |
+| San Miguel | COC113 |
+| Sedgwick | COC115 |
+| Summit | COC117 |
+| Teller | COC119 |
+| Washington | COC121 |
+| Weld | COC123 |
+| Yuma | COC125 |
+
+## CT
+
+| County | Code |
+|--------|------|
+| Fairfield | CTC001 |
+| Hartford | CTC003 |
+| Litchfield | CTC005 |
+| Middlesex | CTC007 |
+| New Haven | CTC009 |
+| New London | CTC011 |
+| Tolland | CTC013 |
+| Windham | CTC015 |
+
+## DE
+
+| County | Code |
+|--------|------|
+| Kent | DEC001 |
+| New Castle | DEC003 |
+| Sussex | DEC005 |
+
+## DC
+
+| County | Code |
+|--------|------|
+| District of Columbia | DCC001 |
+
+## FL
+
+| County | Code |
+|--------|------|
+| Alachua | FLC001 |
+| Baker | FLC003 |
+| Bay | FLC005 |
+| Bradford | FLC007 |
+| Brevard | FLC009 |
+| Broward | FLC011 |
+| Calhoun | FLC013 |
+| Charlotte | FLC015 |
+| Citrus | FLC017 |
+| Clay | FLC019 |
+| Collier | FLC021 |
+| Columbia | FLC023 |
+| DeSoto | FLC027 |
+| Dixie | FLC029 |
+| Duval | FLC031 |
+| Escambia | FLC033 |
+| Flagler | FLC035 |
+| Franklin | FLC037 |
+| Gadsden | FLC039 |
+| Gilchrist | FLC041 |
+| Glades | FLC043 |
+| Gulf | FLC045 |
+| Hamilton | FLC047 |
+| Hardee | FLC049 |
+| Hendry | FLC051 |
+| Hernando | FLC053 |
+| Highlands | FLC055 |
+| Hillsborough | FLC057 |
+| Holmes | FLC059 |
+| Indian River | FLC061 |
+| Jackson | FLC063 |
+| Jefferson | FLC065 |
+| Lafayette | FLC067 |
+| Lake | FLC069 |
+| Lee | FLC071 |
+| Leon | FLC073 |
+| Levy | FLC075 |
+| Liberty | FLC077 |
+| Madison | FLC079 |
+| Manatee | FLC081 |
+| Marion | FLC083 |
+| Martin | FLC085 |
+| Miami-Dade | FLC086 |
+| Monroe | FLC087 |
+| Nassau | FLC089 |
+| Okaloosa | FLC091 |
+| Okeechobee | FLC093 |
+| Orange | FLC095 |
+| Osceola | FLC097 |
+| Palm Beach | FLC099 |
+| Pasco | FLC101 |
+| Pinellas | FLC103 |
+| Polk | FLC105 |
+| Putnam | FLC107 |
+| St. Johns | FLC109 |
+| St. Lucie | FLC111 |
+| Santa Rosa | FLC113 |
+| Sarasota | FLC115 |
+| Seminole | FLC117 |
+| Sumter | FLC119 |
+| Suwannee | FLC121 |
+| Taylor | FLC123 |
+| Union | FLC125 |
+| Volusia | FLC127 |
+| Wakulla | FLC129 |
+| Walton | FLC131 |
+| Washington | FLC133 |
+
+## GA
+
+| County | Code |
+|--------|------|
+| Appling | GAC001 |
+| Atkinson | GAC003 |
+| Bacon | GAC005 |
+| Baker | GAC007 |
+| Baldwin | GAC009 |
+| Banks | GAC011 |
+| Barrow | GAC013 |
+| Bartow | GAC015 |
+| Ben Hill | GAC017 |
+| Berrien | GAC019 |
+| Bibb | GAC021 |
+| Bleckley | GAC023 |
+| Brantley | GAC025 |
+| Brooks | GAC027 |
+| Bryan | GAC029 |
+| Bulloch | GAC031 |
+| Burke | GAC033 |
+| Butts | GAC035 |
+| Calhoun | GAC037 |
+| Camden | GAC039 |
+| Candler | GAC043 |
+| Carroll | GAC045 |
+| Catoosa | GAC047 |
+| Charlton | GAC049 |
+| Chatham | GAC051 |
+| Chattahoochee | GAC053 |
+| Chattooga | GAC055 |
+| Cherokee | GAC057 |
+| Clarke | GAC059 |
+| Clay | GAC061 |
+| Clayton | GAC063 |
+| Clinch | GAC065 |
+| Cobb | GAC067 |
+| Coffee | GAC069 |
+| Colquitt | GAC071 |
+| Columbia | GAC073 |
+| Cook | GAC075 |
+| Coweta | GAC077 |
+| Crawford | GAC079 |
+| Crisp | GAC081 |
+| Dade | GAC083 |
+| Dawson | GAC085 |
+| Decatur | GAC087 |
+| DeKalb | GAC089 |
+| Dodge | GAC091 |
+| Dooly | GAC093 |
+| Dougherty | GAC095 |
+| Douglas | GAC097 |
+| Early | GAC099 |
+| Echols | GAC101 |
+| Effingham | GAC103 |
+| Elbert | GAC105 |
+| Emanuel | GAC107 |
+| Evans | GAC109 |
+| Fannin | GAC111 |
+| Fayette | GAC113 |
+| Floyd | GAC115 |
+| Forsyth | GAC117 |
+| Franklin | GAC119 |
+| Fulton | GAC121 |
+| Gilmer | GAC123 |
+| Glascock | GAC125 |
+| Glynn | GAC127 |
+| Gordon | GAC129 |
+| Grady | GAC131 |
+| Greene | GAC133 |
+| Gwinnett | GAC135 |
+| Habersham | GAC137 |
+| Hall | GAC139 |
+| Hancock | GAC141 |
+| Haralson | GAC143 |
+| Harris | GAC145 |
+| Hart | GAC147 |
+| Heard | GAC149 |
+| Henry | GAC151 |
+| Houston | GAC153 |
+| Irwin | GAC155 |
+| Jackson | GAC157 |
+| Jasper | GAC159 |
+| Jeff Davis | GAC161 |
+| Jefferson | GAC163 |
+| Jenkins | GAC165 |
+| Johnson | GAC167 |
+| Jones | GAC169 |
+| Lamar | GAC171 |
+| Lanier | GAC173 |
+| Laurens | GAC175 |
+| Lee | GAC177 |
+| Liberty | GAC179 |
+| Lincoln | GAC181 |
+| Long | GAC183 |
+| Lowndes | GAC185 |
+| Lumpkin | GAC187 |
+| McDuffie | GAC189 |
+| McIntosh | GAC191 |
+| Macon | GAC193 |
+| Madison | GAC195 |
+| Marion | GAC197 |
+| Meriwether | GAC199 |
+| Miller | GAC201 |
+| Mitchell | GAC205 |
+| Monroe | GAC207 |
+| Montgomery | GAC209 |
+| Morgan | GAC211 |
+| Murray | GAC213 |
+| Muscogee | GAC215 |
+| Newton | GAC217 |
+| Oconee | GAC219 |
+| Oglethorpe | GAC221 |
+| Paulding | GAC223 |
+| Peach | GAC225 |
+| Pickens | GAC227 |
+| Pierce | GAC229 |
+| Pike | GAC231 |
+| Polk | GAC233 |
+| Pulaski | GAC235 |
+| Putnam | GAC237 |
+| Quitman | GAC239 |
+| Rabun | GAC241 |
+| Randolph | GAC243 |
+| Richmond | GAC245 |
+| Rockdale | GAC247 |
+| Schley | GAC249 |
+| Screven | GAC251 |
+| Seminole | GAC253 |
+| Spalding | GAC255 |
+| Stephens | GAC257 |
+| Stewart | GAC259 |
+| Sumter | GAC261 |
+| Talbot | GAC263 |
+| Taliaferro | GAC265 |
+| Tattnall | GAC267 |
+| Taylor | GAC269 |
+| Telfair | GAC271 |
+| Terrell | GAC273 |
+| Thomas | GAC275 |
+| Tift | GAC277 |
+| Toombs | GAC279 |
+| Towns | GAC281 |
+| Treutlen | GAC283 |
+| Troup | GAC285 |
+| Turner | GAC287 |
+| Twiggs | GAC289 |
+| Union | GAC291 |
+| Upson | GAC293 |
+| Walker | GAC295 |
+| Walton | GAC297 |
+| Ware | GAC299 |
+| Warren | GAC301 |
+| Washington | GAC303 |
+| Wayne | GAC305 |
+| Webster | GAC307 |
+| Wheeler | GAC309 |
+| White | GAC311 |
+| Whitfield | GAC313 |
+| Wilcox | GAC315 |
+| Wilkes | GAC317 |
+| Wilkinson | GAC319 |
+| Worth | GAC321 |
+
+## HI
+
+| County | Code |
+|--------|------|
+| Hawaii | HIC001 |
+| Honolulu | HIC003 |
+| Kalawao | HIC005 |
+| Kauai | HIC007 |
+| Maui | HIC009 |
+
+## ID
+
+| County | Code |
+|--------|------|
+| Ada | IDC001 |
+| Adams | IDC003 |
+| Bannock | IDC005 |
+| Bear Lake | IDC007 |
+| Benewah | IDC009 |
+| Bingham | IDC011 |
+| Blaine | IDC013 |
+| Boise | IDC015 |
+| Bonner | IDC017 |
+| Bonneville | IDC019 |
+| Boundary | IDC021 |
+| Butte | IDC023 |
+| Camas | IDC025 |
+| Canyon | IDC027 |
+| Caribou | IDC029 |
+| Cassia | IDC031 |
+| Clark | IDC033 |
+| Clearwater | IDC035 |
+| Custer | IDC037 |
+| Elmore | IDC039 |
+| Franklin | IDC041 |
+| Fremont | IDC043 |
+| Gem | IDC045 |
+| Gooding | IDC047 |
+| Idaho | IDC049 |
+| Jefferson | IDC051 |
+| Jerome | IDC053 |
+| Kootenai | IDC055 |
+| Latah | IDC057 |
+| Lemhi | IDC059 |
+| Lewis | IDC061 |
+| Lincoln | IDC063 |
+| Madison | IDC065 |
+| Minidoka | IDC067 |
+| Nez Perce | IDC069 |
+| Oneida | IDC071 |
+| Owyhee | IDC073 |
+| Payette | IDC075 |
+| Power | IDC077 |
+| Shoshone | IDC079 |
+| Teton | IDC081 |
+| Twin Falls | IDC083 |
+| Valley | IDC085 |
+| Washington | IDC087 |
+
+## IL
+
+| County | Code |
+|--------|------|
+| Adams | ILC001 |
+| Alexander | ILC003 |
+| Bond | ILC005 |
+| Boone | ILC007 |
+| Brown | ILC009 |
+| Bureau | ILC011 |
+| Calhoun | ILC013 |
+| Carroll | ILC015 |
+| Cass | ILC017 |
+| Champaign | ILC019 |
+| Christian | ILC021 |
+| Clark | ILC023 |
+| Clay | ILC025 |
+| Clinton | ILC027 |
+| Coles | ILC029 |
+| Cook | ILC031 |
+| Crawford | ILC033 |
+| Cumberland | ILC035 |
+| DeKalb | ILC037 |
+| De Witt | ILC039 |
+| Douglas | ILC041 |
+| DuPage | ILC043 |
+| Edgar | ILC045 |
+| Edwards | ILC047 |
+| Effingham | ILC049 |
+| Fayette | ILC051 |
+| Ford | ILC053 |
+| Franklin | ILC055 |
+| Fulton | ILC057 |
+| Gallatin | ILC059 |
+| Greene | ILC061 |
+| Grundy | ILC063 |
+| Hamilton | ILC065 |
+| Hancock | ILC067 |
+| Hardin | ILC069 |
+| Henderson | ILC071 |
+| Henry | ILC073 |
+| Iroquois | ILC075 |
+| Jackson | ILC077 |
+| Jasper | ILC079 |
+| Jefferson | ILC081 |
+| Jersey | ILC083 |
+| Jo Daviess | ILC085 |
+| Johnson | ILC087 |
+| Kane | ILC089 |
+| Kankakee | ILC091 |
+| Kendall | ILC093 |
+| Knox | ILC095 |
+| Lake | ILC097 |
+| La Salle | ILC099 |
+| Lawrence | ILC101 |
+| Lee | ILC103 |
+| Livingston | ILC105 |
+| Logan | ILC107 |
+| McDonough | ILC109 |
+| McHenry | ILC111 |
+| McLean | ILC113 |
+| Macon | ILC115 |
+| Macoupin | ILC117 |
+| Madison | ILC119 |
+| Marion | ILC121 |
+| Marshall | ILC123 |
+| Mason | ILC125 |
+| Massac | ILC127 |
+| Menard | ILC129 |
+| Mercer | ILC131 |
+| Monroe | ILC133 |
+| Montgomery | ILC135 |
+| Morgan | ILC137 |
+| Moultrie | ILC139 |
+| Ogle | ILC141 |
+| Peoria | ILC143 |
+| Perry | ILC145 |
+| Piatt | ILC147 |
+| Pike | ILC149 |
+| Pope | ILC151 |
+| Pulaski | ILC153 |
+| Putnam | ILC155 |
+| Randolph | ILC157 |
+| Richland | ILC159 |
+| Rock Island | ILC161 |
+| St. Clair | ILC163 |
+| Saline | ILC165 |
+| Sangamon | ILC167 |
+| Schuyler | ILC169 |
+| Scott | ILC171 |
+| Shelby | ILC173 |
+| Stark | ILC175 |
+| Stephenson | ILC177 |
+| Tazewell | ILC179 |
+| Union | ILC181 |
+| Vermilion | ILC183 |
+| Wabash | ILC185 |
+| Warren | ILC187 |
+| Washington | ILC189 |
+| Wayne | ILC191 |
+| White | ILC193 |
+| Whiteside | ILC195 |
+| Will | ILC197 |
+| Williamson | ILC199 |
+| Winnebago | ILC201 |
+| Woodford | ILC203 |
+
+## IN
+
+| County | Code |
+|--------|------|
+| Adams | INC001 |
+| Allen | INC003 |
+| Bartholomew | INC005 |
+| Benton | INC007 |
+| Blackford | INC009 |
+| Boone | INC011 |
+| Brown | INC013 |
+| Carroll | INC015 |
+| Cass | INC017 |
+| Clark | INC019 |
+| Clay | INC021 |
+| Clinton | INC023 |
+| Crawford | INC025 |
+| Daviess | INC027 |
+| Dearborn | INC029 |
+| Decatur | INC031 |
+| De Kalb | INC033 |
+| Delaware | INC035 |
+| Dubois | INC037 |
+| Elkhart | INC039 |
+| Fayette | INC041 |
+| Floyd | INC043 |
+| Fountain | INC045 |
+| Franklin | INC047 |
+| Fulton | INC049 |
+| Gibson | INC051 |
+| Grant | INC053 |
+| Greene | INC055 |
+| Hamilton | INC057 |
+| Hancock | INC059 |
+| Harrison | INC061 |
+| Hendricks | INC063 |
+| Henry | INC065 |
+| Howard | INC067 |
+| Huntington | INC069 |
+| Jackson | INC071 |
+| Jasper | INC073 |
+| Jay | INC075 |
+| Jefferson | INC077 |
+| Jennings | INC079 |
+| Johnson | INC081 |
+| Knox | INC083 |
+| Kosciusko | INC085 |
+| Lagrange | INC087 |
+| Lake | INC089 |
+| La Porte | INC091 |
+| Lawrence | INC093 |
+| Madison | INC095 |
+| Marion | INC097 |
+| Marshall | INC099 |
+| Martin | INC101 |
+| Miami | INC103 |
+| Monroe | INC105 |
+| Montgomery | INC107 |
+| Morgan | INC109 |
+| Newton | INC111 |
+| Noble | INC113 |
+| Ohio | INC115 |
+| Orange | INC117 |
+| Owen | INC119 |
+| Parke | INC121 |
+| Perry | INC123 |
+| Pike | INC125 |
+| Porter | INC127 |
+| Posey | INC129 |
+| Pulaski | INC131 |
+| Putnam | INC133 |
+| Randolph | INC135 |
+| Ripley | INC137 |
+| Rush | INC139 |
+| St. Joseph | INC141 |
+| Scott | INC143 |
+| Shelby | INC145 |
+| Spencer | INC147 |
+| Starke | INC149 |
+| Steuben | INC151 |
+| Sullivan | INC153 |
+| Switzerland | INC155 |
+| Tippecanoe | INC157 |
+| Tipton | INC159 |
+| Union | INC161 |
+| Vanderburgh | INC163 |
+| Vermillion | INC165 |
+| Vigo | INC167 |
+| Wabash | INC169 |
+| Warren | INC171 |
+| Warrick | INC173 |
+| Washington | INC175 |
+| Wayne | INC177 |
+| Wells | INC179 |
+| White | INC181 |
+| Whitley | INC183 |
+
+## IA
+
+| County | Code |
+|--------|------|
+| Adair | IAC001 |
+| Adams | IAC003 |
+| Allamakee | IAC005 |
+| Appanoose | IAC007 |
+| Audubon | IAC009 |
+| Benton | IAC011 |
+| Black Hawk | IAC013 |
+| Boone | IAC015 |
+| Bremer | IAC017 |
+| Buchanan | IAC019 |
+| Buena Vista | IAC021 |
+| Butler | IAC023 |
+| Calhoun | IAC025 |
+| Carroll | IAC027 |
+| Cass | IAC029 |
+| Cedar | IAC031 |
+| Cerro Gordo | IAC033 |
+| Cherokee | IAC035 |
+| Chickasaw | IAC037 |
+| Clarke | IAC039 |
+| Clay | IAC041 |
+| Clayton | IAC043 |
+| Clinton | IAC045 |
+| Crawford | IAC047 |
+| Dallas | IAC049 |
+| Davis | IAC051 |
+| Decatur | IAC053 |
+| Delaware | IAC055 |
+| Des Moines | IAC057 |
+| Dickinson | IAC059 |
+| Dubuque | IAC061 |
+| Emmet | IAC063 |
+| Fayette | IAC065 |
+| Floyd | IAC067 |
+| Franklin | IAC069 |
+| Fremont | IAC071 |
+| Greene | IAC073 |
+| Grundy | IAC075 |
+| Guthrie | IAC077 |
+| Hamilton | IAC079 |
+| Hancock | IAC081 |
+| Hardin | IAC083 |
+| Harrison | IAC085 |
+| Henry | IAC087 |
+| Howard | IAC089 |
+| Humboldt | IAC091 |
+| Ida | IAC093 |
+| Iowa | IAC095 |
+| Jackson | IAC097 |
+| Jasper | IAC099 |
+| Jefferson | IAC101 |
+| Johnson | IAC103 |
+| Jones | IAC105 |
+| Keokuk | IAC107 |
+| Kossuth | IAC109 |
+| Lee | IAC111 |
+| Linn | IAC113 |
+| Louisa | IAC115 |
+| Lucas | IAC117 |
+| Lyon | IAC119 |
+| Madison | IAC121 |
+| Mahaska | IAC123 |
+| Marion | IAC125 |
+| Marshall | IAC127 |
+| Mills | IAC129 |
+| Mitchell | IAC131 |
+| Monona | IAC133 |
+| Monroe | IAC135 |
+| Montgomery | IAC137 |
+| Muscatine | IAC139 |
+| O'Brien | IAC141 |
+| Osceola | IAC143 |
+| Page | IAC145 |
+| Palo Alto | IAC147 |
+| Plymouth | IAC149 |
+| Pocahontas | IAC151 |
+| Polk | IAC153 |
+| Pottawattamie | IAC155 |
+| Poweshiek | IAC157 |
+| Ringgold | IAC159 |
+| Sac | IAC161 |
+| Scott | IAC163 |
+| Shelby | IAC165 |
+| Sioux | IAC167 |
+| Story | IAC169 |
+| Tama | IAC171 |
+| Taylor | IAC173 |
+| Union | IAC175 |
+| Van Buren | IAC177 |
+| Wapello | IAC179 |
+| Warren | IAC181 |
+| Washington | IAC183 |
+| Wayne | IAC185 |
+| Webster | IAC187 |
+| Winnebago | IAC189 |
+| Winneshiek | IAC191 |
+| Woodbury | IAC193 |
+| Worth | IAC195 |
+| Wright | IAC197 |
+
+## KS
+
+| County | Code |
+|--------|------|
+| Allen | KSC001 |
+| Anderson | KSC003 |
+| Atchison | KSC005 |
+| Barber | KSC007 |
+| Barton | KSC009 |
+| Bourbon | KSC011 |
+| Brown | KSC013 |
+| Butler | KSC015 |
+| Chase | KSC017 |
+| Chautauqua | KSC019 |
+| Cherokee | KSC021 |
+| Cheyenne | KSC023 |
+| Clark | KSC025 |
+| Clay | KSC027 |
+| Cloud | KSC029 |
+| Coffey | KSC031 |
+| Comanche | KSC033 |
+| Cowley | KSC035 |
+| Crawford | KSC037 |
+| Decatur | KSC039 |
+| Dickinson | KSC041 |
+| Doniphan | KSC043 |
+| Douglas | KSC045 |
+| Edwards | KSC047 |
+| Elk | KSC049 |
+| Ellis | KSC051 |
+| Ellsworth | KSC053 |
+| Finney | KSC055 |
+| Ford | KSC057 |
+| Franklin | KSC059 |
+| Geary | KSC061 |
+| Gove | KSC063 |
+| Graham | KSC065 |
+| Grant | KSC067 |
+| Gray | KSC069 |
+| Greeley | KSC071 |
+| Greenwood | KSC073 |
+| Hamilton | KSC075 |
+| Harper | KSC077 |
+| Harvey | KSC079 |
+| Haskell | KSC081 |
+| Hodgeman | KSC083 |
+| Jackson | KSC085 |
+| Jefferson | KSC087 |
+| Jewell | KSC089 |
+| Johnson | KSC091 |
+| Kearny | KSC093 |
+| Kingman | KSC095 |
+| Kiowa | KSC097 |
+| Labette | KSC099 |
+| Lane | KSC101 |
+| Leavenworth | KSC103 |
+| Lincoln | KSC105 |
+| Linn | KSC107 |
+| Logan | KSC109 |
+| Lyon | KSC111 |
+| McPherson | KSC113 |
+| Marion | KSC115 |
+| Marshall | KSC117 |
+| Meade | KSC119 |
+| Miami | KSC121 |
+| Mitchell | KSC123 |
+| Montgomery | KSC125 |
+| Morris | KSC127 |
+| Morton | KSC129 |
+| Nemaha | KSC131 |
+| Neosho | KSC133 |
+| Ness | KSC135 |
+| Norton | KSC137 |
+| Osage | KSC139 |
+| Osborne | KSC141 |
+| Ottawa | KSC143 |
+| Pawnee | KSC145 |
+| Phillips | KSC147 |
+| Pottawatomie | KSC149 |
+| Pratt | KSC151 |
+| Rawlins | KSC153 |
+| Reno | KSC155 |
+| Republic | KSC157 |
+| Rice | KSC159 |
+| Riley | KSC161 |
+| Rooks | KSC163 |
+| Rush | KSC165 |
+| Russell | KSC167 |
+| Saline | KSC169 |
+| Scott | KSC171 |
+| Sedgwick | KSC173 |
+| Seward | KSC175 |
+| Shawnee | KSC177 |
+| Sheridan | KSC179 |
+| Sherman | KSC181 |
+| Smith | KSC183 |
+| Stafford | KSC185 |
+| Stanton | KSC187 |
+| Stevens | KSC189 |
+| Sumner | KSC191 |
+| Thomas | KSC193 |
+| Trego | KSC195 |
+| Wabaunsee | KSC197 |
+| Wallace | KSC199 |
+| Washington | KSC201 |
+| Wichita | KSC203 |
+| Wilson | KSC205 |
+| Woodson | KSC207 |
+| Wyandotte | KSC209 |
+
+## KY
+
+| County | Code |
+|--------|------|
+| Adair | KYC001 |
+| Allen | KYC003 |
+| Anderson | KYC005 |
+| Ballard | KYC007 |
+| Barren | KYC009 |
+| Bath | KYC011 |
+| Bell | KYC013 |
+| Boone | KYC015 |
+| Bourbon | KYC017 |
+| Boyd | KYC019 |
+| Boyle | KYC021 |
+| Bracken | KYC023 |
+| Breathitt | KYC025 |
+| Breckinridge | KYC027 |
+| Bullitt | KYC029 |
+| Butler | KYC031 |
+| Caldwell | KYC033 |
+| Calloway | KYC035 |
+| Campbell | KYC037 |
+| Carlisle | KYC039 |
+| Carroll | KYC041 |
+| Carter | KYC043 |
+| Casey | KYC045 |
+| Christian | KYC047 |
+| Clark | KYC049 |
+| Clay | KYC051 |
+| Clinton | KYC053 |
+| Crittenden | KYC055 |
+| Cumberland | KYC057 |
+| Daviess | KYC059 |
+| Edmonson | KYC061 |
+| Elliott | KYC063 |
+| Estill | KYC065 |
+| Fayette | KYC067 |
+| Fleming | KYC069 |
+| Floyd | KYC071 |
+| Franklin | KYC073 |
+| Fulton | KYC075 |
+| Gallatin | KYC077 |
+| Garrard | KYC079 |
+| Grant | KYC081 |
+| Graves | KYC083 |
+| Grayson | KYC085 |
+| Green | KYC087 |
+| Greenup | KYC089 |
+| Hancock | KYC091 |
+| Hardin | KYC093 |
+| Harlan | KYC095 |
+| Harrison | KYC097 |
+| Hart | KYC099 |
+| Henderson | KYC101 |
+| Henry | KYC103 |
+| Hickman | KYC105 |
+| Hopkins | KYC107 |
+| Jackson | KYC109 |
+| Jefferson | KYC111 |
+| Jessamine | KYC113 |
+| Johnson | KYC115 |
+| Kenton | KYC117 |
+| Knott | KYC119 |
+| Knox | KYC121 |
+| Larue | KYC123 |
+| Laurel | KYC125 |
+| Lawrence | KYC127 |
+| Lee | KYC129 |
+| Leslie | KYC131 |
+| Letcher | KYC133 |
+| Lewis | KYC135 |
+| Lincoln | KYC137 |
+| Livingston | KYC139 |
+| Logan | KYC141 |
+| Lyon | KYC143 |
+| McCracken | KYC145 |
+| McCreary | KYC147 |
+| McLean | KYC149 |
+| Madison | KYC151 |
+| Magoffin | KYC153 |
+| Marion | KYC155 |
+| Marshall | KYC157 |
+| Martin | KYC159 |
+| Mason | KYC161 |
+| Meade | KYC163 |
+| Menifee | KYC165 |
+| Mercer | KYC167 |
+| Metcalfe | KYC169 |
+| Monroe | KYC171 |
+| Montgomery | KYC173 |
+| Morgan | KYC175 |
+| Muhlenberg | KYC177 |
+| Nelson | KYC179 |
+| Nicholas | KYC181 |
+| Ohio | KYC183 |
+| Oldham | KYC185 |
+| Owen | KYC187 |
+| Owsley | KYC189 |
+| Pendleton | KYC191 |
+| Perry | KYC193 |
+| Pike | KYC195 |
+| Powell | KYC197 |
+| Pulaski | KYC199 |
+| Robertson | KYC201 |
+| Rockcastle | KYC203 |
+| Rowan | KYC205 |
+| Russell | KYC207 |
+| Scott | KYC209 |
+| Shelby | KYC211 |
+| Simpson | KYC213 |
+| Spencer | KYC215 |
+| Taylor | KYC217 |
+| Todd | KYC219 |
+| Trigg | KYC221 |
+| Trimble | KYC223 |
+| Union | KYC225 |
+| Warren | KYC227 |
+| Washington | KYC229 |
+| Wayne | KYC231 |
+| Webster | KYC233 |
+| Whitley | KYC235 |
+| Wolfe | KYC237 |
+| Woodford | KYC239 |
+
+## LA
+
+| County | Code |
+|--------|------|
+| Acadia | LAC001 |
+| Allen | LAC003 |
+| Ascension | LAC005 |
+| Assumption | LAC007 |
+| Avoyelles | LAC009 |
+| Beauregard | LAC011 |
+| Bienville | LAC013 |
+| Bossier | LAC015 |
+| Caddo | LAC017 |
+| Calcasieu | LAC019 |
+| Caldwell | LAC021 |
+| Cameron | LAC023 |
+| Catahoula | LAC025 |
+| Claiborne | LAC027 |
+| Concordia | LAC029 |
+| De Soto | LAC031 |
+| East Baton Rouge | LAC033 |
+| East Carroll | LAC035 |
+| East Feliciana | LAC037 |
+| Evangeline | LAC039 |
+| Franklin | LAC041 |
+| Grant | LAC043 |
+| Iberia | LAC045 |
+| Iberville | LAC047 |
+| Jackson | LAC049 |
+| Jefferson | LAC051 |
+| Jefferson Davis | LAC053 |
+| Lafayette | LAC055 |
+| Lafourche | LAC057 |
+| La Salle | LAC059 |
+| Lincoln | LAC061 |
+| Livingston | LAC063 |
+| Madison | LAC065 |
+| Morehouse | LAC067 |
+| Natchitoches | LAC069 |
+| Orleans | LAC071 |
+| Ouachita | LAC073 |
+| Plaquemines | LAC075 |
+| Pointe Coupee | LAC077 |
+| Rapides | LAC079 |
+| Red River | LAC081 |
+| Richland | LAC083 |
+| Sabine | LAC085 |
+| St. Bernard | LAC087 |
+| St. Charles | LAC089 |
+| St. Helena | LAC091 |
+| St. James | LAC093 |
+| St. John the Baptist | LAC095 |
+| St. Landry | LAC097 |
+| St. Martin | LAC099 |
+| St. Mary | LAC101 |
+| St. Tammany | LAC103 |
+| Tangipahoa | LAC105 |
+| Tensas | LAC107 |
+| Terrebonne | LAC109 |
+| Union | LAC111 |
+| Vermilion | LAC113 |
+| Vernon | LAC115 |
+| Washington | LAC117 |
+| Webster | LAC119 |
+| West Baton Rouge | LAC121 |
+| West Carroll | LAC123 |
+| West Feliciana | LAC125 |
+| Winn | LAC127 |
+
+## ME
+
+| County | Code |
+|--------|------|
+| Androscoggin | MEC001 |
+| Aroostook | MEC003 |
+| Cumberland | MEC005 |
+| Franklin | MEC007 |
+| Hancock | MEC009 |
+| Kennebec | MEC011 |
+| Knox | MEC013 |
+| Lincoln | MEC015 |
+| Oxford | MEC017 |
+| Penobscot | MEC019 |
+| Piscataquis | MEC021 |
+| Sagadahoc | MEC023 |
+| Somerset | MEC025 |
+| Waldo | MEC027 |
+| Washington | MEC029 |
+| York | MEC031 |
+
+## MD
+
+| County | Code |
+|--------|------|
+| Allegany | MDC001 |
+| Anne Arundel | MDC003 |
+| Baltimore | MDC005 |
+| Calvert | MDC009 |
+| Caroline | MDC011 |
+| Carroll | MDC013 |
+| Cecil | MDC015 |
+| Charles | MDC017 |
+| Dorchester | MDC019 |
+| Frederick | MDC021 |
+| Garrett | MDC023 |
+| Harford | MDC025 |
+| Howard | MDC027 |
+| Kent | MDC029 |
+| Montgomery | MDC031 |
+| Prince George's | MDC033 |
+| Queen Anne's | MDC035 |
+| St. Mary's | MDC037 |
+| Somerset | MDC039 |
+| Talbot | MDC041 |
+| Washington | MDC043 |
+| Wicomico | MDC045 |
+| Worcester | MDC047 |
+| Baltimore (city) | MDC510 |
+
+## MA
+
+| County | Code |
+|--------|------|
+| Barnstable | MAC001 |
+| Berkshire | MAC003 |
+| Bristol | MAC005 |
+| Dukes | MAC007 |
+| Essex | MAC009 |
+| Franklin | MAC011 |
+| Hampden | MAC013 |
+| Hampshire | MAC015 |
+| Middlesex | MAC017 |
+| Nantucket | MAC019 |
+| Norfolk | MAC021 |
+| Plymouth | MAC023 |
+| Suffolk | MAC025 |
+| Worcester | MAC027 |
+
+## MI
+
+| County | Code |
+|--------|------|
+| Alcona | MIC001 |
+| Alger | MIC003 |
+| Allegan | MIC005 |
+| Alpena | MIC007 |
+| Antrim | MIC009 |
+| Arenac | MIC011 |
+| Baraga | MIC013 |
+| Barry | MIC015 |
+| Bay | MIC017 |
+| Benzie | MIC019 |
+| Berrien | MIC021 |
+| Branch | MIC023 |
+| Calhoun | MIC025 |
+| Cass | MIC027 |
+| Charlevoix | MIC029 |
+| Cheboygan | MIC031 |
+| Chippewa | MIC033 |
+| Clare | MIC035 |
+| Clinton | MIC037 |
+| Crawford | MIC039 |
+| Delta | MIC041 |
+| Dickinson | MIC043 |
+| Eaton | MIC045 |
+| Emmet | MIC047 |
+| Genesee | MIC049 |
+| Gladwin | MIC051 |
+| Gogebic | MIC053 |
+| Grand Traverse | MIC055 |
+| Gratiot | MIC057 |
+| Hillsdale | MIC059 |
+| Houghton | MIC061 |
+| Huron | MIC063 |
+| Ingham | MIC065 |
+| Ionia | MIC067 |
+| Iosco | MIC069 |
+| Iron | MIC071 |
+| Isabella | MIC073 |
+| Jackson | MIC075 |
+| Kalamazoo | MIC077 |
+| Kalkaska | MIC079 |
+| Kent | MIC081 |
+| Keweenaw | MIC083 |
+| Lake | MIC085 |
+| Lapeer | MIC087 |
+| Leelanau | MIC089 |
+| Lenawee | MIC091 |
+| Livingston | MIC093 |
+| Luce | MIC095 |
+| Mackinac | MIC097 |
+| Macomb | MIC099 |
+| Manistee | MIC101 |
+| Marquette | MIC103 |
+| Mason | MIC105 |
+| Mecosta | MIC107 |
+| Menominee | MIC109 |
+| Midland | MIC111 |
+| Missaukee | MIC113 |
+| Monroe | MIC115 |
+| Montcalm | MIC117 |
+| Montmorency | MIC119 |
+| Muskegon | MIC121 |
+| Newaygo | MIC123 |
+| Oakland | MIC125 |
+| Oceana | MIC127 |
+| Ogemaw | MIC129 |
+| Ontonagon | MIC131 |
+| Osceola | MIC133 |
+| Oscoda | MIC135 |
+| Otsego | MIC137 |
+| Ottawa | MIC139 |
+| Presque Isle | MIC141 |
+| Roscommon | MIC143 |
+| Saginaw | MIC145 |
+| St. Clair | MIC147 |
+| St. Joseph | MIC149 |
+| Sanilac | MIC151 |
+| Schoolcraft | MIC153 |
+| Shiawassee | MIC155 |
+| Tuscola | MIC157 |
+| Van Buren | MIC159 |
+| Washtenaw | MIC161 |
+| Wayne | MIC163 |
+| Wexford | MIC165 |
+
+## MN
+
+| County | Code |
+|--------|------|
+| Aitkin | MNC001 |
+| Anoka | MNC003 |
+| Becker | MNC005 |
+| Beltrami | MNC007 |
+| Benton | MNC009 |
+| Big Stone | MNC011 |
+| Blue Earth | MNC013 |
+| Brown | MNC015 |
+| Carlton | MNC017 |
+| Carver | MNC019 |
+| Cass | MNC021 |
+| Chippewa | MNC023 |
+| Chisago | MNC025 |
+| Clay | MNC027 |
+| Clearwater | MNC029 |
+| Cook | MNC031 |
+| Cottonwood | MNC033 |
+| Crow Wing | MNC035 |
+| Dakota | MNC037 |
+| Dodge | MNC039 |
+| Douglas | MNC041 |
+| Faribault | MNC043 |
+| Fillmore | MNC045 |
+| Freeborn | MNC047 |
+| Goodhue | MNC049 |
+| Grant | MNC051 |
+| Hennepin | MNC053 |
+| Houston | MNC055 |
+| Hubbard | MNC057 |
+| Isanti | MNC059 |
+| Itasca | MNC061 |
+| Jackson | MNC063 |
+| Kanabec | MNC065 |
+| Kandiyohi | MNC067 |
+| Kittson | MNC069 |
+| Koochiching | MNC071 |
+| Lac qui Parle | MNC073 |
+| Lake | MNC075 |
+| Lake of the Woods | MNC077 |
+| Le Sueur | MNC079 |
+| Lincoln | MNC081 |
+| Lyon | MNC083 |
+| McLeod | MNC085 |
+| Mahnomen | MNC087 |
+| Marshall | MNC089 |
+| Martin | MNC091 |
+| Meeker | MNC093 |
+| Mille Lacs | MNC095 |
+| Morrison | MNC097 |
+| Mower | MNC099 |
+| Murray | MNC101 |
+| Nicollet | MNC103 |
+| Nobles | MNC105 |
+| Norman | MNC107 |
+| Olmsted | MNC109 |
+| Otter Tail | MNC111 |
+| Pennington | MNC113 |
+| Pine | MNC115 |
+| Pipestone | MNC117 |
+| Polk | MNC119 |
+| Pope | MNC121 |
+| Ramsey | MNC123 |
+| Red Lake | MNC125 |
+| Redwood | MNC127 |
+| Renville | MNC129 |
+| Rice | MNC131 |
+| Rock | MNC133 |
+| Roseau | MNC135 |
+| St. Louis | MNC137 |
+| Scott | MNC139 |
+| Sherburne | MNC141 |
+| Sibley | MNC143 |
+| Stearns | MNC145 |
+| Steele | MNC147 |
+| Stevens | MNC149 |
+| Swift | MNC151 |
+| Todd | MNC153 |
+| Traverse | MNC155 |
+| Wabasha | MNC157 |
+| Wadena | MNC159 |
+| Waseca | MNC161 |
+| Washington | MNC163 |
+| Watonwan | MNC165 |
+| Wilkin | MNC167 |
+| Winona | MNC169 |
+| Wright | MNC171 |
+| Yellow Medicine | MNC173 |
+
+## MS
+
+| County | Code |
+|--------|------|
+| Adams | MSC001 |
+| Alcorn | MSC003 |
+| Amite | MSC005 |
+| Attala | MSC007 |
+| Benton | MSC009 |
+| Bolivar | MSC011 |
+| Calhoun | MSC013 |
+| Carroll | MSC015 |
+| Chickasaw | MSC017 |
+| Choctaw | MSC019 |
+| Claiborne | MSC021 |
+| Clarke | MSC023 |
+| Clay | MSC025 |
+| Coahoma | MSC027 |
+| Copiah | MSC029 |
+| Covington | MSC031 |
+| DeSoto | MSC033 |
+| Forrest | MSC035 |
+| Franklin | MSC037 |
+| George | MSC039 |
+| Greene | MSC041 |
+| Grenada | MSC043 |
+| Hancock | MSC045 |
+| Harrison | MSC047 |
+| Hinds | MSC049 |
+| Holmes | MSC051 |
+| Humphreys | MSC053 |
+| Issaquena | MSC055 |
+| Itawamba | MSC057 |
+| Jackson | MSC059 |
+| Jasper | MSC061 |
+| Jefferson | MSC063 |
+| Jefferson Davis | MSC065 |
+| Jones | MSC067 |
+| Kemper | MSC069 |
+| Lafayette | MSC071 |
+| Lamar | MSC073 |
+| Lauderdale | MSC075 |
+| Lawrence | MSC077 |
+| Leake | MSC079 |
+| Lee | MSC081 |
+| Leflore | MSC083 |
+| Lincoln | MSC085 |
+| Lowndes | MSC087 |
+| Madison | MSC089 |
+| Marion | MSC091 |
+| Marshall | MSC093 |
+| Monroe | MSC095 |
+| Montgomery | MSC097 |
+| Neshoba | MSC099 |
+| Newton | MSC101 |
+| Noxubee | MSC103 |
+| Oktibbeha | MSC105 |
+| Panola | MSC107 |
+| Pearl River | MSC109 |
+| Perry | MSC111 |
+| Pike | MSC113 |
+| Pontotoc | MSC115 |
+| Prentiss | MSC117 |
+| Quitman | MSC119 |
+| Rankin | MSC121 |
+| Scott | MSC123 |
+| Sharkey | MSC125 |
+| Simpson | MSC127 |
+| Smith | MSC129 |
+| Stone | MSC131 |
+| Sunflower | MSC133 |
+| Tallahatchie | MSC135 |
+| Tate | MSC137 |
+| Tippah | MSC139 |
+| Tishomingo | MSC141 |
+| Tunica | MSC143 |
+| Union | MSC145 |
+| Walthall | MSC147 |
+| Warren | MSC149 |
+| Washington | MSC151 |
+| Wayne | MSC153 |
+| Webster | MSC155 |
+| Wilkinson | MSC157 |
+| Winston | MSC159 |
+| Yalobusha | MSC161 |
+| Yazoo | MSC163 |
+
+## MO
+
+| County | Code |
+|--------|------|
+| Adair | MOC001 |
+| Andrew | MOC003 |
+| Atchison | MOC005 |
+| Audrain | MOC007 |
+| Barry | MOC009 |
+| Barton | MOC011 |
+| Bates | MOC013 |
+| Benton | MOC015 |
+| Bollinger | MOC017 |
+| Boone | MOC019 |
+| Buchanan | MOC021 |
+| Butler | MOC023 |
+| Caldwell | MOC025 |
+| Callaway | MOC027 |
+| Camden | MOC029 |
+| Cape Girardeau | MOC031 |
+| Carroll | MOC033 |
+| Carter | MOC035 |
+| Cass | MOC037 |
+| Cedar | MOC039 |
+| Chariton | MOC041 |
+| Christian | MOC043 |
+| Clark | MOC045 |
+| Clay | MOC047 |
+| Clinton | MOC049 |
+| Cole | MOC051 |
+| Cooper | MOC053 |
+| Crawford | MOC055 |
+| Dade | MOC057 |
+| Dallas | MOC059 |
+| Daviess | MOC061 |
+| DeKalb | MOC063 |
+| Dent | MOC065 |
+| Douglas | MOC067 |
+| Dunklin | MOC069 |
+| Franklin | MOC071 |
+| Gasconade | MOC073 |
+| Gentry | MOC075 |
+| Greene | MOC077 |
+| Grundy | MOC079 |
+| Harrison | MOC081 |
+| Henry | MOC083 |
+| Hickory | MOC085 |
+| Holt | MOC087 |
+| Howard | MOC089 |
+| Howell | MOC091 |
+| Iron | MOC093 |
+| Jackson | MOC095 |
+| Jasper | MOC097 |
+| Jefferson | MOC099 |
+| Johnson | MOC101 |
+| Knox | MOC103 |
+| Laclede | MOC105 |
+| Lafayette | MOC107 |
+| Lawrence | MOC109 |
+| Lewis | MOC111 |
+| Lincoln | MOC113 |
+| Linn | MOC115 |
+| Livingston | MOC117 |
+| McDonald | MOC119 |
+| Macon | MOC121 |
+| Madison | MOC123 |
+| Maries | MOC125 |
+| Marion | MOC127 |
+| Mercer | MOC129 |
+| Miller | MOC131 |
+| Mississippi | MOC133 |
+| Moniteau | MOC135 |
+| Monroe | MOC137 |
+| Montgomery | MOC139 |
+| Morgan | MOC141 |
+| New Madrid | MOC143 |
+| Newton | MOC145 |
+| Nodaway | MOC147 |
+| Oregon | MOC149 |
+| Osage | MOC151 |
+| Ozark | MOC153 |
+| Pemiscot | MOC155 |
+| Perry | MOC157 |
+| Pettis | MOC159 |
+| Phelps | MOC161 |
+| Pike | MOC163 |
+| Platte | MOC165 |
+| Polk | MOC167 |
+| Pulaski | MOC169 |
+| Putnam | MOC171 |
+| Ralls | MOC173 |
+| Randolph | MOC175 |
+| Ray | MOC177 |
+| Reynolds | MOC179 |
+| Ripley | MOC181 |
+| St. Charles | MOC183 |
+| St. Clair | MOC185 |
+| Ste. Genevieve | MOC186 |
+| St. Francois | MOC187 |
+| St. Louis | MOC189 |
+| Saline | MOC195 |
+| Schuyler | MOC197 |
+| Scotland | MOC199 |
+| Scott | MOC201 |
+| Shannon | MOC203 |
+| Shelby | MOC205 |
+| Stoddard | MOC207 |
+| Stone | MOC209 |
+| Sullivan | MOC211 |
+| Taney | MOC213 |
+| Texas | MOC215 |
+| Vernon | MOC217 |
+| Warren | MOC219 |
+| Washington | MOC221 |
+| Wayne | MOC223 |
+| Webster | MOC225 |
+| Worth | MOC227 |
+| Wright | MOC229 |
+| St. Louis (city) | MOC510 |
+
+## MT
+
+| County | Code |
+|--------|------|
+| Beaverhead | MTC001 |
+| Big Horn | MTC003 |
+| Blaine | MTC005 |
+| Broadwater | MTC007 |
+| Carbon | MTC009 |
+| Carter | MTC011 |
+| Cascade | MTC013 |
+| Chouteau | MTC015 |
+| Custer | MTC017 |
+| Daniels | MTC019 |
+| Dawson | MTC021 |
+| Deer Lodge | MTC023 |
+| Fallon | MTC025 |
+| Fergus | MTC027 |
+| Flathead | MTC029 |
+| Gallatin | MTC031 |
+| Garfield | MTC033 |
+| Glacier | MTC035 |
+| Golden Valley | MTC037 |
+| Granite | MTC039 |
+| Hill | MTC041 |
+| Jefferson | MTC043 |
+| Judith Basin | MTC045 |
+| Lake | MTC047 |
+| Lewis and Clark | MTC049 |
+| Liberty | MTC051 |
+| Lincoln | MTC053 |
+| McCone | MTC055 |
+| Madison | MTC057 |
+| Meagher | MTC059 |
+| Mineral | MTC061 |
+| Missoula | MTC063 |
+| Musselshell | MTC065 |
+| Park | MTC067 |
+| Petroleum | MTC069 |
+| Phillips | MTC071 |
+| Pondera | MTC073 |
+| Powder River | MTC075 |
+| Powell | MTC077 |
+| Prairie | MTC079 |
+| Ravalli | MTC081 |
+| Richland | MTC083 |
+| Roosevelt | MTC085 |
+| Rosebud | MTC087 |
+| Sanders | MTC089 |
+| Sheridan | MTC091 |
+| Silver Bow | MTC093 |
+| Stillwater | MTC095 |
+| Sweet Grass | MTC097 |
+| Teton | MTC099 |
+| Toole | MTC101 |
+| Treasure | MTC103 |
+| Valley | MTC105 |
+| Wheatland | MTC107 |
+| Wibaux | MTC109 |
+| Yellowstone | MTC111 |
+
+## NE
+
+| County | Code |
+|--------|------|
+| Adams | NEC001 |
+| Antelope | NEC003 |
+| Arthur | NEC005 |
+| Banner | NEC007 |
+| Blaine | NEC009 |
+| Boone | NEC011 |
+| Box Butte | NEC013 |
+| Boyd | NEC015 |
+| Brown | NEC017 |
+| Buffalo | NEC019 |
+| Burt | NEC021 |
+| Butler | NEC023 |
+| Cass | NEC025 |
+| Cedar | NEC027 |
+| Chase | NEC029 |
+| Cherry | NEC031 |
+| Cheyenne | NEC033 |
+| Clay | NEC035 |
+| Colfax | NEC037 |
+| Cuming | NEC039 |
+| Custer | NEC041 |
+| Dakota | NEC043 |
+| Dawes | NEC045 |
+| Dawson | NEC047 |
+| Deuel | NEC049 |
+| Dixon | NEC051 |
+| Dodge | NEC053 |
+| Douglas | NEC055 |
+| Dundy | NEC057 |
+| Fillmore | NEC059 |
+| Franklin | NEC061 |
+| Frontier | NEC063 |
+| Furnas | NEC065 |
+| Gage | NEC067 |
+| Garden | NEC069 |
+| Garfield | NEC071 |
+| Gosper | NEC073 |
+| Grant | NEC075 |
+| Greeley | NEC077 |
+| Hall | NEC079 |
+| Hamilton | NEC081 |
+| Harlan | NEC083 |
+| Hayes | NEC085 |
+| Hitchcock | NEC087 |
+| Holt | NEC089 |
+| Hooker | NEC091 |
+| Howard | NEC093 |
+| Jefferson | NEC095 |
+| Johnson | NEC097 |
+| Kearney | NEC099 |
+| Keith | NEC101 |
+| Keya Paha | NEC103 |
+| Kimball | NEC105 |
+| Knox | NEC107 |
+| Lancaster | NEC109 |
+| Lincoln | NEC111 |
+| Logan | NEC113 |
+| Loup | NEC115 |
+| McPherson | NEC117 |
+| Madison | NEC119 |
+| Merrick | NEC121 |
+| Morrill | NEC123 |
+| Nance | NEC125 |
+| Nemaha | NEC127 |
+| Nuckolls | NEC129 |
+| Otoe | NEC131 |
+| Pawnee | NEC133 |
+| Perkins | NEC135 |
+| Phelps | NEC137 |
+| Pierce | NEC139 |
+| Platte | NEC141 |
+| Polk | NEC143 |
+| Red Willow | NEC145 |
+| Richardson | NEC147 |
+| Rock | NEC149 |
+| Saline | NEC151 |
+| Sarpy | NEC153 |
+| Saunders | NEC155 |
+| Scotts Bluff | NEC157 |
+| Seward | NEC159 |
+| Sheridan | NEC161 |
+| Sherman | NEC163 |
+| Sioux | NEC165 |
+| Stanton | NEC167 |
+| Thayer | NEC169 |
+| Thomas | NEC171 |
+| Thurston | NEC173 |
+| Valley | NEC175 |
+| Washington | NEC177 |
+| Wayne | NEC179 |
+| Webster | NEC181 |
+| Wheeler | NEC183 |
+| York | NEC185 |
+
+## NV
+
+| County | Code |
+|--------|------|
+| Churchill | NVC001 |
+| Clark | NVC003 |
+| Douglas | NVC005 |
+| Elko | NVC007 |
+| Esmeralda | NVC009 |
+| Eureka | NVC011 |
+| Humboldt | NVC013 |
+| Lander | NVC015 |
+| Lincoln | NVC017 |
+| Lyon | NVC019 |
+| Mineral | NVC021 |
+| Nye | NVC023 |
+| Pershing | NVC027 |
+| Storey | NVC029 |
+| Washoe | NVC031 |
+| White Pine | NVC033 |
+| Carson (city) | NVC510 |
+
+## NH
+
+| County | Code |
+|--------|------|
+| Belknap | NHC001 |
+| Carroll | NHC003 |
+| Cheshire | NHC005 |
+| Coos | NHC007 |
+| Grafton | NHC009 |
+| Hillsborough | NHC011 |
+| Merrimack | NHC013 |
+| Rockingham | NHC015 |
+| Strafford | NHC017 |
+| Sullivan | NHC019 |
+
+## NJ
+
+| County | Code |
+|--------|------|
+| Atlantic | NJC001 |
+| Bergen | NJC003 |
+| Burlington | NJC005 |
+| Camden | NJC007 |
+| Cape May | NJC009 |
+| Cumberland | NJC011 |
+| Essex | NJC013 |
+| Gloucester | NJC015 |
+| Hudson | NJC017 |
+| Hunterdon | NJC019 |
+| Mercer | NJC021 |
+| Middlesex | NJC023 |
+| Monmouth | NJC025 |
+| Morris | NJC027 |
+| Ocean | NJC029 |
+| Passaic | NJC031 |
+| Salem | NJC033 |
+| Somerset | NJC035 |
+| Sussex | NJC037 |
+| Union | NJC039 |
+| Warren | NJC041 |
+
+## NM
+
+| County | Code |
+|--------|------|
+| Bernalillo | NMC001 |
+| Catron | NMC003 |
+| Chaves | NMC005 |
+| Cibola | NMC006 |
+| Colfax | NMC007 |
+| Curry | NMC009 |
+| DeBaca | NMC011 |
+| Dona Ana | NMC013 |
+| Eddy | NMC015 |
+| Grant | NMC017 |
+| Guadalupe | NMC019 |
+| Harding | NMC021 |
+| Hidalgo | NMC023 |
+| Lea | NMC025 |
+| Lincoln | NMC027 |
+| Los Alamos | NMC028 |
+| Luna | NMC029 |
+| McKinley | NMC031 |
+| Mora | NMC033 |
+| Otero | NMC035 |
+| Quay | NMC037 |
+| Rio Arriba | NMC039 |
+| Roosevelt | NMC041 |
+| Sandoval | NMC043 |
+| San Juan | NMC045 |
+| San Miguel | NMC047 |
+| Santa Fe | NMC049 |
+| Sierra | NMC051 |
+| Socorro | NMC053 |
+| Taos | NMC055 |
+| Torrance | NMC057 |
+| Union | NMC059 |
+| Valencia | NMC061 |
+
+## NY
+
+| County | Code |
+|--------|------|
+| Albany | NYC001 |
+| Allegany | NYC003 |
+| Bronx | NYC005 |
+| Broome | NYC007 |
+| Cattaraugus | NYC009 |
+| Cayuga | NYC011 |
+| Chautauqua | NYC013 |
+| Chemung | NYC015 |
+| Chenango | NYC017 |
+| Clinton | NYC019 |
+| Columbia | NYC021 |
+| Cortland | NYC023 |
+| Delaware | NYC025 |
+| Dutchess | NYC027 |
+| Erie | NYC029 |
+| Essex | NYC031 |
+| Franklin | NYC033 |
+| Fulton | NYC035 |
+| Genesee | NYC037 |
+| Greene | NYC039 |
+| Hamilton | NYC041 |
+| Herkimer | NYC043 |
+| Jefferson | NYC045 |
+| Kings | NYC047 |
+| Lewis | NYC049 |
+| Livingston | NYC051 |
+| Madison | NYC053 |
+| Monroe | NYC055 |
+| Montgomery | NYC057 |
+| Nassau | NYC059 |
+| New York | NYC061 |
+| Niagara | NYC063 |
+| Oneida | NYC065 |
+| Onondaga | NYC067 |
+| Ontario | NYC069 |
+| Orange | NYC071 |
+| Orleans | NYC073 |
+| Oswego | NYC075 |
+| Otsego | NYC077 |
+| Putnam | NYC079 |
+| Queens | NYC081 |
+| Rensselaer | NYC083 |
+| Richmond | NYC085 |
+| Rockland | NYC087 |
+| St. Lawrence | NYC089 |
+| Saratoga | NYC091 |
+| Schenectady | NYC093 |
+| Schoharie | NYC095 |
+| Schuyler | NYC097 |
+| Seneca | NYC099 |
+| Steuben | NYC101 |
+| Suffolk | NYC103 |
+| Sullivan | NYC105 |
+| Tioga | NYC107 |
+| Tompkins | NYC109 |
+| Ulster | NYC111 |
+| Warren | NYC113 |
+| Washington | NYC115 |
+| Wayne | NYC117 |
+| Westchester | NYC119 |
+| Wyoming | NYC121 |
+| Yates | NYC123 |
+
+## NC
+
+| County | Code |
+|--------|------|
+| Alamance | NCC001 |
+| Alexander | NCC003 |
+| Alleghany | NCC005 |
+| Anson | NCC007 |
+| Ashe | NCC009 |
+| Avery | NCC011 |
+| Beaufort | NCC013 |
+| Bertie | NCC015 |
+| Bladen | NCC017 |
+| Brunswick | NCC019 |
+| Buncombe | NCC021 |
+| Burke | NCC023 |
+| Cabarrus | NCC025 |
+| Caldwell | NCC027 |
+| Camden | NCC029 |
+| Carteret | NCC031 |
+| Caswell | NCC033 |
+| Catawba | NCC035 |
+| Chatham | NCC037 |
+| Cherokee | NCC039 |
+| Chowan | NCC041 |
+| Clay | NCC043 |
+| Cleveland | NCC045 |
+| Columbus | NCC047 |
+| Craven | NCC049 |
+| Cumberland | NCC051 |
+| Currituck | NCC053 |
+| Dare | NCC055 |
+| Davidson | NCC057 |
+| Davie | NCC059 |
+| Duplin | NCC061 |
+| Durham | NCC063 |
+| Edgecombe | NCC065 |
+| Forsyth | NCC067 |
+| Franklin | NCC069 |
+| Gaston | NCC071 |
+| Gates | NCC073 |
+| Graham | NCC075 |
+| Granville | NCC077 |
+| Greene | NCC079 |
+| Guilford | NCC081 |
+| Halifax | NCC083 |
+| Harnett | NCC085 |
+| Haywood | NCC087 |
+| Henderson | NCC089 |
+| Hertford | NCC091 |
+| Hoke | NCC093 |
+| Hyde | NCC095 |
+| Iredell | NCC097 |
+| Jackson | NCC099 |
+| Johnston | NCC101 |
+| Jones | NCC103 |
+| Lee | NCC105 |
+| Lenoir | NCC107 |
+| Lincoln | NCC109 |
+| McDowell | NCC111 |
+| Macon | NCC113 |
+| Madison | NCC115 |
+| Martin | NCC117 |
+| Mecklenburg | NCC119 |
+| Mitchell | NCC121 |
+| Montgomery | NCC123 |
+| Moore | NCC125 |
+| Nash | NCC127 |
+| New Hanover | NCC129 |
+| Northampton | NCC131 |
+| Onslow | NCC133 |
+| Orange | NCC135 |
+| Pamlico | NCC137 |
+| Pasquotank | NCC139 |
+| Pender | NCC141 |
+| Perquimans | NCC143 |
+| Person | NCC145 |
+| Pitt | NCC147 |
+| Polk | NCC149 |
+| Randolph | NCC151 |
+| Richmond | NCC153 |
+| Robeson | NCC155 |
+| Rockingham | NCC157 |
+| Rowan | NCC159 |
+| Rutherford | NCC161 |
+| Sampson | NCC163 |
+| Scotland | NCC165 |
+| Stanly | NCC167 |
+| Stokes | NCC169 |
+| Surry | NCC171 |
+| Swain | NCC173 |
+| Transylvania | NCC175 |
+| Tyrrell | NCC177 |
+| Union | NCC179 |
+| Vance | NCC181 |
+| Wake | NCC183 |
+| Warren | NCC185 |
+| Washington | NCC187 |
+| Watauga | NCC189 |
+| Wayne | NCC191 |
+| Wilkes | NCC193 |
+| Wilson | NCC195 |
+| Yadkin | NCC197 |
+| Yancey | NCC199 |
+
+## ND
+
+| County | Code |
+|--------|------|
+| Adams | NDC001 |
+| Barnes | NDC003 |
+| Benson | NDC005 |
+| Billings | NDC007 |
+| Bottineau | NDC009 |
+| Bowman | NDC011 |
+| Burke | NDC013 |
+| Burleigh | NDC015 |
+| Cass | NDC017 |
+| Cavalier | NDC019 |
+| Dickey | NDC021 |
+| Divide | NDC023 |
+| Dunn | NDC025 |
+| Eddy | NDC027 |
+| Emmons | NDC029 |
+| Foster | NDC031 |
+| Golden Valley | NDC033 |
+| Grand Forks | NDC035 |
+| Grant | NDC037 |
+| Griggs | NDC039 |
+| Hettinger | NDC041 |
+| Kidder | NDC043 |
+| LaMoure | NDC045 |
+| Logan | NDC047 |
+| McHenry | NDC049 |
+| McIntosh | NDC051 |
+| McKenzie | NDC053 |
+| McLean | NDC055 |
+| Mercer | NDC057 |
+| Morton | NDC059 |
+| Mountrail | NDC061 |
+| Nelson | NDC063 |
+| Oliver | NDC065 |
+| Pembina | NDC067 |
+| Pierce | NDC069 |
+| Ramsey | NDC071 |
+| Ransom | NDC073 |
+| Renville | NDC075 |
+| Richland | NDC077 |
+| Rolette | NDC079 |
+| Sargent | NDC081 |
+| Sheridan | NDC083 |
+| Sioux | NDC085 |
+| Slope | NDC087 |
+| Stark | NDC089 |
+| Steele | NDC091 |
+| Stutsman | NDC093 |
+| Towner | NDC095 |
+| Traill | NDC097 |
+| Walsh | NDC099 |
+| Ward | NDC101 |
+| Wells | NDC103 |
+| Williams | NDC105 |
+
+## OH
+
+| County | Code |
+|--------|------|
+| Adams | OHC001 |
+| Allen | OHC003 |
+| Ashland | OHC005 |
+| Ashtabula | OHC007 |
+| Athens | OHC009 |
+| Auglaize | OHC011 |
+| Belmont | OHC013 |
+| Brown | OHC015 |
+| Butler | OHC017 |
+| Carroll | OHC019 |
+| Champaign | OHC021 |
+| Clark | OHC023 |
+| Clermont | OHC025 |
+| Clinton | OHC027 |
+| Columbiana | OHC029 |
+| Coshocton | OHC031 |
+| Crawford | OHC033 |
+| Cuyahoga | OHC035 |
+| Darke | OHC037 |
+| Defiance | OHC039 |
+| Delaware | OHC041 |
+| Erie | OHC043 |
+| Fairfield | OHC045 |
+| Fayette | OHC047 |
+| Franklin | OHC049 |
+| Fulton | OHC051 |
+| Gallia | OHC053 |
+| Geauga | OHC055 |
+| Greene | OHC057 |
+| Guernsey | OHC059 |
+| Hamilton | OHC061 |
+| Hancock | OHC063 |
+| Hardin | OHC065 |
+| Harrison | OHC067 |
+| Henry | OHC069 |
+| Highland | OHC071 |
+| Hocking | OHC073 |
+| Holmes | OHC075 |
+| Huron | OHC077 |
+| Jackson | OHC079 |
+| Jefferson | OHC081 |
+| Knox | OHC083 |
+| Lake | OHC085 |
+| Lawrence | OHC087 |
+| Licking | OHC089 |
+| Logan | OHC091 |
+| Lorain | OHC093 |
+| Lucas | OHC095 |
+| Madison | OHC097 |
+| Mahoning | OHC099 |
+| Marion | OHC101 |
+| Medina | OHC103 |
+| Meigs | OHC105 |
+| Mercer | OHC107 |
+| Miami | OHC109 |
+| Monroe | OHC111 |
+| Montgomery | OHC113 |
+| Morgan | OHC115 |
+| Morrow | OHC117 |
+| Muskingum | OHC119 |
+| Noble | OHC121 |
+| Ottawa | OHC123 |
+| Paulding | OHC125 |
+| Perry | OHC127 |
+| Pickaway | OHC129 |
+| Pike | OHC131 |
+| Portage | OHC133 |
+| Preble | OHC135 |
+| Putnam | OHC137 |
+| Richland | OHC139 |
+| Ross | OHC141 |
+| Sandusky | OHC143 |
+| Scioto | OHC145 |
+| Seneca | OHC147 |
+| Shelby | OHC149 |
+| Stark | OHC151 |
+| Summit | OHC153 |
+| Trumbull | OHC155 |
+| Tuscarawas | OHC157 |
+| Union | OHC159 |
+| Van Wert | OHC161 |
+| Vinton | OHC163 |
+| Warren | OHC165 |
+| Washington | OHC167 |
+| Wayne | OHC169 |
+| Williams | OHC171 |
+| Wood | OHC173 |
+| Wyandot | OHC175 |
+
+## OK
+
+| County | Code |
+|--------|------|
+| Adair | OKC001 |
+| Alfalfa | OKC003 |
+| Atoka | OKC005 |
+| Beaver | OKC007 |
+| Beckham | OKC009 |
+| Blaine | OKC011 |
+| Bryan | OKC013 |
+| Caddo | OKC015 |
+| Canadian | OKC017 |
+| Carter | OKC019 |
+| Cherokee | OKC021 |
+| Choctaw | OKC023 |
+| Cimarron | OKC025 |
+| Cleveland | OKC027 |
+| Coal | OKC029 |
+| Comanche | OKC031 |
+| Cotton | OKC033 |
+| Craig | OKC035 |
+| Creek | OKC037 |
+| Custer | OKC039 |
+| Delaware | OKC041 |
+| Dewey | OKC043 |
+| Ellis | OKC045 |
+| Garfield | OKC047 |
+| Garvin | OKC049 |
+| Grady | OKC051 |
+| Grant | OKC053 |
+| Greer | OKC055 |
+| Harmon | OKC057 |
+| Harper | OKC059 |
+| Haskell | OKC061 |
+| Hughes | OKC063 |
+| Jackson | OKC065 |
+| Jefferson | OKC067 |
+| Johnston | OKC069 |
+| Kay | OKC071 |
+| Kingfisher | OKC073 |
+| Kiowa | OKC075 |
+| Latimer | OKC077 |
+| Le Flore | OKC079 |
+| Lincoln | OKC081 |
+| Logan | OKC083 |
+| Love | OKC085 |
+| McClain | OKC087 |
+| McCurtain | OKC089 |
+| McIntosh | OKC091 |
+| Major | OKC093 |
+| Marshall | OKC095 |
+| Mayes | OKC097 |
+| Murray | OKC099 |
+| Muskogee | OKC101 |
+| Noble | OKC103 |
+| Nowata | OKC105 |
+| Okfuskee | OKC107 |
+| Oklahoma | OKC109 |
+| Okmulgee | OKC111 |
+| Osage | OKC113 |
+| Ottawa | OKC115 |
+| Pawnee | OKC117 |
+| Payne | OKC119 |
+| Pittsburg | OKC121 |
+| Pontotoc | OKC123 |
+| Pottawatomie | OKC125 |
+| Pushmataha | OKC127 |
+| Roger Mills | OKC129 |
+| Rogers | OKC131 |
+| Seminole | OKC133 |
+| Sequoyah | OKC135 |
+| Stephens | OKC137 |
+| Texas | OKC139 |
+| Tillman | OKC141 |
+| Tulsa | OKC143 |
+| Wagoner | OKC145 |
+| Washington | OKC147 |
+| Washita | OKC149 |
+| Woods | OKC151 |
+| Woodward | OKC153 |
+
+## OR
+
+| County | Code |
+|--------|------|
+| Baker | ORC001 |
+| Benton | ORC003 |
+| Clackamas | ORC005 |
+| Clatsop | ORC007 |
+| Columbia | ORC009 |
+| Coos | ORC011 |
+| Crook | ORC013 |
+| Curry | ORC015 |
+| Deschutes | ORC017 |
+| Douglas | ORC019 |
+| Gilliam | ORC021 |
+| Grant | ORC023 |
+| Harney | ORC025 |
+| Hood River | ORC027 |
+| Jackson | ORC029 |
+| Jefferson | ORC031 |
+| Josephine | ORC033 |
+| Klamath | ORC035 |
+| Lake | ORC037 |
+| Lane | ORC039 |
+| Lincoln | ORC041 |
+| Linn | ORC043 |
+| Malheur | ORC045 |
+| Marion | ORC047 |
+| Morrow | ORC049 |
+| Multnomah | ORC051 |
+| Polk | ORC053 |
+| Sherman | ORC055 |
+| Tillamook | ORC057 |
+| Umatilla | ORC059 |
+| Union | ORC061 |
+| Wallowa | ORC063 |
+| Wasco | ORC065 |
+| Washington | ORC067 |
+| Wheeler | ORC069 |
+| Yamhill | ORC071 |
+
+## PA
+
+| County | Code |
+|--------|------|
+| Adams | PAC001 |
+| Allegheny | PAC003 |
+| Armstrong | PAC005 |
+| Beaver | PAC007 |
+| Bedford | PAC009 |
+| Berks | PAC011 |
+| Blair | PAC013 |
+| Bradford | PAC015 |
+| Bucks | PAC017 |
+| Butler | PAC019 |
+| Cambria | PAC021 |
+| Cameron | PAC023 |
+| Carbon | PAC025 |
+| Centre | PAC027 |
+| Chester | PAC029 |
+| Clarion | PAC031 |
+| Clearfield | PAC033 |
+| Clinton | PAC035 |
+| Columbia | PAC037 |
+| Crawford | PAC039 |
+| Cumberland | PAC041 |
+| Dauphin | PAC043 |
+| Delaware | PAC045 |
+| Elk | PAC047 |
+| Erie | PAC049 |
+| Fayette | PAC051 |
+| Forest | PAC053 |
+| Franklin | PAC055 |
+| Fulton | PAC057 |
+| Greene | PAC059 |
+| Huntingdon | PAC061 |
+| Indiana | PAC063 |
+| Jefferson | PAC065 |
+| Juniata | PAC067 |
+| Lackawanna | PAC069 |
+| Lancaster | PAC071 |
+| Lawrence | PAC073 |
+| Lebanon | PAC075 |
+| Lehigh | PAC077 |
+| Luzerne | PAC079 |
+| Lycoming | PAC081 |
+| McKean | PAC083 |
+| Mercer | PAC085 |
+| Mifflin | PAC087 |
+| Monroe | PAC089 |
+| Montgomery | PAC091 |
+| Montour | PAC093 |
+| Northampton | PAC095 |
+| Northumberland | PAC097 |
+| Perry | PAC099 |
+| Philadelphia | PAC101 |
+| Pike | PAC103 |
+| Potter | PAC105 |
+| Schuylkill | PAC107 |
+| Snyder | PAC109 |
+| Somerset | PAC111 |
+| Sullivan | PAC113 |
+| Susquehanna | PAC115 |
+| Tioga | PAC117 |
+| Union | PAC119 |
+| Venango | PAC121 |
+| Warren | PAC123 |
+| Washington | PAC125 |
+| Wayne | PAC127 |
+| Westmoreland | PAC129 |
+| Wyoming | PAC131 |
+| York | PAC133 |
+
+## RI
+
+| County | Code |
+|--------|------|
+| Bristol | RIC001 |
+| Kent | RIC003 |
+| Newport | RIC005 |
+| Providence | RIC007 |
+| Washington | RIC009 |
+
+## SC
+
+| County | Code |
+|--------|------|
+| Abbeville | SCC001 |
+| Aiken | SCC003 |
+| Allendale | SCC005 |
+| Anderson | SCC007 |
+| Bamberg | SCC009 |
+| Barnwell | SCC011 |
+| Beaufort | SCC013 |
+| Berkeley | SCC015 |
+| Calhoun | SCC017 |
+| Charleston | SCC019 |
+| Cherokee | SCC021 |
+| Chester | SCC023 |
+| Chesterfield | SCC025 |
+| Clarendon | SCC027 |
+| Colleton | SCC029 |
+| Darlington | SCC031 |
+| Dillon | SCC033 |
+| Dorchester | SCC035 |
+| Edgefield | SCC037 |
+| Fairfield | SCC039 |
+| Florence | SCC041 |
+| Georgetown | SCC043 |
+| Greenville | SCC045 |
+| Greenwood | SCC047 |
+| Hampton | SCC049 |
+| Horry | SCC051 |
+| Jasper | SCC053 |
+| Kershaw | SCC055 |
+| Lancaster | SCC057 |
+| Laurens | SCC059 |
+| Lee | SCC061 |
+| Lexington | SCC063 |
+| McCormick | SCC065 |
+| Marion | SCC067 |
+| Marlboro | SCC069 |
+| Newberry | SCC071 |
+| Oconee | SCC073 |
+| Orangeburg | SCC075 |
+| Pickens | SCC077 |
+| Richland | SCC079 |
+| Saluda | SCC081 |
+| Spartanburg | SCC083 |
+| Sumter | SCC085 |
+| Union | SCC087 |
+| Williamsburg | SCC089 |
+| York | SCC091 |
+
+## SD
+
+| County | Code |
+|--------|------|
+| Aurora | SDC003 |
+| Beadle | SDC005 |
+| Bennett | SDC007 |
+| Bon Homme | SDC009 |
+| Brookings | SDC011 |
+| Brown | SDC013 |
+| Brule | SDC015 |
+| Buffalo | SDC017 |
+| Butte | SDC019 |
+| Campbell | SDC021 |
+| Charles Mix | SDC023 |
+| Clark | SDC025 |
+| Clay | SDC027 |
+| Codington | SDC029 |
+| Corson | SDC031 |
+| Custer | SDC033 |
+| Davison | SDC035 |
+| Day | SDC037 |
+| Deuel | SDC039 |
+| Dewey | SDC041 |
+| Douglas | SDC043 |
+| Edmunds | SDC045 |
+| Fall River | SDC047 |
+| Faulk | SDC049 |
+| Grant | SDC051 |
+| Gregory | SDC053 |
+| Haakon | SDC055 |
+| Hamlin | SDC057 |
+| Hand | SDC059 |
+| Hanson | SDC061 |
+| Harding | SDC063 |
+| Hughes | SDC065 |
+| Hutchinson | SDC067 |
+| Hyde | SDC069 |
+| Jackson | SDC071 |
+| Jerauld | SDC073 |
+| Jones | SDC075 |
+| Kingsbury | SDC077 |
+| Lake | SDC079 |
+| Lawrence | SDC081 |
+| Lincoln | SDC083 |
+| Lyman | SDC085 |
+| McCook | SDC087 |
+| McPherson | SDC089 |
+| Marshall | SDC091 |
+| Meade | SDC093 |
+| Mellette | SDC095 |
+| Miner | SDC097 |
+| Minnehaha | SDC099 |
+| Moody | SDC101 |
+| Pennington | SDC103 |
+| Perkins | SDC105 |
+| Potter | SDC107 |
+| Roberts | SDC109 |
+| Sanborn | SDC111 |
+| Shannon | SDC113 |
+| Spink | SDC115 |
+| Stanley | SDC117 |
+| Sully | SDC119 |
+| Todd | SDC121 |
+| Tripp | SDC123 |
+| Turner | SDC125 |
+| Union | SDC127 |
+| Walworth | SDC129 |
+| Yankton | SDC135 |
+| Ziebach | SDC137 |
+
+## TN
+
+| County | Code |
+|--------|------|
+| Anderson | TNC001 |
+| Bedford | TNC003 |
+| Benton | TNC005 |
+| Bledsoe | TNC007 |
+| Blount | TNC009 |
+| Bradley | TNC011 |
+| Campbell | TNC013 |
+| Cannon | TNC015 |
+| Carroll | TNC017 |
+| Carter | TNC019 |
+| Cheatham | TNC021 |
+| Chester | TNC023 |
+| Claiborne | TNC025 |
+| Clay | TNC027 |
+| Cocke | TNC029 |
+| Coffee | TNC031 |
+| Crockett | TNC033 |
+| Cumberland | TNC035 |
+| Davidson | TNC037 |
+| Decatur | TNC039 |
+| DeKalb | TNC041 |
+| Dickson | TNC043 |
+| Dyer | TNC045 |
+| Fayette | TNC047 |
+| Fentress | TNC049 |
+| Franklin | TNC051 |
+| Gibson | TNC053 |
+| Giles | TNC055 |
+| Grainger | TNC057 |
+| Greene | TNC059 |
+| Grundy | TNC061 |
+| Hamblen | TNC063 |
+| Hamilton | TNC065 |
+| Hancock | TNC067 |
+| Hardeman | TNC069 |
+| Hardin | TNC071 |
+| Hawkins | TNC073 |
+| Haywood | TNC075 |
+| Henderson | TNC077 |
+| Henry | TNC079 |
+| Hickman | TNC081 |
+| Houston | TNC083 |
+| Humphreys | TNC085 |
+| Jackson | TNC087 |
+| Jefferson | TNC089 |
+| Johnson | TNC091 |
+| Knox | TNC093 |
+| Lake | TNC095 |
+| Lauderdale | TNC097 |
+| Lawrence | TNC099 |
+| Lewis | TNC101 |
+| Lincoln | TNC103 |
+| Loudon | TNC105 |
+| McMinn | TNC107 |
+| McNairy | TNC109 |
+| Macon | TNC111 |
+| Madison | TNC113 |
+| Marion | TNC115 |
+| Marshall | TNC117 |
+| Maury | TNC119 |
+| Meigs | TNC121 |
+| Monroe | TNC123 |
+| Montgomery | TNC125 |
+| Moore | TNC127 |
+| Morgan | TNC129 |
+| Obion | TNC131 |
+| Overton | TNC133 |
+| Perry | TNC135 |
+| Pickett | TNC137 |
+| Polk | TNC139 |
+| Putnam | TNC141 |
+| Rhea | TNC143 |
+| Roane | TNC145 |
+| Robertson | TNC147 |
+| Rutherford | TNC149 |
+| Scott | TNC151 |
+| Sequatchie | TNC153 |
+| Sevier | TNC155 |
+| Shelby | TNC157 |
+| Smith | TNC159 |
+| Stewart | TNC161 |
+| Sullivan | TNC163 |
+| Sumner | TNC165 |
+| Tipton | TNC167 |
+| Trousdale | TNC169 |
+| Unicoi | TNC171 |
+| Union | TNC173 |
+| Van Buren | TNC175 |
+| Warren | TNC177 |
+| Washington | TNC179 |
+| Wayne | TNC181 |
+| Weakley | TNC183 |
+| White | TNC185 |
+| Williamson | TNC187 |
+| Wilson | TNC189 |
+
+## TX
+
+| County | Code |
+|--------|------|
+| Anderson | TXC001 |
+| Andrews | TXC003 |
+| Angelina | TXC005 |
+| Aransas | TXC007 |
+| Archer | TXC009 |
+| Armstrong | TXC011 |
+| Atascosa | TXC013 |
+| Austin | TXC015 |
+| Bailey | TXC017 |
+| Bandera | TXC019 |
+| Bastrop | TXC021 |
+| Baylor | TXC023 |
+| Bee | TXC025 |
+| Bell | TXC027 |
+| Bexar | TXC029 |
+| Blanco | TXC031 |
+| Borden | TXC033 |
+| Bosque | TXC035 |
+| Bowie | TXC037 |
+| Brazoria | TXC039 |
+| Brazos | TXC041 |
+| Brewster | TXC043 |
+| Briscoe | TXC045 |
+| Brooks | TXC047 |
+| Brown | TXC049 |
+| Burleson | TXC051 |
+| Burnet | TXC053 |
+| Caldwell | TXC055 |
+| Calhoun | TXC057 |
+| Callahan | TXC059 |
+| Cameron | TXC061 |
+| Camp | TXC063 |
+| Carson | TXC065 |
+| Cass | TXC067 |
+| Castro | TXC069 |
+| Chambers | TXC071 |
+| Cherokee | TXC073 |
+| Childress | TXC075 |
+| Clay | TXC077 |
+| Cochran | TXC079 |
+| Coke | TXC081 |
+| Coleman | TXC083 |
+| Collin | TXC085 |
+| Collingsworth | TXC087 |
+| Colorado | TXC089 |
+| Comal | TXC091 |
+| Comanche | TXC093 |
+| Concho | TXC095 |
+| Cooke | TXC097 |
+| Coryell | TXC099 |
+| Cottle | TXC101 |
+| Crane | TXC103 |
+| Crockett | TXC105 |
+| Crosby | TXC107 |
+| Culberson | TXC109 |
+| Dallam | TXC111 |
+| Dallas | TXC113 |
+| Dawson | TXC115 |
+| Deaf Smith | TXC117 |
+| Delta | TXC119 |
+| Denton | TXC121 |
+| DeWitt | TXC123 |
+| Dickens | TXC125 |
+| Dimmit | TXC127 |
+| Donley | TXC129 |
+| Duval | TXC131 |
+| Eastland | TXC133 |
+| Ector | TXC135 |
+| Edwards | TXC137 |
+| Ellis | TXC139 |
+| El Paso | TXC141 |
+| Erath | TXC143 |
+| Falls | TXC145 |
+| Fannin | TXC147 |
+| Fayette | TXC149 |
+| Fisher | TXC151 |
+| Floyd | TXC153 |
+| Foard | TXC155 |
+| Fort Bend | TXC157 |
+| Franklin | TXC159 |
+| Freestone | TXC161 |
+| Frio | TXC163 |
+| Gaines | TXC165 |
+| Galveston | TXC167 |
+| Garza | TXC169 |
+| Gillespie | TXC171 |
+| Glasscock | TXC173 |
+| Goliad | TXC175 |
+| Gonzales | TXC177 |
+| Gray | TXC179 |
+| Grayson | TXC181 |
+| Gregg | TXC183 |
+| Grimes | TXC185 |
+| Guadalupe | TXC187 |
+| Hale | TXC189 |
+| Hall | TXC191 |
+| Hamilton | TXC193 |
+| Hansford | TXC195 |
+| Hardeman | TXC197 |
+| Hardin | TXC199 |
+| Harris | TXC201 |
+| Harrison | TXC203 |
+| Hartley | TXC205 |
+| Haskell | TXC207 |
+| Hays | TXC209 |
+| Hemphill | TXC211 |
+| Henderson | TXC213 |
+| Hidalgo | TXC215 |
+| Hill | TXC217 |
+| Hockley | TXC219 |
+| Hood | TXC221 |
+| Hopkins | TXC223 |
+| Houston | TXC225 |
+| Howard | TXC227 |
+| Hudspeth | TXC229 |
+| Hunt | TXC231 |
+| Hutchinson | TXC233 |
+| Irion | TXC235 |
+| Jack | TXC237 |
+| Jackson | TXC239 |
+| Jasper | TXC241 |
+| Jeff Davis | TXC243 |
+| Jefferson | TXC245 |
+| Jim Hogg | TXC247 |
+| Jim Wells | TXC249 |
+| Johnson | TXC251 |
+| Jones | TXC253 |
+| Karnes | TXC255 |
+| Kaufman | TXC257 |
+| Kendall | TXC259 |
+| Kenedy | TXC261 |
+| Kent | TXC263 |
+| Kerr | TXC265 |
+| Kimble | TXC267 |
+| King | TXC269 |
+| Kinney | TXC271 |
+| Kleberg | TXC273 |
+| Knox | TXC275 |
+| Lamar | TXC277 |
+| Lamb | TXC279 |
+| Lampasas | TXC281 |
+| La Salle | TXC283 |
+| Lavaca | TXC285 |
+| Lee | TXC287 |
+| Leon | TXC289 |
+| Liberty | TXC291 |
+| Limestone | TXC293 |
+| Lipscomb | TXC295 |
+| Live Oak | TXC297 |
+| Llano | TXC299 |
+| Loving | TXC301 |
+| Lubbock | TXC303 |
+| Lynn | TXC305 |
+| McCulloch | TXC307 |
+| McLennan | TXC309 |
+| McMullen | TXC311 |
+| Madison | TXC313 |
+| Marion | TXC315 |
+| Martin | TXC317 |
+| Mason | TXC319 |
+| Matagorda | TXC321 |
+| Maverick | TXC323 |
+| Medina | TXC325 |
+| Menard | TXC327 |
+| Midland | TXC329 |
+| Milam | TXC331 |
+| Mills | TXC333 |
+| Mitchell | TXC335 |
+| Montague | TXC337 |
+| Montgomery | TXC339 |
+| Moore | TXC341 |
+| Morris | TXC343 |
+| Motley | TXC345 |
+| Nacogdoches | TXC347 |
+| Navarro | TXC349 |
+| Newton | TXC351 |
+| Nolan | TXC353 |
+| Nueces | TXC355 |
+| Ochiltree | TXC357 |
+| Oldham | TXC359 |
+| Orange | TXC361 |
+| Palo Pinto | TXC363 |
+| Panola | TXC365 |
+| Parker | TXC367 |
+| Parmer | TXC369 |
+| Pecos | TXC371 |
+| Polk | TXC373 |
+| Potter | TXC375 |
+| Presidio | TXC377 |
+| Rains | TXC379 |
+| Randall | TXC381 |
+| Reagan | TXC383 |
+| Real | TXC385 |
+| Red River | TXC387 |
+| Reeves | TXC389 |
+| Refugio | TXC391 |
+| Roberts | TXC393 |
+| Robertson | TXC395 |
+| Rockwall | TXC397 |
+| Runnels | TXC399 |
+| Rusk | TXC401 |
+| Sabine | TXC403 |
+| San Augustine | TXC405 |
+| San Jacinto | TXC407 |
+| San Patricio | TXC409 |
+| San Saba | TXC411 |
+| Schleicher | TXC413 |
+| Scurry | TXC415 |
+| Shackelford | TXC417 |
+| Shelby | TXC419 |
+| Sherman | TXC421 |
+| Smith | TXC423 |
+| Somervell | TXC425 |
+| Starr | TXC427 |
+| Stephens | TXC429 |
+| Sterling | TXC431 |
+| Stonewall | TXC433 |
+| Sutton | TXC435 |
+| Swisher | TXC437 |
+| Tarrant | TXC439 |
+| Taylor | TXC441 |
+| Terrell | TXC443 |
+| Terry | TXC445 |
+| Throckmorton | TXC447 |
+| Titus | TXC449 |
+| Tom Green | TXC451 |
+| Travis | TXC453 |
+| Trinity | TXC455 |
+| Tyler | TXC457 |
+| Upshur | TXC459 |
+| Upton | TXC461 |
+| Uvalde | TXC463 |
+| Val Verde | TXC465 |
+| Van Zandt | TXC467 |
+| Victoria | TXC469 |
+| Walker | TXC471 |
+| Waller | TXC473 |
+| Ward | TXC475 |
+| Washington | TXC477 |
+| Webb | TXC479 |
+| Wharton | TXC481 |
+| Wheeler | TXC483 |
+| Wichita | TXC485 |
+| Wilbarger | TXC487 |
+| Willacy | TXC489 |
+| Williamson | TXC491 |
+| Wilson | TXC493 |
+| Winkler | TXC495 |
+| Wise | TXC497 |
+| Wood | TXC499 |
+| Yoakum | TXC501 |
+| Young | TXC503 |
+| Zapata | TXC505 |
+| Zavala | TXC507 |
+
+## UT
+
+| County | Code |
+|--------|------|
+| Beaver | UTC001 |
+| Box Elder | UTC003 |
+| Cache | UTC005 |
+| Carbon | UTC007 |
+| Daggett | UTC009 |
+| Davis | UTC011 |
+| Duchesne | UTC013 |
+| Emery | UTC015 |
+| Garfield | UTC017 |
+| Grand | UTC019 |
+| Iron | UTC021 |
+| Juab | UTC023 |
+| Kane | UTC025 |
+| Millard | UTC027 |
+| Morgan | UTC029 |
+| Piute | UTC031 |
+| Rich | UTC033 |
+| Salt Lake | UTC035 |
+| San Juan | UTC037 |
+| Sanpete | UTC039 |
+| Sevier | UTC041 |
+| Summit | UTC043 |
+| Tooele | UTC045 |
+| Uintah | UTC047 |
+| Utah | UTC049 |
+| Wasatch | UTC051 |
+| Washington | UTC053 |
+| Wayne | UTC055 |
+| Weber | UTC057 |
+
+## VT
+
+| County | Code |
+|--------|------|
+| Addison | VTC001 |
+| Bennington | VTC003 |
+| Caledonia | VTC005 |
+| Chittenden | VTC007 |
+| Essex | VTC009 |
+| Franklin | VTC011 |
+| Grand Isle | VTC013 |
+| Lamoille | VTC015 |
+| Orange | VTC017 |
+| Orleans | VTC019 |
+| Rutland | VTC021 |
+| Washington | VTC023 |
+| Windham | VTC025 |
+| Windsor | VTC027 |
+
+## VA
+
+| County | Code |
+|--------|------|
+| Accomack | VAC001 |
+| Albemarle | VAC003 |
+| Alleghany | VAC005 |
+| Amelia | VAC007 |
+| Amherst | VAC009 |
+| Appomattox | VAC011 |
+| Arlington | VAC013 |
+| Augusta | VAC015 |
+| Bath | VAC017 |
+| Bedford | VAC019 |
+| Bland | VAC021 |
+| Botetourt | VAC023 |
+| Brunswick | VAC025 |
+| Buchanan | VAC027 |
+| Buckingham | VAC029 |
+| Campbell | VAC031 |
+| Caroline | VAC033 |
+| Carroll | VAC035 |
+| Charles City (city) | VAC036 |
+| Charlotte | VAC037 |
+| Chesterfield | VAC041 |
+| Clarke | VAC043 |
+| Craig | VAC045 |
+| Culpeper | VAC047 |
+| Cumberland | VAC049 |
+| Dickenson | VAC051 |
+| Dinwiddie | VAC053 |
+| Essex | VAC057 |
+| Fairfax | VAC059 |
+| Fauquier | VAC061 |
+| Floyd | VAC063 |
+| Fluvanna | VAC065 |
+| Franklin | VAC067 |
+| Frederick | VAC069 |
+| Giles | VAC071 |
+| Gloucester | VAC073 |
+| Goochland | VAC075 |
+| Grayson | VAC077 |
+| Greene | VAC079 |
+| Greensville | VAC081 |
+| Halifax | VAC083 |
+| Hanover | VAC085 |
+| Henrico | VAC087 |
+| Henry | VAC089 |
+| Highland | VAC091 |
+| Isle of Wight | VAC093 |
+| James City (city) | VAC095 |
+| King and Queen | VAC097 |
+| King George | VAC099 |
+| King William | VAC101 |
+| Lancaster | VAC103 |
+| Lee | VAC105 |
+| Loudoun | VAC107 |
+| Louisa | VAC109 |
+| Lunenburg | VAC111 |
+| Madison | VAC113 |
+| Mathews | VAC115 |
+| Mecklenburg | VAC117 |
+| Middlesex | VAC119 |
+| Montgomery | VAC121 |
+| Nelson | VAC125 |
+| New Kent | VAC127 |
+| Northampton | VAC131 |
+| Northumberland | VAC133 |
+| Nottoway | VAC135 |
+| Orange | VAC137 |
+| Page | VAC139 |
+| Patrick | VAC141 |
+| Pittsylvania | VAC143 |
+| Powhatan | VAC145 |
+| Prince Edward | VAC147 |
+| Prince George | VAC149 |
+| Prince William | VAC153 |
+| Pulaski | VAC155 |
+| Rappahannock | VAC157 |
+| Richmond | VAC159 |
+| Roanoke | VAC161 |
+| Rockbridge | VAC163 |
+| Rockingham | VAC165 |
+| Russell | VAC167 |
+| Scott | VAC169 |
+| Shenandoah | VAC171 |
+| Smyth | VAC173 |
+| Southampton | VAC175 |
+| Spotsylvania | VAC177 |
+| Stafford | VAC179 |
+| Surry | VAC181 |
+| Sussex | VAC183 |
+| Tazewell | VAC185 |
+| Warren | VAC187 |
+| Washington | VAC191 |
+| Westmoreland | VAC193 |
+| Wise | VAC195 |
+| Wythe | VAC197 |
+| York | VAC199 |
+| Alexandria (city) | VAC510 |
+| Bedford (city) | VAC515 |
+| Bristol (city) | VAC520 |
+| Buena Vista (city) | VAC530 |
+| Charlottesville (city) | VAC540 |
+| Chesapeake (city) | VAC550 |
+| Clifton Forge (city) | VAC560 |
+| Colonial Heights (city) | VAC570 |
+| Covington (city) | VAC580 |
+| Danville (city) | VAC590 |
+| Emporia (city) | VAC595 |
+| Fairfax (city) | VAC600 |
+| Falls Church (city) | VAC610 |
+| Franklin (city) | VAC620 |
+| Fredericksburg (city) | VAC630 |
+| Galax (city) | VAC640 |
+| Hampton (city) | VAC650 |
+| Harrisonburg (city) | VAC660 |
+| Hopewell (city) | VAC670 |
+| Lexington (city) | VAC678 |
+| Lynchburg (city) | VAC680 |
+| Manassas (city) | VAC683 |
+| Manassas Park (city) | VAC685 |
+| Martinsville (city) | VAC690 |
+| Newport News (city) | VAC700 |
+| Norfolk (city) | VAC710 |
+| Norton (city) | VAC720 |
+| Petersburg (city) | VAC730 |
+| Poquoson (city) | VAC735 |
+| Portsmouth (city) | VAC740 |
+| Radford (city) | VAC750 |
+| Richmond (city) | VAC760 |
+| Roanoke (city) | VAC770 |
+| Salem (city) | VAC775 |
+| Staunton (city) | VAC790 |
+| Suffolk (city) | VAC800 |
+| Virginia Beach (city) | VAC810 |
+| Waynesboro (city) | VAC820 |
+| Williamsburg (city) | VAC830 |
+| Winchester (city) | VAC840 |
+
+## WA
+
+| County | Code |
+|--------|------|
+| Adams | WAC001 |
+| Asotin | WAC003 |
+| Benton | WAC005 |
+| Chelan | WAC007 |
+| Clallam | WAC009 |
+| Clark | WAC011 |
+| Columbia | WAC013 |
+| Cowlitz | WAC015 |
+| Douglas | WAC017 |
+| Ferry | WAC019 |
+| Franklin | WAC021 |
+| Garfield | WAC023 |
+| Grant | WAC025 |
+| Grays Harbor | WAC027 |
+| Island | WAC029 |
+| Jefferson | WAC031 |
+| King | WAC033 |
+| Kitsap | WAC035 |
+| Kittitas | WAC037 |
+| Klickitat | WAC039 |
+| Lewis | WAC041 |
+| Lincoln | WAC043 |
+| Mason | WAC045 |
+| Okanogan | WAC047 |
+| Pacific | WAC049 |
+| Pend Oreille | WAC051 |
+| Pierce | WAC053 |
+| San Juan | WAC055 |
+| Skagit | WAC057 |
+| Skamania | WAC059 |
+| Snohomish | WAC061 |
+| Spokane | WAC063 |
+| Stevens | WAC065 |
+| Thurston | WAC067 |
+| Wahkiakum | WAC069 |
+| Walla Walla | WAC071 |
+| Whatcom | WAC073 |
+| Whitman | WAC075 |
+| Yakima | WAC077 |
+
+## WV
+
+| County | Code |
+|--------|------|
+| Barbour | WVC001 |
+| Berkeley | WVC003 |
+| Boone | WVC005 |
+| Braxton | WVC007 |
+| Brooke | WVC009 |
+| Cabell | WVC011 |
+| Calhoun | WVC013 |
+| Clay | WVC015 |
+| Doddridge | WVC017 |
+| Fayette | WVC019 |
+| Gilmer | WVC021 |
+| Grant | WVC023 |
+| Greenbrier | WVC025 |
+| Hampshire | WVC027 |
+| Hancock | WVC029 |
+| Hardy | WVC031 |
+| Harrison | WVC033 |
+| Jackson | WVC035 |
+| Jefferson | WVC037 |
+| Kanawha | WVC039 |
+| Lewis | WVC041 |
+| Lincoln | WVC043 |
+| Logan | WVC045 |
+| McDowell | WVC047 |
+| Marion | WVC049 |
+| Marshall | WVC051 |
+| Mason | WVC053 |
+| Mercer | WVC055 |
+| Mineral | WVC057 |
+| Mingo | WVC059 |
+| Monongalia | WVC061 |
+| Monroe | WVC063 |
+| Morgan | WVC065 |
+| Nicholas | WVC067 |
+| Ohio | WVC069 |
+| Pendleton | WVC071 |
+| Pleasants | WVC073 |
+| Pocahontas | WVC075 |
+| Preston | WVC077 |
+| Putnam | WVC079 |
+| Raleigh | WVC081 |
+| Randolph | WVC083 |
+| Ritchie | WVC085 |
+| Roane | WVC087 |
+| Summers | WVC089 |
+| Taylor | WVC091 |
+| Tucker | WVC093 |
+| Tyler | WVC095 |
+| Upshur | WVC097 |
+| Wayne | WVC099 |
+| Webster | WVC101 |
+| Wetzel | WVC103 |
+| Wirt | WVC105 |
+| Wood | WVC107 |
+| Wyoming | WVC109 |
+
+## WI
+
+| County | Code |
+|--------|------|
+| Adams | WIC001 |
+| Ashland | WIC003 |
+| Barron | WIC005 |
+| Bayfield | WIC007 |
+| Brown | WIC009 |
+| Buffalo | WIC011 |
+| Burnett | WIC013 |
+| Calumet | WIC015 |
+| Chippewa | WIC017 |
+| Clark | WIC019 |
+| Columbia | WIC021 |
+| Crawford | WIC023 |
+| Dane | WIC025 |
+| Dodge | WIC027 |
+| Door | WIC029 |
+| Douglas | WIC031 |
+| Dunn | WIC033 |
+| Eau Claire | WIC035 |
+| Florence | WIC037 |
+| Fond du Lac | WIC039 |
+| Forest | WIC041 |
+| Grant | WIC043 |
+| Green | WIC045 |
+| Green Lake | WIC047 |
+| Iowa | WIC049 |
+| Iron | WIC051 |
+| Jackson | WIC053 |
+| Jefferson | WIC055 |
+| Juneau | WIC057 |
+| Kenosha | WIC059 |
+| Kewaunee | WIC061 |
+| La Crosse | WIC063 |
+| Lafayette | WIC065 |
+| Langlade | WIC067 |
+| Lincoln | WIC069 |
+| Manitowoc | WIC071 |
+| Marathon | WIC073 |
+| Marinette | WIC075 |
+| Marquette | WIC077 |
+| Menominee | WIC078 |
+| Milwaukee | WIC079 |
+| Monroe | WIC081 |
+| Oconto | WIC083 |
+| Oneida | WIC085 |
+| Outagamie | WIC087 |
+| Ozaukee | WIC089 |
+| Pepin | WIC091 |
+| Pierce | WIC093 |
+| Polk | WIC095 |
+| Portage | WIC097 |
+| Price | WIC099 |
+| Racine | WIC101 |
+| Richland | WIC103 |
+| Rock | WIC105 |
+| Rusk | WIC107 |
+| St. Croix | WIC109 |
+| Sauk | WIC111 |
+| Sawyer | WIC113 |
+| Shawano | WIC115 |
+| Sheboygan | WIC117 |
+| Taylor | WIC119 |
+| Trempealeau | WIC121 |
+| Vernon | WIC123 |
+| Vilas | WIC125 |
+| Walworth | WIC127 |
+| Washburn | WIC129 |
+| Washington | WIC131 |
+| Waukesha | WIC133 |
+| Waupaca | WIC135 |
+| Waushara | WIC137 |
+| Winnebago | WIC139 |
+| Wood | WIC141 |
+
+## WY
+
+| County | Code |
+|--------|------|
+| Albany | WYC001 |
+| Big Horn | WYC003 |
+| Campbell | WYC005 |
+| Carbon | WYC007 |
+| Converse | WYC009 |
+| Crook | WYC011 |
+| Fremont | WYC013 |
+| Goshen | WYC015 |
+| Hot Springs | WYC017 |
+| Johnson | WYC019 |
+| Laramie | WYC021 |
+| Lincoln | WYC023 |
+| Natrona | WYC025 |
+| Niobrara | WYC027 |
+| Park | WYC029 |
+| Platte | WYC031 |
+| Sheridan | WYC033 |
+| Sublette | WYC035 |
+| Sweetwater | WYC037 |
+| Teton | WYC039 |
+| Uinta | WYC041 |
+| Washakie | WYC043 |
+| Weston | WYC045 |
+
diff --git a/CountyIDGen.py b/CountyIDGen.py
new file mode 100644
index 0000000..4790635
--- /dev/null
+++ b/CountyIDGen.py
@@ -0,0 +1,287 @@
+#!/usr/bin/python3
+
+"""
+CountyIDGen.py by Mason Nelson
+===============================================================================
+This script is a utility for generating WAV audio files corresponding to each
+county code defined in the SkywarnPlus config.yaml. The audio files are generated
+using the Voice RSS Text-to-Speech API and the settings defined in the config.yaml.
+
+This script will generate the files, save them in the correct location, and automatically
+modify the SkywarnPlus config.yaml to utilize them.
+
+This file is part of SkywarnPlus.
+SkywarnPlus is free software: you can redistribute it and/or modify it under the terms of
+the GNU General Public License as published by the Free Software Foundation, either version 3
+of the License, or (at your option) any later version. SkywarnPlus is distributed in the hope
+that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+You should have received a copy of the GNU General Public License along with SkywarnPlus. If not, see .
+"""
+
+import os
+import io
+import re
+import sys
+import requests
+import logging
+import zipfile
+from datetime import datetime
+from ruamel.yaml import YAML
+from pydub import AudioSegment
+from pydub.silence import split_on_silence
+
+# Initialize YAML
+yaml = YAML()
+
+# Directories and Paths
+BASE_DIR = os.path.dirname(os.path.realpath(__file__))
+CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml")
+COUNTY_CODES_PATH = os.path.join(BASE_DIR, "CountyCodes.md")
+
+# Load configurations
+with open(CONFIG_PATH, "r") as config_file:
+ config = yaml.load(config_file)
+
+# Logging setup
+LOG_CONFIG = config.get("Logging", {})
+ENABLE_DEBUG = LOG_CONFIG.get("Debug", False)
+LOG_FILE = LOG_CONFIG.get("LogPath", os.path.join(BASE_DIR, "SkywarnPlus.log"))
+
+# Set up logging
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG if ENABLE_DEBUG else logging.INFO)
+
+# Set up log message formatting
+LOG_FORMATTER = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
+
+# Set up console log handler
+C_HANDLER = logging.StreamHandler()
+C_HANDLER.setFormatter(LOG_FORMATTER)
+LOGGER.addHandler(C_HANDLER)
+
+# Ensure the directory for the log file exists
+log_directory = os.path.dirname(LOG_FILE)
+if not os.path.exists(log_directory):
+ os.makedirs(log_directory)
+
+# Set up file log handler
+F_HANDLER = logging.FileHandler(LOG_FILE)
+F_HANDLER.setFormatter(LOG_FORMATTER)
+LOGGER.addHandler(F_HANDLER)
+
+# Extract API parameters from the config
+API_KEY = config["SkyDescribe"]["APIKey"]
+LANGUAGE = config["SkyDescribe"]["Language"]
+SPEED = str(config["SkyDescribe"]["Speed"])
+VOICE = config["SkyDescribe"]["Voice"]
+SOUNDS_PATH = config.get("Alerting", {}).get(
+ "SoundsPath", os.path.join(BASE_DIR, "SOUNDS")
+)
+
+
+def generate_wav(api_key, language, speed, voice, text, output_file):
+ """
+ Convert the given text to audio using the Voice RSS Text-to-Speech API and trims silence.
+ """
+ base_url = "http://api.voicerss.org/"
+ params = {
+ "key": api_key,
+ "hl": language,
+ "src": text,
+ "c": "WAV",
+ "f": "8khz_16bit_mono",
+ "r": str(speed),
+ "v": voice,
+ }
+
+ response = requests.get(base_url, params=params)
+ response.raise_for_status()
+
+ # If the response text contains "ERROR" then log it and exit
+ if "ERROR" in response.text:
+ LOGGER.error("SkyDescribe: %s", response.text)
+ sys.exit(1)
+
+ # Load the audio data into pydub's AudioSegment
+ sound = AudioSegment.from_wav(io.BytesIO(response.content))
+
+ # Normalize the entire audio clip
+ target_dBFS = -6.0
+ gain_difference = target_dBFS - sound.max_dBFS
+ sound = sound.apply_gain(gain_difference)
+
+ # Split track where silence is 100ms or more and get chunks
+ chunks = split_on_silence(sound, min_silence_len=200, silence_thresh=-40)
+
+ # If there are chunks, concatenate all of them
+ if chunks:
+ combined_sound = sum(chunks, AudioSegment.empty())
+
+ # Export the combined audio
+ combined_sound.export(output_file, format="wav")
+ else:
+ # If there are no chunks, just save the original audio
+ sound.export(output_file, format="wav")
+
+
+def sanitize_text_for_tts(text):
+ """
+ Sanitize the text for TTS processing.
+ Remove characters that aren't alphanumeric or whitespace.
+ """
+ sanitized_text = re.sub(r"[^a-zA-Z0-9\s]", "", text)
+ return sanitized_text
+
+
+def backup_existing_files(path, filename_pattern, backup_name):
+ """
+ Backup files matching the filename pattern in the specified path to a zip file.
+ """
+ files_to_backup = [
+ f
+ for f in os.listdir(path)
+ if f.startswith(filename_pattern) and f.endswith(".wav")
+ ]
+ if not files_to_backup:
+ return
+
+ with zipfile.ZipFile(backup_name, "w") as zipf:
+ for file in files_to_backup:
+ zipf.write(os.path.join(path, file), file)
+
+
+def process_county_codes():
+ """
+ Process county codes and make changes.
+ """
+ new_county_codes = []
+ for entry in config["Alerting"]["CountyCodes"]:
+ overwrite = False
+ if isinstance(entry, str): # County code without WAV file
+ county_code = entry
+ elif isinstance(entry, dict): # County code with WAV file
+ county_code = list(entry.keys())[0]
+
+ county_name = county_data.get(county_code)
+ sanitized_county_name = sanitize_text_for_tts(county_name)
+ expected_wav_file = "{}.wav".format(sanitized_county_name)
+
+ if os.path.exists(os.path.join(SOUNDS_PATH, expected_wav_file)):
+ if not overwrite:
+ user_input = input(
+ "The WAV file for {} ({}) already exists. Do you want to overwrite it? [yes/no]: ".format(
+ county_name, expected_wav_file
+ )
+ ).lower()
+ if user_input != "yes":
+ LOGGER.info(
+ "Skipping generation for {} due to user input.".format(
+ county_name
+ )
+ )
+ new_county_codes.append({county_code: expected_wav_file})
+ continue # Skip to the next county code
+ overwrite = True
+
+ # At this point, we are sure that we either have a new county code or the user has agreed to overwrite.
+ county_name = county_data.get(county_code)
+ if county_name:
+ sanitized_county_name = sanitize_text_for_tts(county_name)
+ output_file = os.path.join(
+ SOUNDS_PATH, "{}.wav".format(sanitized_county_name)
+ )
+ generate_wav(
+ API_KEY, LANGUAGE, SPEED, VOICE, sanitized_county_name, output_file
+ )
+
+ # Add the mapping for the county code to the new list
+ new_county_codes.append(
+ {county_code: "{}.wav".format(sanitized_county_name)}
+ )
+
+ # Replace the old CountyCodes list with the new one
+ config["Alerting"]["CountyCodes"] = new_county_codes
+
+
+def load_county_codes_from_md(md_file_path):
+ """
+ Load county names from the MD file and return a dictionary mapping county codes to county names.
+ """
+ with open(md_file_path, "r") as file:
+ lines = file.readlines()
+
+ county_data = {}
+ for line in lines:
+ if line.startswith("|") and "County Name" not in line and "-----" not in line:
+ _, county_name, code, _ = line.strip().split("|")
+ county_data[code.strip()] = county_name.strip()
+
+ return county_data
+
+
+def display_initial_warning():
+ warning_message = """
+ ============================================================
+ WARNING: Please read the following information carefully before proceeding.
+
+ This utility is designed to generate WAV audio files corresponding to each county code
+ defined in the SkywarnPlus config.yaml using the Voice RSS Text-to-Speech API. The generated
+ audio files will be saved in the appropriate location, and the SkywarnPlus config.yaml will
+ be automatically updated to use them.
+
+ However, a few things to keep in mind:
+ - The script will only attempt to generate WAV files for county codes that are defined in the config.
+
+ - Pronunciations for some county names might not be accurate. In such cases, you may need to
+ manually create the files using VoiceRSS. This might involve intentionally misspelling the county
+ name to achieve the desired pronunciation.
+
+ - This script will attempt to backup any files before it modifies them, but it is always a good idea to
+ manually back up your existing configuration and files before running this script.
+
+ - This script will modify your config.yaml file, so you should ALWAYS double check the changes it makes.
+ There might be improperly formatted indentations, comments, etc. that you will need to fix manually.
+
+ Proceed with caution.
+ ============================================================
+ """
+ print(warning_message)
+
+
+# Display the initial warning
+display_initial_warning()
+
+# Wait for user acknowledgment before proceeding.
+user_input = input("Do you want to proceed? [yes/no]: ").lower()
+if user_input != "yes":
+ LOGGER.info("Aborting process due to user input.")
+ sys.exit()
+
+# Load county names and generate WAV files
+backup_date = datetime.now().strftime("%Y%m%d")
+backup_name = os.path.join(SOUNDS_PATH, "CountyID_Backup_{}.zip".format(backup_date))
+backup_existing_files(SOUNDS_PATH, "", backup_name)
+
+# Load county codes and names
+county_data = load_county_codes_from_md(COUNTY_CODES_PATH)
+
+# Call the function to process the county codes
+process_county_codes()
+
+# Update config.yaml to reflect the WAV file mappings
+for i, county_code in enumerate(config["Alerting"]["CountyCodes"]):
+ if isinstance(county_code, str):
+ county_name = county_data.get(county_code)
+ if county_name:
+ sanitized_county_name = sanitize_text_for_tts(county_name)
+ config["Alerting"]["CountyCodes"][i] = {
+ county_code: "{}.wav".format(sanitized_county_name)
+ }
+
+# Write the updated config.yaml
+with open(CONFIG_PATH, "w") as config_file:
+ yaml.indent(sequence=4, offset=2)
+ yaml.dump(config, config_file)
+
+LOGGER.info("County WAV files generation completed.")
diff --git a/README.md b/README.md
index edb6a51..2e89de7 100644
--- a/README.md
+++ b/README.md
@@ -120,7 +120,7 @@ Follow the steps below to install:
pacman -S ffmpeg
wget https://bootstrap.pypa.io/pip/3.5/get-pip.py
python get-pip.py
- pip install pyyaml requests python-dateutil pydub
+ pip install requests python-dateutil pydub
pip install ruamel.yaml==0.15.100
```
@@ -152,7 +152,7 @@ Follow the steps below to install:
nano config.yaml
```
- You can find your area code(s) at https://alerts.weather.gov/. Select `County List` to the right of your state, and use the `County Code` associated with the area(s) you want SkywarnPlus to poll for WX alerts.
+ You can find your county codes in the [CountyCodes.md](CountyCodes.md) file included in this repository. Navigate to the file and look for your state and then your specific county to find the associated County Code you'll use in SkywarnPlus to poll for alerts.
## **IMPORTANT**: YOU WILL MISS ALERTS IF YOU USE A **ZONE** CODE. DO NOT USE **ZONE** CODES UNLESS YOU KNOW WHAT YOU ARE DOING.
@@ -553,7 +553,7 @@ In _most_ cases, any multiple counties that SkywarnPlus is set up to monitor wil
SkywarnPlus comes with a library of audio files that can be replaced with any 8kHz mono PCM16 WAV files you want. These are found in the `SOUNDS/` directory by default, along with `DICTIONARY.txt` which explains audio file assignments. Several customizations can be easily made in `config.yaml`, but the sound files are always available for you to modify directly as well.
-If you'd like to use IDChange or add county identifiers, you must create your own audio files. Follow **[this guide](https://wiki.allstarlink.org/images/d/dd/RecordingSoundFiles.pdf)** on how to record/convert audio files for use with Asterisk/app_rpt.
+If you'd like to use IDChange, you must create your own audio files. Follow **[this guide](https://wiki.allstarlink.org/images/d/dd/RecordingSoundFiles.pdf)** on how to record/convert audio files for use with Asterisk/app_rpt.
>**For users wishing to maintain vocal continuity in their SkywarnPlus installation, the original creator of SkywarnPlus (N5LSN) and the woman behind the voice of it's included library of audio recordings (N5LSN XYL) will, for a small fee, record custom audio files for your SkywarnPlus installation. Contact information is readily available via QRZ.**
@@ -561,13 +561,25 @@ If you'd like to use IDChange or add county identifiers, you must create your ow
SkywarnPlus features the capability to play county-specific audio files to reference the affected area of alerts. It enhances the user's awareness of the geographic area affected by an event, making the system more informative and valuable to users monitoring systems that provide coverage for multiple counties. By assigning unique audio tags to each county, users can immediately recognize which county is affected by an event as soon as it is detected by SkywarnPlus.
-### How to use it?
+### Automated Setup using `CountyIDGen.py`
-To use the county-specific audio tags, you need to specify the audio files to use for each county in your `config.yaml` file. You must create or otherwise aquire these audio files yourself. The audio files must be located in the root of the `SkywarnPlus/SOUNDS/` directory.
+To simplify the process of setting up county-specific audio tags, SkywarnPlus provides a utility script called CountyIDGen.py. This script is designed to:
+
+- Generate WAV audio files for each county code defined in the config.yaml using the Voice RSS Text-to-Speech API.
+- Save these generated files in the proper directory.
+- Modify the config.yaml automatically to reference these files.
+
+To use the script for automated setup, simply make sure you have already set up all of your county codes (`Alerting` section) and VoiceRSS details (`SkyDescribe` section) in `config.yaml`, and then execute the script:
+```bash
+./CountyIDGen.py
+```
+### Manual Setup
+
+Manual setup involves creating or otherwise aquire these audio files yourself. The audio files must be located in the root of the `SkywarnPlus/SOUNDS/` directory.
The `config.yaml` explains how to use the free VoiceRSS API to generate these files using a computer synthesized voice.
-Here is an example of how to configure the `config.yaml` to utilize this feature:
+Here is an example of how to manually configure the `config.yaml` to utilize this feature:
```yaml
Alerting:
diff --git a/SkyControl.py b/SkyControl.py
index a5bdc5f..f222956 100644
--- a/SkyControl.py
+++ b/SkyControl.py
@@ -1,7 +1,7 @@
#!/usr/bin/python3
"""
-SkyControl v0.4.2 by Mason Nelson
+SkyControl.py v0.5.0 by Mason Nelson
==================================
A Control Script for SkywarnPlus
diff --git a/SkyDescribe.py b/SkyDescribe.py
index 5c12684..bacc47a 100644
--- a/SkyDescribe.py
+++ b/SkyDescribe.py
@@ -1,7 +1,7 @@
#!/usr/bin/python3
"""
-SkyDescribe v0.4.2 by Mason Nelson
+SkyDescribe.py v0.5.0 by Mason Nelson
==================================================
Text to Speech conversion for Weather Descriptions
@@ -297,9 +297,7 @@ def main(index_or_title):
description = alert_data[0]["description"]
else:
description = "There are {} unique instances of {}. Describing the first one. {}".format(
- unique_instances,
- alert,
- alert_data[0]["description"]
+ unique_instances, alert, alert_data[0]["description"]
)
else:
@@ -320,9 +318,7 @@ def main(index_or_title):
description = alert_data[0]["description"]
else:
description = "There are {} unique instances of {}. Describing the first one. {}".format(
- unique_instances,
- alert,
- alert_data[0]["description"]
+ unique_instances, alert, alert_data[0]["description"]
)
break
else:
diff --git a/SkywarnPlus.py b/SkywarnPlus.py
index c420603..1f019ab 100644
--- a/SkywarnPlus.py
+++ b/SkywarnPlus.py
@@ -1,7 +1,7 @@
#!/usr/bin/python3
"""
-SkywarnPlus v0.4.2 by Mason Nelson
+SkywarnPlus.py v0.5.0 by Mason Nelson
===============================================================================
SkywarnPlus is a utility that retrieves severe weather alerts from the National
Weather Service and integrates these alerts with an Asterisk/app_rpt based
@@ -53,6 +53,7 @@ yaml = YAML()
# Directories and Paths
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml")
+COUNTY_CODES_PATH = os.path.join(BASE_DIR, "CountyCodes.md")
# Open and read configuration file
with open(CONFIG_PATH, "r") as config_file:
@@ -319,19 +320,31 @@ LOGGER.debug("Tailmessage Blocked events: %s", TAILMESSAGE_BLOCKED_EVENTS)
def load_state():
"""
Load the state from the state file if it exists, else return an initial state.
+
+ The state file is expected to be a JSON file. If certain keys are missing in the
+ loaded state, this function will provide default values for those keys.
"""
+
+ # Check if the state data file exists
if os.path.exists(DATA_FILE):
with open(DATA_FILE, "r") as file:
state = json.load(file)
+
+ # Ensure 'alertscript_alerts' key is present in the state, default to an empty list
state["alertscript_alerts"] = state.get("alertscript_alerts", [])
- # Process 'last_alerts' and maintain the order of alerts
+ # Process 'last_alerts' key to maintain the order of alerts using OrderedDict
+ # This step is necessary because JSON does not preserve order by default
last_alerts = state.get("last_alerts", [])
state["last_alerts"] = OrderedDict((x[0], x[1]) for x in last_alerts)
+ # Ensure 'last_sayalert' and 'active_alerts' keys are present in the state
state["last_sayalert"] = state.get("last_sayalert", [])
state["active_alerts"] = state.get("active_alerts", [])
+
return state
+
+ # If the state data file does not exist, return a default state
else:
return {
"ct": None,
@@ -346,22 +359,33 @@ def load_state():
def save_state(state):
"""
Save the state to the state file.
+
+ The state is saved as a JSON file. The function ensures certain keys in the state
+ are converted to lists before saving, ensuring consistency and ease of processing
+ when the state is later loaded.
"""
+
+ # Convert 'alertscript_alerts', 'last_sayalert', and 'active_alerts' keys to lists
+ # This ensures consistency in data format, especially useful when loading the state later
state["alertscript_alerts"] = list(state["alertscript_alerts"])
- state["last_alerts"] = list(state["last_alerts"].items())
state["last_sayalert"] = list(state["last_sayalert"])
state["active_alerts"] = list(state["active_alerts"])
+
+ # Convert 'last_alerts' from OrderedDict to list of items
+ # This step is necessary because JSON does not natively support OrderedDict
+ state["last_alerts"] = list(state["last_alerts"].items())
+
+ # Save the state to the data file in a formatted manner
with open(DATA_FILE, "w") as file:
json.dump(state, file, ensure_ascii=False, indent=4)
def get_alerts(countyCodes):
"""
- This function retrieves severe weather alerts for specified county codes.
+ Retrieves severe weather alerts for specified county codes and processes them.
"""
- # Mapping dictionaries are defined for converting the severity level from the
- # API's terminology to a numeric scale and from common alert language to the same numeric scale.
+ # Define mappings to convert severity levels from various terminologies to a numeric scale
severity_mapping_api = {
"Extreme": 4,
"Severe": 3,
@@ -371,17 +395,15 @@ def get_alerts(countyCodes):
}
severity_mapping_words = {"Warning": 4, "Watch": 3, "Advisory": 2, "Statement": 1}
- # 'alerts' will store the alert data we retrieve.
- # 'seen_alerts' is used to keep track of the alerts we've already processed.
+ # Initialize storage for the alerts and a set to keep track of processed alerts
alerts = OrderedDict()
seen_alerts = set()
- # We retrieve the current time and log it for debugging purposes.
+ # Log current time for reference
current_time = datetime.now(timezone.utc)
LOGGER.debug("getAlerts: Current time: %s", current_time)
- # The script allows for alert injection for testing purposes.
- # If injection is enabled in the configuration, we process these injected alerts first.
+ # Handle alert injection for development/testing purposes
if config.get("DEV", {}).get("INJECT", False):
LOGGER.debug("getAlerts: DEV Alert Injection Enabled")
injected_alerts = config["DEV"].get("INJECTALERTS", [])
@@ -390,6 +412,8 @@ def get_alerts(countyCodes):
max_counties = len(countyCodes) # Assuming countyCodes is a list of counties
county_codes_cycle = itertools.cycle(countyCodes)
+ county_assignment_counter = 1
+
for alert_info in injected_alerts:
if isinstance(alert_info, dict):
alert_title = alert_info.get("Title", "")
@@ -404,6 +428,17 @@ def get_alerts(countyCodes):
end_time_str = alert_info.get("EndTime")
county_data = []
+
+ # If no counties are specified, use the ones provided to the function in an increasing manner
+ if not specified_counties:
+ # Limit the number of counties assigned to not exceed max_counties
+ counties_to_assign = min(county_assignment_counter, max_counties)
+
+ specified_counties = [
+ next(county_codes_cycle) for _ in range(counties_to_assign)
+ ]
+ county_assignment_counter += 1 # Increment for the next injected alert
+
for county in specified_counties:
if county not in countyCodes:
LOGGER.error(
@@ -431,11 +466,8 @@ def get_alerts(countyCodes):
alert_title
] = county_data # Add the list of dictionaries to the alert
- # We limit the number of alerts to the maximum defined constant.
- alerts = OrderedDict(list(alerts.items())[:MAX_ALERTS])
-
# If injected alerts are used, we return them here and don't proceed with the function.
- return alerts
+ return sort_alerts(alerts)
# Configuration specifies whether to use 'effective' or 'onset' time for alerts.
# Depending on the configuration, we set the appropriate keys for start and end time.
@@ -632,16 +664,52 @@ def get_alerts(countyCodes):
return alerts
+def sort_alerts(alerts):
+ """
+ Sorts and limits the alerts based on their severity and word severity.
+ """
+
+ # Define mapping for converting common alert terminologies to a numeric severity scale
+ severity_mapping_words = {"Warning": 4, "Watch": 3, "Advisory": 2, "Statement": 1}
+
+ # Sort the alerts first by their maximum severity, and then by their word severity
+ sorted_alerts = OrderedDict(
+ sorted(
+ alerts.items(),
+ key=lambda item: (
+ max([x["severity"] for x in item[1]]), # Max Severity for the alert
+ severity_mapping_words.get(
+ item[0].split()[-1], 0
+ ), # Severity based on last word in the alert title
+ ),
+ reverse=True, # Sort in descending order
+ )
+ )
+
+ # Truncate the list of alerts to the maximum allowed number (MAX_ALERTS)
+ limited_alerts = OrderedDict(list(sorted_alerts.items())[:MAX_ALERTS])
+
+ return limited_alerts
+
+
def time_until(start_time_utc, current_time):
"""
Calculate the time difference between two datetime objects and returns it
as a formatted string.
"""
+
+ # Calculate the time difference between the two datetime objects
delta = start_time_utc - current_time
+
+ # Determine the sign (used for formatting)
sign = "-" if delta < timedelta(0) else ""
+
+ # Decompose the time difference into days, hours, and minutes
days, remainder = divmod(abs(delta.total_seconds()), 86400)
hours, remainder = divmod(remainder, 3600)
minutes, _ = divmod(remainder, 60)
+
+ # Return the time difference as a formatted string
return "{}{} days, {} hours, {} minutes".format(
sign, int(days), int(hours), int(minutes)
)
@@ -651,34 +719,41 @@ def say_alerts(alerts):
"""
Generate and broadcast severe weather alert sounds on Asterisk.
"""
- # Define the path of the alert file
+
+ # Load the current state
state = load_state()
- # Extract only the alert names from the OrderedDict keys
- alert_names = [alert for alert in alerts.keys()]
+ # Extract only the alert names and associated counties
+ alert_names_and_counties = {
+ alert: [county["county_code"] for county in counties]
+ for alert, counties in alerts.items()
+ }
- filtered_alerts = []
- for alert in alert_names:
+ # Filter out alerts that are blocked based on configuration
+ filtered_alerts_and_counties = {}
+ for alert, county_codes in alert_names_and_counties.items():
if any(
fnmatch.fnmatch(alert, blocked_event)
for blocked_event in SAYALERT_BLOCKED_EVENTS
):
LOGGER.debug("sayAlert: blocking %s as per configuration", alert)
continue
- filtered_alerts.append(alert)
+ filtered_alerts_and_counties[alert] = county_codes
# Check if the filtered alerts are the same as the last run
- if filtered_alerts == state["last_sayalert"]:
- LOGGER.debug("sayAlert: alerts are the same as the last broadcast - skipping.")
+ if filtered_alerts_and_counties == state.get("last_sayalert", {}):
+ LOGGER.debug(
+ "sayAlert: alerts and counties are the same as the last broadcast - skipping."
+ )
return
- state["last_sayalert"] = filtered_alerts
+ # Update the state with the current alerts
+ state["last_sayalert"] = filtered_alerts_and_counties
save_state(state)
+ # Initialize the audio segments and paths
alert_file = "{}/alert.wav".format(TMP_DIR)
-
word_space = AudioSegment.silent(duration=600)
-
sound_effect = AudioSegment.from_wav(
os.path.join(
SOUNDS_PATH,
@@ -687,7 +762,6 @@ def say_alerts(alerts):
config.get("Alerting", {}).get("AlertSeperator", "Woodblock.wav"),
)
)
-
intro_effect = AudioSegment.from_wav(
os.path.join(
SOUNDS_PATH,
@@ -696,16 +770,16 @@ def say_alerts(alerts):
config.get("Alerting", {}).get("AlertSound", "Duncecap.wav.wav"),
)
)
-
combined_sound = (
intro_effect
+ word_space
+ AudioSegment.from_wav(os.path.join(SOUNDS_PATH, "ALERTS", "SWP_148.wav"))
)
+ # Build the combined sound with alerts and county names
alert_count = 0
for alert, counties in alerts.items():
- if alert in filtered_alerts:
+ if alert in filtered_alerts_and_counties:
try:
descriptions = [county["description"] for county in counties]
end_times = [county["end_time_utc"] for county in counties]
@@ -831,12 +905,13 @@ def say_allclear():
"""
Generate and broadcast 'all clear' message on Asterisk.
"""
- # Empty the last_sayalert list so that the next run will broadcast alerts
+
+ # Load current state and clear the last_sayalert list
state = load_state()
state["last_sayalert"] = []
save_state(state)
- # Load sound file paths
+ # Define file paths for the sounds
all_clear_sound_file = os.path.join(
config.get("Alerting", {}).get("SoundsPath"),
"ALERTS",
@@ -845,25 +920,28 @@ def say_allclear():
)
swp_147_file = os.path.join(SOUNDS_PATH, "ALERTS", "SWP_147.wav")
- # Create AudioSegment objects
+ # Load sound files into AudioSegment objects
all_clear_sound = AudioSegment.from_wav(all_clear_sound_file)
swp_147_sound = AudioSegment.from_wav(swp_147_file)
- # Create silence
- silence = AudioSegment.silent(duration=600) # 600 ms silence
+ # Generate silence for spacing between sounds
+ silence = AudioSegment.silent(duration=600) # 600 ms of silence
- # Combine the sounds with silence in between
+ # Combine the sound clips
combined_sound = all_clear_sound + silence + swp_147_sound
+ # Add a delay before the sound if configured
if AUDIO_DELAY > 0:
LOGGER.debug("sayAllClear: Prepending audio with %sms of silence", AUDIO_DELAY)
silence = AudioSegment.silent(duration=AUDIO_DELAY)
combined_sound = silence + combined_sound
+ # Export the combined sound to a file
all_clear_file = os.path.join(TMP_DIR, "allclear.wav")
converted_combined_sound = convert_audio(combined_sound)
converted_combined_sound.export(all_clear_file, format="wav")
+ # Append a suffix to the sound if configured
if config.get("Alerting", {}).get("SayAllClearSuffix", None) is not None:
suffix_file = os.path.join(
SOUNDS_PATH, config.get("Alerting", {}).get("SayAllClearSuffix")
@@ -873,6 +951,7 @@ def say_allclear():
converted_suffix_sound = convert_audio(suffix_sound)
converted_suffix_sound.export(all_clear_file, format="wav")
+ # Play the "all clear" sound on the configured Asterisk nodes
node_numbers = config.get("Asterisk", {}).get("Nodes", [])
for node_number in node_numbers:
LOGGER.info("Broadcasting all clear message on node %s", node_number)
@@ -897,6 +976,7 @@ def build_tailmessage(alerts):
# Get the suffix config
tailmessage_suffix = config.get("Tailmessage", {}).get("TailmessageSuffix", None)
+ # If alerts is empty
if not alerts:
LOGGER.debug("buildTailMessage: No alerts, creating silent tailmessage")
silence = AudioSegment.silent(duration=100)
@@ -1345,6 +1425,73 @@ def supermon_back_compat(alerts):
file.write("
".join(alert_titles))
+def detect_county_changes(old_alerts, new_alerts):
+ """
+ Detect if any counties have been added to or removed from an alert and return the alerts
+ with added or removed counties.
+ """
+ alerts_with_changed_counties = OrderedDict()
+ changes_detected = {}
+
+ for alert_name, alert_info in new_alerts.items():
+ if alert_name not in old_alerts:
+ continue
+ old_alert_info = old_alerts.get(alert_name, [])
+ old_county_codes = {info["county_code"] for info in old_alert_info}
+ new_county_codes = {info["county_code"] for info in alert_info}
+
+ added_counties = new_county_codes - old_county_codes
+ removed_counties = old_county_codes - new_county_codes
+
+ added_counties = {
+ code.replace("{", "").replace("}", "").replace('"', "")
+ for code in added_counties
+ }
+ removed_counties = {
+ code.replace("{", "").replace("}", "").replace('"', "")
+ for code in removed_counties
+ }
+
+ if added_counties or removed_counties:
+ alerts_with_changed_counties[alert_name] = new_alerts[alert_name]
+ changes_detected[alert_name] = {
+ "old": old_county_codes,
+ "added": added_counties,
+ "removed": removed_counties,
+ }
+
+ return alerts_with_changed_counties, changes_detected
+
+
+def load_county_names(md_file):
+ """
+ Load county names from separate markdown tables so that county codes can be replaced with county names.
+ """
+ with open(md_file, "r") as f:
+ lines = f.readlines()
+
+ county_data = {}
+ in_table = False
+ for line in lines:
+ if line.startswith("| County Name"):
+ in_table = True
+ continue # Skip the header
+ elif not in_table or line.strip() == "":
+ continue
+ else:
+ name, code = [s.strip() for s in line.split("|")[1:-1]]
+ county_data[code] = name
+
+ return county_data
+
+
+def replace_with_county_name(county_code, county_data):
+ """
+ Translate county code to county name.
+ """
+ return county_data.get(county_code, county_code)
+
+
def main():
"""
The main function that orchestrates the entire process of fetching and
@@ -1353,96 +1500,220 @@ def main():
"""
# Fetch configurations
say_alert_enabled = config["Alerting"].get("SayAlert", False)
+ say_alert_all = config["Alerting"].get("SayAlertAll", False)
say_all_clear_enabled = config["Alerting"].get("SayAllClear", False)
alertscript_enabled = config["AlertScript"].get("Enable", False)
-
- # Fetch state
+ ct_alerts = config["CourtesyTones"].get("CTAlerts", [])
+ enable_ct_auto_change = config["CourtesyTones"].get("Enable", False)
+ id_alerts = config["IDChange"].get("IDAlerts", [])
+ enable_id_auto_change = config["IDChange"].get("Enable", False)
+ pushover_enabled = config["Pushover"].get("Enable", False)
+ pushover_debug = config["Pushover"].get("Debug", False)
+ supermon_compat_enabled = config["DEV"].get("SupermonCompat", True)
+ say_alerts_changed = config["Alerting"].get("SayAlertsChanged", True)
+
+ # If data file does not exist, assume this is the first run and initialize CT/ID/Tailmessage files if enabled
+ if not os.path.isfile(DATA_FILE):
+ LOGGER.info("Data file does not exist, assuming first run.")
+ if enable_ct_auto_change:
+ LOGGER.info("Initializing CT files")
+ change_ct("NORMAL")
+ if enable_id_auto_change:
+ LOGGER.info("Initializing ID files")
+ change_id("NORMAL")
+ if ENABLE_TAILMESSAGE:
+ LOGGER.info("Initializing Tailmessage file")
+ empty_alerts = OrderedDict()
+ build_tailmessage(empty_alerts)
+
+ # Load previous alert data to compare changes
state = load_state()
-
- # Load old alerts
last_alerts = state["last_alerts"]
- # Fetch new alerts
+ # Fetch new alert data
alerts = get_alerts(COUNTY_CODES)
- # If new alerts differ from old ones, process new alerts
- if [alert[0] for alert in last_alerts.keys()] != [
- alert[0] for alert in alerts.keys()
- ]:
- added_alerts = [
- alert for alert in alerts.keys() if alert not in last_alerts.keys()
- ]
- removed_alerts = [
- alert for alert in last_alerts.keys() if alert not in alerts.keys()
- ]
-
- if added_alerts:
- LOGGER.info("Added: %s", ", ".join(alert for alert in added_alerts))
- if removed_alerts:
- LOGGER.info("Removed: %s", ", ".join(alert for alert in removed_alerts))
+ # Load county names from YAML file so that county codes can be replaced with county names in messages
+ county_data = load_county_names(COUNTY_CODES_PATH)
+
+ # Placeholder for constructing a pushover message
+ pushover_message = ""
+
+ # Determine which alerts have been added since the last check
+ added_alerts = [alert for alert in alerts if alert not in last_alerts]
+ for alert in added_alerts:
+ counties_str = (
+ "["
+ + ", ".join(
+ [
+ replace_with_county_name(x["county_code"], county_data)
+ for x in alerts[alert]
+ ]
+ )
+ + "]"
+ )
+ LOGGER.info("Added: {} for {}".format(alert, counties_str))
+ pushover_message += "Added: {} for {}\n".format(alert, counties_str)
+
+ # Determine which alerts have been removed since the last check
+ removed_alerts = [alert for alert in last_alerts if alert not in alerts]
+ for alert in removed_alerts:
+ counties_str = (
+ "["
+ + ", ".join(
+ [
+ replace_with_county_name(x["county_code"], county_data)
+ for x in last_alerts[alert]
+ ]
+ )
+ + "]"
+ )
+ LOGGER.info("Removed: {} for {}".format(alert, counties_str))
+ pushover_message += "Removed: {} for {}\n".format(alert, counties_str)
+
+ # Placeholder for storing alerts with changed county codes
+ changed_alerts = {}
+
+ # If the list of alerts is not empty
+ if alerts:
+ # Compare old and new alerts to detect changes in affected counties
+ changed_alerts, changes_details = detect_county_changes(last_alerts, alerts)
+
+ for alert, details in changes_details.items():
+ old_counties_str = (
+ "["
+ + ", ".join(
+ [
+ replace_with_county_name(county, county_data)
+ for county in details["old"]
+ ]
+ )
+ + "]"
+ )
+ added_msg = ""
+ if details["added"]:
+ added_counties_str = (
+ "["
+ + ", ".join(
+ [
+ replace_with_county_name(county, county_data)
+ for county in details["added"]
+ ]
+ )
+ + "]"
+ )
+ added_msg = "is now also affecting {}".format(added_counties_str)
+
+ removed_msg = ""
+ if details["removed"]:
+ removed_counties_str = (
+ "["
+ + ", ".join(
+ [
+ replace_with_county_name(county, county_data)
+ for county in details["removed"]
+ ]
+ )
+ + "]"
+ )
+ removed_msg = "is no longer affecting {}".format(removed_counties_str)
+
+ # Combining the log messages
+ combined_msg_parts = [f"Changed: {alert} for {old_counties_str}"]
+ combined_msg_parts = ["Changed: {} for {}".format(alert, old_counties_str)]
+ if added_msg:
+ combined_msg_parts.append(added_msg)
+ if removed_msg:
+ if (
+ added_msg
+ ): # if there's an 'added' message, then use 'and' to combine with 'removed' message
+ combined_msg_parts.append("and")
+ combined_msg_parts.append(removed_msg)
+
+ log_msg = " ".join(combined_msg_parts)
+ LOGGER.info(log_msg)
+ pushover_message += log_msg + "\n"
+
+ # Process changes in alerts
+ if added_alerts or removed_alerts or changed_alerts:
+ # Save the data
state["last_alerts"] = alerts
save_state(state)
- ct_alerts = config["CourtesyTones"].get("CTAlerts", [])
- enable_ct_auto_change = config["CourtesyTones"].get("Enable", False)
+ # Send "all clear" messages if alerts have changed but are empty
+ if not alerts:
+ LOGGER.info("Alerts cleared")
+ # Call say_allclear if enabled
+ if say_all_clear_enabled:
+ say_allclear()
+ # Add "Alerts Cleared" to pushover message
+ pushover_message += "Alerts Cleared\n"
+
+ # If alerts have been added, removed
+ if added_alerts or removed_alerts:
+ # Push alert titles to Supermon if enabled
+ if supermon_compat_enabled:
+ supermon_back_compat(alerts)
+
+ # Change CT/ID if necessary and enabled
+ change_ct_id_helper(
+ alerts,
+ ct_alerts,
+ enable_ct_auto_change,
+ "CT",
+ pushover_debug,
+ pushover_message,
+ )
+ change_ct_id_helper(
+ alerts,
+ id_alerts,
+ enable_id_auto_change,
+ "ID",
+ pushover_debug,
+ pushover_message,
+ )
- id_alerts = config["IDChange"].get("IDAlerts", [])
- enable_id_auto_change = config["IDChange"].get("Enable", False)
+ # Call alert_script if enabled
+ if alertscript_enabled:
+ alert_script(alerts)
- pushover_enabled = config["Pushover"].get("Enable", False)
- pushover_debug = config["Pushover"].get("Debug", False)
+ # Say alerts if enabled
+ if say_alert_enabled:
+ # If say_alert_all is enabled, say all currently active alerts
+ if say_alert_all:
+ alerts_to_say = alerts
- supermon_compat_enabled = config["DEV"].get("SupermonCompat", True)
- if supermon_compat_enabled:
- supermon_back_compat(alerts)
+ # Otherwise, only say newly added alerts
+ else:
+ alerts_to_say = {alert: alerts[alert] for alert in added_alerts}
+ # If County IDs have been set up and there is more than one county code, then also say alerts with county changes
+ # Only if enabled
+ if (
+ changed_alerts
+ and say_alerts_changed
+ and COUNTY_WAVS
+ and len(COUNTY_CODES) > 1
+ ):
+ alerts_to_say.update(changed_alerts)
- # Initialize pushover message
- pushover_message = ""
- if not added_alerts and not removed_alerts:
- pushover_message = "Alerts Cleared\n"
- else:
- if added_alerts:
- pushover_message += "Added: {}\n".format(", ".join(added_alerts))
- if removed_alerts:
- pushover_message += "Removed: {}\n".format(", ".join(removed_alerts))
-
- # Check if Courtesy Tones (CT) or ID needs to be changed
- change_ct_id_helper(
- alerts,
- ct_alerts,
- enable_ct_auto_change,
- "CT",
- pushover_debug,
- pushover_message,
- )
- change_ct_id_helper(
- alerts,
- id_alerts,
- enable_id_auto_change,
- "ID",
- pushover_debug,
- pushover_message,
- )
+ # Sort alerts based on severity
+ alerts_to_say = sort_alerts(alerts_to_say)
- if alertscript_enabled:
- alert_script(alerts)
+ # Say the alerts
+ say_alerts(alerts_to_say)
- # Check if alerts need to be communicated
- if len(alerts) == 0:
- LOGGER.info("Alerts cleared")
- if say_all_clear_enabled:
- say_allclear()
- else:
- if say_alert_enabled:
- if config["Alerting"].get("SayAlertAll", False):
- say_alerts(alerts)
- else:
- say_alerts({alert: alerts[alert] for alert in added_alerts})
+ # If alerts have changed, but none added or removed
+ elif changed_alerts:
+ # Say changed alerts only if enabled, County IDs have been set up, and there is more than one county code
+ if say_alerts_changed and COUNTY_WAVS and len(COUNTY_CODES) > 1:
+ # Sort alerts based on severity
+ changed_alerts = sort_alerts(changed_alerts)
+ # Say the alerts
+ say_alerts(changed_alerts)
- # Check if tailmessage needs to be built
- enable_tailmessage = config.get("Tailmessage", {}).get("Enable", False)
- if enable_tailmessage:
+ # Alerts have changed, update tailmessage if enabled
+ if ENABLE_TAILMESSAGE:
build_tailmessage(alerts)
if pushover_debug:
pushover_message += (
@@ -1451,17 +1722,35 @@ def main():
else "Built WX tailmessage\n"
)
- # Send pushover notification
+ # Send pushover message if enabled
if pushover_enabled:
pushover_message = pushover_message.rstrip("\n")
- LOGGER.debug("Sending pushover notification: %s", pushover_message)
- send_pushover(pushover_message, title="Alerts Changed")
+ LOGGER.debug("Sending Pushover message: %s", pushover_message)
+ send_pushover(pushover_message, title="SkywarnPlus")
+
+ # If no changes detected in alerts
else:
+ # If this is being run interactively, inform the user that nothing has changed
if sys.stdin.isatty():
- # list of current alerts, unless there arent any, then current_alerts = "None"
- current_alerts = "None" if len(alerts) == 0 else ", ".join(alerts.keys())
+ # Log list of current alerts, unless there aren't any, then current_alerts = "None"
+ if len(alerts) == 0:
+ current_alerts = "None"
+ else:
+ alert_details = []
+ for alert, counties in alerts.items():
+ counties_str = ", ".join(
+ [
+ replace_with_county_name(county["county_code"], county_data)
+ for county in counties
+ ]
+ )
+ alert_details.append(f"{alert} ({counties_str})")
+ current_alerts = "; ".join(alert_details)
+
LOGGER.info("No change in alerts.")
LOGGER.info("Current alerts: %s.", current_alerts)
+
+ # If this is being run non-interactively, only log if debug is enabled
else:
LOGGER.debug("No change in alerts.")
diff --git a/UpdateSWP.py b/UpdateSWP.py
index ca2db2a..19c4b0f 100644
--- a/UpdateSWP.py
+++ b/UpdateSWP.py
@@ -1,7 +1,7 @@
#!/usr/bin/python3
"""
-SkywarnPlus Updater v0.4.2 by Mason Nelson
+UpdateSWP.py by Mason Nelson
===============================================================================
Script to update SkywarnPlus to the latest version. This script will download
the latest version of SkywarnPlus from GitHub, and then merge the existing
@@ -112,6 +112,35 @@ def remove_duplicate_comments(filename):
# Write the new lines back to the file
with open(filename, "w") as f:
f.writelines(new_lines)
+
+
+# Display the initial warning
+def display_update_warning():
+ warning_message = """
+ ============================================================
+ WARNING: Please read the following information carefully before updating.
+
+ This utility is designed to update SkywarnPlus to the latest version by fetching it
+ directly from GitHub. Before updating:
+
+ - A backup of the existing SkywarnPlus directory will be created to ensure safety.
+
+ - The updater will attempt to merge your existing config.yaml with the new version's
+ config.yaml. ALWAYS double-check your config.yaml after updating. This script is not
+ perfect and may not merge your configuration correctly.
+
+ - If you've made significant changes to the SkywarnPlus code, directory structure, or
+ configuration, this updater might not work correctly. In such cases, manual updating
+ is recommended.
+
+ Remember, this script's primary goal is to help with the updating process. However,
+ given the complexities of merging and updating, always verify the results yourself to
+ ensure your system continues to operate as expected.
+
+ Proceed with caution.
+ ============================================================
+ """
+ print(warning_message)
# Check for root privileges
@@ -125,17 +154,10 @@ if not os.path.isfile("SkywarnPlus.py"):
)
exit()
-# Prompt for confirmation unless -f flag is present
+# Display the warning message
if not args.force:
- print("\nThis script will update SkywarnPlus to the latest version.")
- print(
- "It will create a backup of the existing SkywarnPlus directory before updating."
- )
- print(
- "Be aware that if you've made significant changes to the code or directory structure, this may cause issues."
- )
- print("If you've made significant changes, it is recommended to update manually.\n")
- print("ALWAYS DOUBLE CHECK YOUR CONFIG.YAML AFTER UPDATING! This script is not perfect and may not merge your config.yaml correctly.\n")
+ display_update_warning()
+
confirmation = input("\nDo you want to continue with the update? (yes/no) ")
if confirmation.lower() != "yes":
log("Update cancelled by user.")
diff --git a/config.yaml b/config.yaml
index 098c7ef..45e03a4 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,4 +1,4 @@
-# SkywarnPlus v0.4.2 Configuration File
+# SkywarnPlus v0.5.0 Configuration File
# Author: Mason Nelson (N5LSN/WRKF394)
# Please edit this file according to your specific requirements.
@@ -41,7 +41,8 @@ Alerting:
# SkywarnPlus allows adding county-specific audio indicators to each alert in the message.
# To enable this feature, specify an audio file containing a recording of the county name in the
# ROOT of the SOUNDS/ directory as shown in the below example. You must create these files yourself.
- # You can use the same VoiceRSS API used for SkyDescribe (see below) to generate these files with a synthetic voice:
+ # A helper script is provided in the SkywarnPlus repository to help generate these files.
+ # You can manually use the same VoiceRSS API used for SkyDescribe (see below) to generate these files with a synthetic voice:
# http://api.voicerss.org/?key=[YOUR_API_KEY_HERE]&hl=en-us&f=8khz_16bit_mono&v=John&src=[YOUR COUNTY NAME HERE]
# http://api.voicerss.org/?key=1234567890QWERTY&hl=en-us&f=8khz_16bit_mono&v=John&src=Saline County
# Example:
@@ -54,6 +55,10 @@ Alerting:
# Enable instant voice announcement when new weather alerts are issued.
SayAlert: true
+ # Enable SayAlert to "say" any alerts whose list of affected counties has changed, in addition to new alerts.
+ # Only applies if more than one CountyCode is specified AND County IDs have been setup.
+ SayAlertsChanged: true
+
# When a change is detected, make SayAlert say ALL of the currently active alerts, not just newly detected one(s)
SayAlertAll: false