I bodged together a bit of CLI code to retrieved your local forecast by passing the latitude and longitude. It isn’t elegant code, but it is part of a technical book called ‘Practical Python for People’ where I walk you through collecting data from the internet and doing basic analysis of said data. So have fun! (ps, wordpress won’t let me link a python script, which is probably for the best)
#These modules should be standard in current Python distros (3.1+)
#requests module to retrieve API urls
import requests
#regular expressions module for Emoji function
import re
#system module for using passed command line string
import sys
#datetime module to make pretty time strings
from datetime import datetime
"""Sample request script to retrieve hourly or daily forecasts
forecast.py 'lat=<latitude>,long=<longitude>,retrieve=<hourly or daily>,number=<number of entries to retrieve>'
lat = latitude, passed as a number
long = longitude, passed as a number
for example; lat=33.93806,long=-118.38889
retrieve = what to retrieve 'd'aily or 'h'ourly
number = number of records to display, 15 max for daily, 156 for hourly
This script only works in the US via NWS Forecast API!
see: https://www.weather.gov/documentation/services-web-api
"""
"""Functions to make your life easier"""
#Print out the help message
def cl_help_msg():
""" The help message"""
print('Forecaster.py expects four command line arguments passed as a single string;')
print('\'lat=,long=,retrieve=,number=\'')
print()
print('where lat=latitude, long=longitude, retrieve = h (hours) or d (days),')
print('number = number of entries to retrieve is limited to 14 for daily and 156 for hourly records')
print('passing h, H, or help will display this message')
print()
print('for example. To retrieves the next 4 daily entries forcasted for LAX;')
print('forecaster.py \'lat=33.93,long=-118.38,retrieve=d,number=4\'')
#Error message function - Kinda clunky
def error_messages(errno=0):
"""Generate error messayges"""
f = 'Error: {error}, MSG: {msg}'
match errno:
case 0:
print('An unspecified error occured')
case 1:
print(f.format(error='One', msg='Invalid command line arguments'))
case 2:
print(f.format(error='Two', msg='Bad grid request. Check lat/long'))
cl_help_msg()
#Pass a string and get an emoji. Could use some work
def weather_conditions_emoji(cc):
"""
Return an emoji from a passed string.
this is not very good, and really needs work.
"""
if cc == None:
return 'na'
if bool(re.search(r'Thunder|T-storms', cc)):
return '🌩️ '
if bool(re.search(r'Rain', cc)):
return '🌧️ '
if bool(re.search(r'Partly', cc)):
return '🌤️ '
if bool(re.search(r'Mostly', cc)):
return '🌥️ '
if bool(re.search(r'Cloudy|Overcast', cc)):
return '☁️ '
if bool(re.search(r'Rain', cc)):
return '🌧️ '
if bool(re.search(r'Snow', cc)):
return '🌨️ '
if bool(re.search(r'Fog', cc)):
return '🌁 '
if bool(re.search(r'Clear|Sunny', cc)):
return '☀️ '
#Return forecast grid json request from lat/long
def getGrid(slocation):
"""
Get the grid from lat/long passed as a string ('lat,long')
"""
try:
r = requests.get('https://api.weather.gov/points/' + slocation)
#return r
r.raise_for_status()
return r
except:
return 'error'
#Pass a grid and return hourly forecast response
def gethourlyforecast(grid):
"""Get the hourly forecast from NWS API"""
try:
spath = grid['forecastHourly']
r = requests.get(spath)
r.raise_for_status()
return r
except requests.exceptions.HTTPError as e:
print('HTTP CONNECION ERROR', r.text)
print(e)
return r
except requests.exceptions.RequestException as e:
print('REQUEST EXCEPTION:', r.text)
print(e)
return r
except:
print('an unaccounted for error occurred')
#return r
#pass a grid get the daily forecast response
def getforecast(grid):
#look up grid and reporting office
"""Get daily forecast"""
try:
#Get the path from the string in the grid
spath = grid['forecast']
r = requests.get(spath)
r.raise_for_status()
return r
except requests.exceptions.HTTPError as e:
print('HTTP CONNECION ERROR', r.text)
print(e)
return r
except requests.exceptions.RequestException as e:
print('REQUEST EXCEPTION:', r.text)
print(e)
return r
except:
print('an unaccounted for error occurred')
""" The Program Itself """
#Get Command Line String if it exists
try:
baselines = sys.argv[1]
except:
#print help and quit
print('error')
cl_help_msg()
exit()
#oh, you need help!
if baselines == 'h' or baselines == 'H' or baselines == 'help':
#print help and quit
cl_help_msg()
exit()
#It's the string so:
#1. Split it
try:
command_line = baselines.split(',')
except:
error_messages(1)
exit()
#2. validate command_line list. This should be at least 4
# with a future optional arguments to output the data to CSV file, with a possible
#default for type and periods
if len(command_line) >= 4:
lat = ''
long = ''
rtype = ''
periods = 0
#3. Parse the list
for i in command_line:
tmp_split = i.split('=')
if tmp_split[0] == 'lat': lat=tmp_split[1]
if tmp_split[0] == 'long': long=tmp_split[1]
if tmp_split[0] == 'retrieve': rtype=tmp_split[1]
if tmp_split[0] == 'number': periods=int(tmp_split[1])
try:
#Pass latitude and longitude to get the forecast grid dictionary
grid = getGrid(str(lat) + ',' + str(long)).json()['properties']
except:
error_messages(2)
exit()
#print(grid)
#Just procededural - strings, format, pieces and parts - good housekeeping
fo = '{num} {name}: {icon} {desc} \n It will be {tmp}°F with winds {wd} at {ws} \n {prcp}% chance of precipitation until {end}'
title = ''
weather = ''
#Local indentifiers from the grid dictionary
city = grid['relativeLocation']['properties']['city']
state = grid['relativeLocation']['properties']['state']
#Make a title string - we will add to it shortly
title = '{tod} Forecast for ' + city + ', ' + state + ', from station {stn} for next {periods} {tod2}'
#This is funky. You have to call for a complete list of stations associated with lat long we passed earlier
#so we grab the whole station list from a requests using a path provided by the grid dictionary
#then we just pluck what we need from the request.json() response. That dictionary path is selected
# by indexing 0, the first entry, which is the closest station. Yes, it is one big, long, run on sentence
# but it works, and why should you monkey about when you can just grab it and move on
#Now, clearly there are many (many) ways this can fail, so that should be cleaned up
try:
station = requests.get(grid['observationStations']).json()['features'][0]['properties']['stationIdentifier']
except:
error_messages(2)
exit()
#Okay, let's get the forecast by passing our grid
#Do you want the hourly forecast?
if rtype == 'h':
weather = gethourlyforecast(grid)
title = title.format(tod='Hourly', stn=station, periods=str(periods), tod2='hours')
#Or the daily forecast?
if rtype == 'd':
weather = getforecast(grid)
title = title.format(tod='Daily', stn=station, periods=str(periods), tod2='days')
print(title)
#We just need the dictionary of forecast entries
weather = weather.json()['properties']['periods']
#COUNTER!
thenum = 0
#retrieve the records
while thenum <= (periods -1):
desc = weather[thenum]['shortForecast']
# swap the description for daily records since the
# daily and hourly forecasts deal with them differently
if rtype == 'd':
desc = weather[thenum]['detailedForecast']
#Grab an emoji
icon = weather_conditions_emoji(desc)
#Convert it to a datetime timestamp, then covert it to a prettier string
thedate = datetime.strptime(weather[thenum]['endTime'], '%Y-%m-%dT%H:%M:%S%z').strftime('%A, %B %-d, %Y %X')
#Build the forecast string for display
fprint = fo.format(num=weather[thenum]['number'], name=weather[thenum]['name'], desc=desc,
tmp=weather[thenum]['temperature'], wd=weather[thenum]['windDirection'],
ws=weather[thenum]['windSpeed'],
prcp=weather[thenum]['probabilityOfPrecipitation']['value'],
end=thedate, icon=icon)
print(fprint)
thenum = thenum + 1
else:
#Nothing worked so spit out help again
error_messages(0)
exit()
