Source code for aaa_modules.environment_canada
'''
An utility class to retrieve the weather forecast from the Environment Canada
service.
https://www.weather.gc.ca/forecast/
'''
import re
import time
import urllib2
[docs]class Forecast(object):
'''
Represent the weather forecast.
'''
[docs] def __init__(self, forecastTime, temperature, condition,
precipationProbability, wind):
self._forecastTime = forecastTime
self._temperature = temperature
self._condition = condition
self._precipationProbability = precipationProbability
self._wind = wind
[docs] def getForecastTime(self):
'''
Return the hour in 24-hour format.
:rtype: int
'''
return self._forecastTime
[docs] def getUserFriendlyForecastTime(self):
'''
Returns the forecast hour in user friendly format (1AM, 2PM,...)
:rtype: str
'''
hour = self._forecastTime
if hour == 0:
return 'Midnight'
elif hour < 12:
return str(hour) + ' AM'
elif hour == 12:
return 'Noon'
else:
return str(hour - 12) + ' PM'
[docs] def getTemperature(self):
'''
Return the temperature in Celcius.
:rtype: int
'''
return self._temperature
[docs] def getCondition(self):
'''
Return the weather condition.
:rtype: str
'''
return self._condition
[docs] def getPrecipationProbability(self):
'''
Return the precipation probability.
Possible values: High (70%+), Medium (60% - 70%), Low (< 40%), or Nil (0%).
:rtype: str
'''
return self._precipationProbability
[docs] def getWind(self):
'''
Return the wind info such as "15 NW".
:rtype: str
'''
return self._wind
def __str__(self):
'''
:rtype: str
'''
return unicode(self).encode('utf-8')
def __unicode__(self):
'''
:rtype: unicode
'''
str = u"{:5}: {:7} {:25} {:6} {:6}".format(self.getForecastTime(),
self.getTemperature(), self.getCondition(),
self.getPrecipationProbability(), self.getWind())
return str
[docs]class EnvCanada(object):
'''
Utility class to retreive the hourly forecast.
'''
CITY_FORECAST_MAPPING = { 'ottawa' : 'on-118' }
'''
Mapping from lowercase city name to the env canada identifier.
'''
[docs] @staticmethod
def retrieveHourlyForecast(cityOrUrl, hourCount = 12):
'''
Retrieves the hourly forecast for the given city.
:param str cityOrUrl: the city name or the Environment Canada website\
'https://www.weather.gc.ca/forecast/hourly/on-118_metric_e.html'
:param int hourCount: the # of forecast hour to get, starting from \
the next hour relative to the current time.
:rtype: list(Forecast)
:raise: ValueError if cityOrUrl points to an invalid city, or if \
hourCount is more than 24 and less than 1.
'''
if hourCount > 24 or hourCount < 1:
raise ValueError("hourCount must be between 1 and 24.")
if cityOrUrl[0:6].lower() != 'https:':
normalizedCity = cityOrUrl.lower()
if normalizedCity not in EnvCanada.CITY_FORECAST_MAPPING:
raise ValueError(
"Can't map city name to URL for {}".format(cityOrUrl))
url = 'https://www.weather.gc.ca/forecast/hourly/{}_metric_e.html'.format(
EnvCanada.CITY_FORECAST_MAPPING[normalizedCity])
else:
url = cityOrUrl
data = urllib2.urlopen(url).read()
data = unicode(data, 'utf-8')
timeStruct = time.localtime()
hourOfDay = timeStruct[3]
pattern = r"""header2.*?\>(\d+)< # temp
.*?<p>(.*?)</p> # condition
.*?header4.*?>(.+?)< # precip probability
.*?abbr.*?>(.+?)</abbr> (.*?)< # wind direction and speed
"""
forecasts = []
index = 0
for increment in range(1, hourCount + 1):
hour = (hourOfDay + increment) % 24
hourString = ("0" + str(hour)) if hour < 10 else str(hour)
hourString += ":00"
searchString = '<td headers="header1" class="text-center">{}</td>'.format(hourString)
index = data.find(searchString, index)
subdata = data[index:]
match = re.search(pattern, subdata,
re.MULTILINE | re.DOTALL | re.VERBOSE)
if not match:
raise ValueError("Invalid pattern.")
temperature = int(match.group(1))
condition = match.group(2)
precipProb = match.group(3)
wind = u'' + match.group(5) + ' ' + match.group(4)
forecasts.append(Forecast(hour, temperature, condition, precipProb, wind))
return forecasts