From d39f6c9ec558fe2d6d5761e88a13ef9dd4152054 Mon Sep 17 00:00:00 2001 From: Starfall Date: Thu, 1 Dec 2022 11:37:03 -0600 Subject: rename to meteor --- meteor.py | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ weather.py | 102 ------------------------------------------------------------- 2 files changed, 102 insertions(+), 102 deletions(-) create mode 100755 meteor.py delete mode 100755 weather.py diff --git a/meteor.py b/meteor.py new file mode 100755 index 0000000..a63d304 --- /dev/null +++ b/meteor.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +from argparse import ArgumentParser +import requests + +# 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.1 (us@starfall.systems)', + 'accept': 'application/ld+json' +} + +# SGR decorations / ANSI escape colors +class term: + RESET = '\033[0m' + BOLD = '\033[1m' + +def handle_args(): + parser = ArgumentParser() + parser.add_argument('lat', help='latitude') + parser.add_argument('lon', help='longitude') + return parser.parse_args() + +def print_alerts(county, zone): + # in some areas, alerts are not published for the zone but only for the county + # you can request both like this rather than making two requests + 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'): + print(f'{alert.get("headline")}') + 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. + # verious datetimes: sent, effective, onset, expires, ends + # severity: Minor, Moderate, Severe, Extreme, Unknown + # certainty: Possible, Likely, Observed, Unknown + # urgency: Future, Expected, Immediate, Unknown + # messageType: Alert, Update + + # can construct the headline ourseves as f'{event} issued {sent} [until {end}] by {senderName}' + print() + +def print_forecast(forecast_url): + # 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 + + forecast = requests.get(forecast_url, headers=headers).json() + # TODO retry on 500 - known issue + # TODO query param units=si + # TODO cache for like four hours per location, forecast doesn't change that frequently + + # 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 + + print(f'{term.BOLD}forecast') + print(f'===================={term.RESET}') + for period in forecast.get('periods'): + # TODO parse isDaytime true/false to squish a day together + print(f'{term.BOLD}{period.get("name")}{term.RESET}:') + print(f' {period.get("temperature")}°{period.get("temperatureUnit")}, wind {period.get("windSpeed")} {period.get("windDirection")}') + print(f' {period.get("detailedForecast")}') + +def main(): + args = handle_args() + + point = requests.get(base_url + f'/points/{args.lat},{args.lon}', + headers=headers).json() + + county = point.get('county').split('/')[-1] + zone = point.get('forecastZone').split('/')[-1] + print_alerts(county, zone) + + # TODO pick out location name from somewhere. + # city and state hide in point.relativeLocation.properties.city (and state) + # getting the county or zone's `name (id) state` is good too + + # TODO can hazardous weather outlook be gotten programatically? + + + forecast_url = point.get('forecast') + print_forecast(forecast_url) + + # hourly_forecast_url = point.get('forecastHourly') + +if __name__ == '__main__': + import sys + sys.exit(main()) diff --git a/weather.py b/weather.py deleted file mode 100755 index a30a4e7..0000000 --- a/weather.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -from argparse import ArgumentParser -import requests - -# 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': 'Starweather/0.1 (us@starfall.systems)', - 'accept': 'application/ld+json' -} - -# SGR decorations / ANSI escape colors -class term: - RESET = '\033[0m' - BOLD = '\033[1m' - -def handle_args(): - parser = ArgumentParser() - parser.add_argument('lat', help='latitude') - parser.add_argument('lon', help='longitude') - return parser.parse_args() - -def print_alerts(county, zone): - # in some areas, alerts are not published for the zone but only for the county - # you can request both like this rather than making two requests - 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'): - print(f'{alert.get("headline")}') - 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. - # verious datetimes: sent, effective, onset, expires, ends - # severity: Minor, Moderate, Severe, Extreme, Unknown - # certainty: Possible, Likely, Observed, Unknown - # urgency: Future, Expected, Immediate, Unknown - # messageType: Alert, Update - - # can construct the headline ourseves as f'{event} issued {sent} [until {end}] by {senderName}' - print() - -def print_forecast(forecast_url): - # 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 - - forecast = requests.get(forecast_url, headers=headers).json() - # TODO retry on 500 - known issue - # TODO query param units=si - # TODO cache for like four hours per location, forecast doesn't change that frequently - - # 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 - - print(f'{term.BOLD}forecast') - print(f'===================={term.RESET}') - for period in forecast.get('periods'): - # TODO parse isDaytime true/false to squish a day together - print(f'{term.BOLD}{period.get("name")}{term.RESET}:') - print(f' {period.get("temperature")}°{period.get("temperatureUnit")}, wind {period.get("windSpeed")} {period.get("windDirection")}') - print(f' {period.get("detailedForecast")}') - -def main(): - args = handle_args() - - point = requests.get(base_url + f'/points/{args.lat},{args.lon}', - headers=headers).json() - - county = point.get('county').split('/')[-1] - zone = point.get('forecastZone').split('/')[-1] - print_alerts(county, zone) - - # TODO pick out location name from somewhere. - # city and state hide in point.relativeLocation.properties.city (and state) - # getting the county or zone's `name (id) state` is good too - - # TODO can hazardous weather outlook be gotten programatically? - - - forecast_url = point.get('forecast') - print_forecast(forecast_url) - - # hourly_forecast_url = point.get('forecastHourly') - -if __name__ == '__main__': - import sys - sys.exit(main()) -- cgit