From f7d69f5948f708388df81f76b62dcc11b857dea3 Mon Sep 17 00:00:00 2001 From: Starfall Date: Mon, 12 Dec 2022 14:27:43 -0600 Subject: add README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ fscache.py | 1 - meteor.py | 20 -------------------- 3 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..95e9bc3 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +meteor.py +========= + +A simple script written in about 4 hours that gets the weather. wttr.in was giving somewhat inaccurate results and +we found the [National Weather Service API](https://www.weather.gov/documentation/services-web-api) one day, so we +figured that we ought as well spend a little bit of time and have some fun with it. + +Usage +----- + +`meteor.py lat lon` + +`lat` and `lon` are the latitude & longitude of a particular point in the United States (e.g. 38.57 -121.48 is within Sacramento, California) + +TODO / Issues +------------- + +- Geocoding would make this a little more usable. [NWS lists several potential services](https://weather-gov.github.io/api/general-faqs#geocoding) +- Round decimals to 4 places ourselves to avoid errors +- Cache responses appropriately (the API uses HTTP caching headers correctly, TBA if we want to drop in requests-cache or do it ourselves) +- Use the /gridpoints/gridId/gridX,gridY endpoint to generate our own forecast with things like probabilityOfPrecipitation +- Fancy output like wttr.in +- Display the names of the zones (requires extra API calls - cacheable for a long duration, though) +- Shorter alert descriptions (`parameters.NWSheadline[0]` instead of `description`) +- Work around known issues in the NWS API: + - retry when gridpoints 500s + - reduce gridX and gridY by 1 for AFC (AER/ALU), AFG, AJK, BOX, CAE, DLH, FSD, HGX, HNX, LIX, LWX, MAF, MFR, MLB, MRX, MTR, PIH offices/gridIds +- Gracefully handle cache keys that include '/' + +Reference +--------- + +- [National Weather Service API Documentation](https://www.weather.gov/documentation/services-web-api) +- [NWS OpenAPI](https://api.weather.gov/openapi.json) + +License +------- + +(c) 2022 Starfall. All rights reserved. + +- "It shall be permissible to make quotations from a work ... provided that their making is + compatible with fair practice." - Berne Convention +- Information wants to be free. +- Be gay, do crimes. diff --git a/fscache.py b/fscache.py index d52ff62..b6d164b 100644 --- a/fscache.py +++ b/fscache.py @@ -15,7 +15,6 @@ class Cache: self.cache_dir.mkdir(exist_ok=True) def write(self, key, value): - # TODO gracefully handle keys with '/' by creating directories or rewriting loc = self.cache_dir/key loc.write_text(value, errors='ignore') diff --git a/meteor.py b/meteor.py index 4e41a64..8b7ab2c 100755 --- a/meteor.py +++ b/meteor.py @@ -3,9 +3,6 @@ from argparse import ArgumentParser import requests import fscache as fs -# doco is at https://www.weather.gov/documentation/services-web-api -# openAPI spec is at https://api.weather.gov/openapi.json - base_url = 'https://api.weather.gov' headers = { 'user-agent': 'meteor.py/0.2 (us@starfall.systems)', @@ -29,9 +26,6 @@ def print_alerts(county, zone): alerts = requests.get(base_url + f'/alerts/active?zone={county},{zone}', headers=headers).json() - # this title was cooler when we requested only one zone - # e.g. it would end with "for Holmes County (MSC051) MS" - # but now it's just "current watches, warnings, and advisories" print(f'{term.BOLD}{alerts.get("title")}') print(f'===================={term.RESET}') for alert in alerts.get('@graph'): @@ -39,9 +33,6 @@ def print_alerts(county, zone): print(f' {alert.get("severity")} - {alert.get("certainty")} - {alert.get("urgency")}') print(f' Until: {alert.get("expires")}') print(f'{alert.get("description")}') - # TODO description can be quite long - # short desc appears to be available as parameters.NWSheadline[0] - # and follow up with instruction when it exists print('--------------------') # many more cool fields available. @@ -55,8 +46,6 @@ def print_alerts(county, zone): print() def print_forecast(point): - # TODO pick up gridId AFC (AER/ALU), AFG, AJK, BOX< CAE, DLH, FSD, HGX, HNX, LIX, LWX, MAF, MFR, MLB, MRX, MTR, PIH and reduce gridX and gridY by 1 - known issue - cache_key = f'{point.get("gridId")}-{point.get("gridX")},{point.get("gridY")}' try: @@ -64,14 +53,6 @@ def print_forecast(point): except fs.CacheMiss: forecast_url = point.get('forecast') forecast = requests.get(forecast_url, headers=headers).json() - # TODO retry on 500 - known issue - # TODO query param units=si - - # TODO replace the whole thing with /gridpoints/{point.gridId}/{gridX},{gridY} and create our own forecast - # there is lots more information there that would be useful - # probabilityOfPrecipitation is the neatest one that's not in .../forecast or .../forecast/hourly - # but there's also relativeHumidity, apparentTemperature (and, separately, windChill), skyCover, windGust - # downside, it appears to always convert the F/mph measurements into C/kph for no reason. e.g. all temps are measured in 9ths of C buffer = f'{term.BOLD}forecast for {point.get("relativeLocation").get("city")}, {point.get("relativeLocation").get("state")} ({cache_key})' buffer += '\n' + f'===================={term.RESET}' @@ -83,7 +64,6 @@ def print_forecast(point): buffer += '\n' + f' {period.get("detailedForecast")}' # print further forecasted periods in short format for period in periods[2:]: - # TODO parse isDaytime true/false to squish a day together buffer += '\n' + f'{term.BOLD}{period.get("name").ljust(15)}{term.RESET}: {period.get("shortForecast")} - {period.get("temperature")}°{period.get("temperatureUnit")}, {period.get("windSpeed")} {period.get("windDirection")}' cache.write(cache_key, buffer) -- cgit