Weather Forecasting App Using Python

How to create a weather forecasting app using Python.

Introduction

An application that prompts the user for a city or zip code and requests a weather report or forecast - based upon the user’s selection - from OpenWeatherMap.

weatherApp.py retrieves the requested city data from OWM, pulls in reverse-geolocation state and county/province information using the reverse_geocoder module, and then presents the data to the user in an easy-to-read format.

# Program Purpose:
#
# An application that prompts the user for a city or zip code and requests a
# weather report or forecast - based upon the user's selection - from OpenWeatherMap.
#
# weatherApp retrieves the requested city data from OWM, pulls in reverse-geolocation state and county/province
# information using the reverse_geocoder module, and then presents the data to the user in an easy-to-read format.
#

# ----------| MODULES |----------

# Import the modules:

import requests
import json
import time
import reverse_geocoder  # module info at: https://github.com/thampiman/reverse-geocoder
import datetime


# ----------| WELCOME AND HEADER |----------

# Let's welcome the user with a brief introduction

# First we create a few variables that style the heading output:

def heading():
    global headingLine
    headingLine = "*" * 75
    headingTitle = "Welcome to the DSC510 Weather Machine!"

    # Print the program header:

    print(headingLine)
    print("{:^75}".format(headingTitle))
    print(headingLine)
    print("\n")
    print("Get a weather report or forecast for any city or ZIP code you desire!\n")


# ----------| MENU |----------

# Prompt the user to enter a city name or zip code to get a weather report or
# forecast and provide a helpful menu system to drive them through the process:

def menu():

    global menuInput, menuForecastCity, menuForecastZIP, menuReportCity, menuReportZIP

    menuInput = input('''
Please make a choice from the menu items below: 

1 for forecast by city
2 for forecast ZIP Code
3 for report by city
4 for report ZIP Code
\n''')

    # Loop the program based upon the user's selection(s):

    if menuInput == '1':
        menuForecastCity = input("What is the city name? ")
        wf.weatherForecastInput()
    elif menuInput == '2':
        try:
            menuForecastZIP = int(input("What is the ZIP code? "))
            wf.weatherForecastInput()
        except ValueError:
            print("\nYou have not entered a valid ZIP code. Please try again.")
    elif menuInput == '3':
        menuReportCity = input("What is the city name? ")
        wr.weatherReportInput()
    elif menuInput == '4':
        try:
            menuReportZIP = int(input("What is the ZIP code? "))
            wr.weatherReportInput()
        except ValueError:
            print("\nYou have not entered a valid ZIP code. Please try again.")
    else:
        print("You have not made a valid entry.")

    return menuInput


# ----------| WEATHER REPORT |----------

# 1) Make a call to the OpenWeatherMap API
# 2) Get current weather data in return and put it into a JSON dictionary
# 3) Print the current weather information to the screen


class WeatherReport:

    def __init__(self):
        self.apiID = "a42694a8daa8291c88ff863a26c2da62"
        self.units = "imperial"
        self.apiWeather = "https://api.openweathermap.org/data/2.5/weather"

    # A method that gets the user's requested city weather information:

    def weatherReportInput(self):

        global requestWeather, weatherReportTitle, weatherData

        # Define the variables that will pass the user's city and the API key
        # to the OWM weather API and make a get request using that information:

        if menuInput == '3':
            payload = {'q': menuReportCity, 'APPID': self.apiID, 'units': self.units}
            requestWeather = requests.get(self.apiWeather, params=payload)
        elif menuInput == '4':
            payload = {'zip': menuReportZIP, 'APPID': self.apiID, 'units': self.units}
            requestWeather = requests.get(self.apiWeather, params=payload)

        # Ensure the connection was successful and that the user made a valid city
        # or ZIP entry and put the returned data from the API into a JSON dictionary:

        requestStatus = requestWeather.status_code
        if requestStatus == 200:  # This is our check to ensure a successful web connection
            print("\nConnecting to OWM... success!")
            weatherData = json.loads(requestWeather.text)
        else:
            print("\nThe city or ZIP code you entered is not valid, or there was a connection error."
                  "\nPlease try again.")
            menu()

        # Define variables for the different types of weather data we will present
        # and get their values from the JSON dictionary we created:

        wCountry = weatherData['sys']['country']
        wTemp = weatherData['main']['temp']
        wWindSpeed = weatherData['wind']['speed']
        wDescription = weatherData['weather'][0]['description']
        wHumidty = weatherData['main']['humidity']
        wPressure = weatherData['main']['pressure']
        wSunrise = weatherData['sys']['sunrise']
        wSunset = weatherData['sys']['sunset']
        wLatitude = weatherData['coord']['lat']
        wLongitude = weatherData['coord']['lon']

        # Let the user know something is happening before the next step:

        if menuInput == '3':
            print("\nLoading the weather data for", menuReportCity, "...\n")
            weatherReportTitle = "Weather Report for " + menuReportCity.capitalize()
        elif menuInput == '4':
            print("\nLoading the weather data for", menuReportZIP, "...\n")
            weatherReportTitle = "Weather Report for " + str(menuReportZIP)

        # OWM does not display state, county, or country information so let's use another webservice
        # and the reverse_geocoder module to get that data from the latitude and longitude information
        # from OWM's data. Module info here: (https://pypi.org/project/reverse_geocoder/)

        # Lookup reverse geographical information based upon latitude and longitude results from OWM:

        coordinates = (wLatitude, wLongitude)
        geoLookupR = reverse_geocoder.search(coordinates)

        # Define variables for the geographical data we will present
        # and get their values from the JSON dictionary we created:

        weatherCity = geoLookupR[0]['name']
        weatherState = geoLookupR[0]['admin1']
        weatherCounty = geoLookupR[0]['admin2']

        # ----------| PRINT WEATHER REPORT |----------

        # This is the area that presents all of our inputted and gathered data into a nice, readable
        # format for the user:

        # DEBUG

        # Uncomment the lines below to print the weather and geolocation data in the JSON dictionaries
        # when running the program. This will help to verify the right URL is getting accessed and that
        # all of the expected information is retrieved:

        # print(requestWeather.url) # <--- Display the full url that we are accessing:
        # print(json.dumps(weatherData, indent=4, sort_keys=True, separators=(',', ':')))
        # print(json.dumps(geoLookupR, indent=4, sort_keys=True, separators=(',', ':')))

        # Print the weather report:

        print("\n")
        print(headingLine)
        print("{:^75}".format(weatherReportTitle.title()))
        print(headingLine)
        print("\n")
        print("City:", weatherCity)
        print("State:", weatherState)
        print("County:", weatherCounty)
        print("Country:", wCountry)
        print("Conditions:", wDescription.capitalize())
        print("Temp:", round(wTemp), "\u00b0" + "F")
        print("Humidity:", wHumidty, "%")
        print("Pressure:", wPressure, "hpa")
        print("Wind:", wWindSpeed, "mph")
        print("Sunrise:", time.ctime(wSunrise))  # Convert from OWM's Unix time to more human readable
        print("Sunset:", time.ctime(wSunset))  # Convert from OWM's Unix time to more human readable
        print("Latitude:", wLatitude)
        print("Longitude:", wLongitude)
        print("\n")
        print(headingLine)


wr = WeatherReport()

# ----------| WEATHER FORECAST |----------

# 1) Make a call to the OpenWeatherMap API
# 2) Get weather forecast data in return and put it into a JSON dictionary
# 3) Print the current weather forecast information to the screen


class WeatherForecast:

    def __init__(self):
        self.apiID = "a42694a8daa8291c88ff863a26c2da62"
        self.units = "imperial"
        self.apiForecast = "https://api.openweathermap.org/data/2.5/forecast"

    # A method that gets the user's requested city weather forecast information:

    def weatherForecastInput(self):

        global requestForecast, weatherForecastTitle, forecastData

        # Define the variables that will pass the user's city and the API key
        # to the OWM forecast API and make a get request using that information:

        if menuInput == '1':
            payload = {'q': menuForecastCity, 'APPID': self.apiID, 'units': self.units}
            requestForecast = requests.get(self.apiForecast, params=payload)
        elif menuInput == '2':
            payload = {'zip': menuForecastZIP, 'APPID': self.apiID, 'units': self.units}
            requestForecast = requests.get(self.apiForecast, params=payload)

        # Ensure the connection was successful and that the user made a valid city
        # or ZIP entry and put the returned data from the API into a JSON dictionary:

        requestStatus = requestForecast.status_code
        if requestStatus == 200:  # This is our check to ensure a successful web connection
            print("\nConnecting to OWM... success!")
            forecastData = json.loads(requestForecast.text)
        else:
            print("\nThe city or ZIP code you entered is not valid, or there was a connection error."
                  "\nPlease try again.")
            menu()

        # Define variables for the different types of weather data we will present
        # and get their values from the JSON dictionary we created:

        forecastCountry = forecastData['city']['country']

        fcTemp01 = forecastData['list'][4]['main']['temp']
        fcTemp02 = forecastData['list'][12]['main']['temp']
        fcTemp03 = forecastData['list'][20]['main']['temp']
        fcTemp04 = forecastData['list'][28]['main']['temp']
        fcTemp05 = forecastData['list'][36]['main']['temp']

        fcDesc01 = forecastData['list'][4]['weather'][0]['description']
        fcDesc02 = forecastData['list'][12]['weather'][0]['description']
        fcDesc03 = forecastData['list'][20]['weather'][0]['description']
        fcDesc04 = forecastData['list'][28]['weather'][0]['description']
        fcDesc05 = forecastData['list'][36]['weather'][0]['description']

        fcWind01 = forecastData['list'][4]['wind']['speed']
        fcWind02 = forecastData['list'][12]['wind']['speed']
        fcWind03 = forecastData['list'][20]['wind']['speed']
        fcWind04 = forecastData['list'][28]['wind']['speed']
        fcWind05 = forecastData['list'][36]['wind']['speed']

        fcHumidity01 = forecastData['list'][4]['main']['humidity']
        fcHumidity02 = forecastData['list'][12]['main']['humidity']
        fcHumidity03 = forecastData['list'][20]['main']['humidity']
        fcHumidity04 = forecastData['list'][28]['main']['humidity']
        fcHumidity05 = forecastData['list'][36]['main']['humidity']

        # forecastPressure = forecastData['list'][0]['main']['pressure']

        forecastLatitude = forecastData['city']['coord']['lat']
        forecastLongitude = forecastData['city']['coord']['lon']

        # Let the user know something is happening before the next step:

        if menuInput == '1':
            print("\nLoading the weather data for", menuForecastCity, "...\n")
            weatherForecastTitle = "Weather Forecast for " + menuForecastCity.capitalize()
        elif menuInput == '2':
            print("\nLoading the weather data for", menuForecastZIP, "...\n")
            weatherForecastTitle = "Weather Forecast for " + str(menuForecastZIP)

        # OWM does not display state, county, or country information so let's use another webservice
        # and the reverse_geocoder module to get that data from the latitude and longitude information
        # from OWM's data. Module info here: (https://pypi.org/project/reverse_geocoder/)

        # Lookup reverse geographical information based upon latitude and longitude results from OWM:

        coordinates = (forecastLatitude, forecastLongitude)
        geoLookupF = reverse_geocoder.search(coordinates)

        # Define variables for the geographical data we will present
        # and get their values from the JSON dictionary we created:

        forecastCity = geoLookupF[0]['name']
        forecastState = geoLookupF[0]['admin1']
        forecastCounty = geoLookupF[0]['admin2']

        # Define variables for getting the dates in the forecast to display nicely:

        day1 = datetime.date.today() + datetime.timedelta(days=1)
        day2 = datetime.date.today() + datetime.timedelta(days=2)
        day3 = datetime.date.today() + datetime.timedelta(days=3)
        day4 = datetime.date.today() + datetime.timedelta(days=4)
        day5 = datetime.date.today() + datetime.timedelta(days=5)

        # ----------| PRINT WEATHER FORECAST |----------

        # This is the area that presents all of our inputted and gathered data into a nice, readable
        # format for the user:

        # DEBUG

        # Uncomment the lines below to print the weather and geolocation data in the JSON dictionaries
        # when running the program. This will help to verify the right URL is getting accessed and that
        # all of the expected information is retrieved:

        # print(requestForecast.url)  # <--- Display the full url that we are accessing:
        # print(json.dumps(forecastData, indent=4, sort_keys=True, separators=(',', ':')))
        # print(json.dumps(geoLookupF, indent=4, sort_keys=True, separators=(',', ':')))

        # Print the forecast:

        # Print the heading and a few geographic bits of information.

        print("\n")
        print(headingLine)
        print("{:^75}".format(weatherForecastTitle.title()))
        print(headingLine)
        print("\n")
        print("City:", forecastCity)
        print("State:", forecastState)
        print("County:", forecastCounty)
        print("Country:", forecastCountry)
        print("\n")

        # Print the five day forecast:

        print("Five-Day Forecast:")
        print("\n")
        print('{:<14}'.format("Date"), '{:<12}'.format("Temp"), '{:<10}'.format("Wind"), '{:<18}'.format("Humidity"),
              "Outlook")
        print("-" * 75)
        print('{:<14}'.format(day1.strftime("%b %d")), round(fcTemp01), '{:<9}'.format("\u00b0" + "F"),
              round(fcWind01), '{:<11}'.format("mph"), round(fcHumidity01), '{:<9}'.format("%"), str.capitalize(fcDesc01))
        print('{:<14}'.format(day2.strftime("%b %d")), round(fcTemp02), '{:<9}'.format("\u00b0" + "F"),
              round(fcWind02), '{:<11}'.format("mph"), round(fcHumidity02), '{:<9}'.format("%"), str.capitalize(fcDesc02))
        print('{:<14}'.format(day3.strftime("%b %d")), round(fcTemp03), '{:<9}'.format("\u00b0" + "F"),
              round(fcWind03), '{:<11}'.format("mph"), round(fcHumidity03), '{:<9}'.format("%"), str.capitalize(fcDesc03))
        print('{:<14}'.format(day4.strftime("%b %d")), round(fcTemp04), '{:<9}'.format("\u00b0" + "F"),
              round(fcWind04), '{:<11}'.format("mph"), round(fcHumidity04), '{:<9}'.format("%"), str.capitalize(fcDesc04))
        print('{:<14}'.format(day5.strftime("%b %d")), round(fcTemp05), '{:<9}'.format("\u00b0" + "F"),
              round(fcWind05), '{:<11}'.format("mph"), round(fcHumidity05), '{:<9}'.format("%"), str.capitalize(fcDesc05))
        print("\n")
        print(headingLine)


wf = WeatherForecast()


# ----------| REPEAT |----------

# Ask the user if they would like to continue getting more weather information:

def repeat():
    global repeatSelection
    answer = "y"
    while answer == "y":
        repeatSelection = input('''
Would you like to run the program again?

y for Yes
n for No
''')
        if repeatSelection.lower() == "y":
            menu()
            continue
        elif repeatSelection.lower() == "n":
            print("Thanks for using the DSC510 Weather Machine, see you next time!")
            break
        else:
            print("That was not a valid selection. Please try again.")
        continue


# ----------| MAIN |----------

def main():
    if __name__ == '__main__':
        heading()
        menu()
        repeat()


main()