123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- # Copyright (c) 2009 Eugene Kaznacheev <qetzal@gmail.com>
- # Copyright (c) 2013 Joshua Tasker <jtasker@gmail.com>
- # Permission is hereby granted, free of charge, to any person
- # obtaining a copy of this software and associated documentation
- # files (the "Software"), to deal in the Software without
- # restriction, including without limitation the rights to use,
- # copy, modify, merge, publish, distribute, sublicense, and/or sell
- # copies of the Software, and to permit persons to whom the
- # Software is furnished to do so, subject to the following
- # conditions:
- # The above copyright notice and this permission notice shall be
- # included in all copies or substantial portions of the Software.
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- # OTHER DEALINGS IN THE SOFTWARE.
- """
- Fetches weather reports from Google Weather, Yahoo! Weather, Weather.com and NOAA
- """
- try:
- # Python 3 imports
- from urllib.request import urlopen
- from urllib.parse import quote
- from urllib.error import URLError
- except ImportError:
- # Python 2 imports
- from urllib2 import urlopen
- from urllib import quote
- from urllib2 import URLError
- import sys
- import re
- from xml.dom import minidom
- GOOGLE_COUNTRIES_URL = "http://www.google.com/ig/countries?output=xml&hl=%s"
- GOOGLE_CITIES_URL = "http://www.google.com/ig/cities?output=xml&country=%s&hl=%s"
- YAHOO_WEATHER_URL = "http://xml.weather.yahoo.com/forecastrss/%s_%s.xml"
- YAHOO_WEATHER_NS = "http://xml.weather.yahoo.com/ns/rss/1.0"
- NOAA_WEATHER_URL = "http://www.weather.gov/xml/current_obs/%s.xml"
- WEATHER_COM_URL = "http://xml.weather.com/weather/local/%s?par=1138276742&key=15ee9c789ccd70f5&unit=%s&dayf=5&cc=*"
- # WEATHER_COM_SEARCH_URL = 'http://xml.weather.com/search/search?where=%s'
- # WUNDERGROUND_URL = 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=%s'
- def get_weather_from_weather_com(location_id, units="metric"):
- """
- Fetches weather report from Weather.com
- Parameters:
- location_id: A five digit US zip code or location ID. To find your location ID,
- browse or search for your city from the Weather.com home page (http://www.weather.com/)
- The weather ID is in the URL for the forecast page for that city. You can also get
- the location ID by entering your zip code on the home page. For example, if you
- search for Los Angeles on the Weather home page, the forecast page for that city
- is http://www.weather.com/weather/today/Los+Angeles+CA+USCA0638:1:US. The location
- ID is USCA0638.
- units: type of units. 'metric' for metric and '' for non-metric
- Note that choosing metric units changes all the weather units to metric,
- for example, wind speed will be reported as kilometers per hour and
- barometric pressure as millibars.
-
- Returns:
- weather_data: a dictionary of weather data that exists in XML feed.
- """
- location_id = quote(location_id)
- if units == "metric":
- unit = "m"
- else:
- unit = ""
- url = WEATHER_COM_URL % (location_id, unit)
- try:
- handler = urlopen(url)
- except URLError:
- return {"error": "Could not connect to Weather.com"}
- if sys.version > "3":
- # Python 3
- content_type = dict(handler.getheaders())["Content-Type"]
- else:
- # Python 2
- content_type = handler.info().dict["content-type"]
- charset = re.search("charset\=(.*)", content_type).group(1)
- if not charset:
- charset = "utf-8"
- if charset.lower() != "utf-8":
- xml_response = handler.read().decode(charset).encode("utf-8")
- else:
- xml_response = handler.read()
- dom = minidom.parseString(xml_response)
- handler.close()
- try:
- weather_dom = dom.getElementsByTagName("weather")[0]
- except IndexError:
- error_data = {
- "error": dom.getElementsByTagName("error")[0]
- .getElementsByTagName("err")[0]
- .firstChild.data
- }
- dom.unlink()
- return error_data
- key_map = {
- "head": "units",
- "ut": "temperature",
- "ud": "distance",
- "us": "speed",
- "up": "pressure",
- "ur": "rainfall",
- "loc": "location",
- "dnam": "name",
- "lat": "lat",
- "lon": "lon",
- "cc": "current_conditions",
- "lsup": "last_updated",
- "obst": "station",
- "tmp": "temperature",
- "flik": "feels_like",
- "t": "text",
- "icon": "icon",
- "bar": "barometer",
- "r": "reading",
- "d": "direction",
- "wind": "wind",
- "s": "speed",
- "gust": "gust",
- "hmid": "humidity",
- "vis": "visibility",
- "uv": "uv",
- "i": "index",
- "dewp": "dewpoint",
- "moon": "moon_phase",
- "hi": "high",
- "low": "low",
- "sunr": "sunrise",
- "suns": "sunset",
- "bt": "brief_text",
- "ppcp": "chance_precip",
- }
- data_structure = {
- "head": ("ut", "ud", "us", "up", "ur"),
- "loc": ("dnam", "lat", "lon"),
- "cc": ("lsup", "obst", "tmp", "flik", "t", "icon", "hmid", "vis", "dewp"),
- }
- cc_structure = {
- "bar": ("r", "d"),
- "wind": ("s", "gust", "d", "t"),
- "uv": ("i", "t"),
- "moon": ("icon", "t"),
- }
- weather_data = {}
- for (tag, list_of_tags2) in data_structure.items():
- key = key_map[tag]
- weather_data[key] = {}
- for tag2 in list_of_tags2:
- key2 = key_map[tag2]
- weather_data[key][key2] = (
- weather_dom.getElementsByTagName(tag)[0]
- .getElementsByTagName(tag2)[0]
- .firstChild.data
- )
- cc_dom = weather_dom.getElementsByTagName("cc")[0]
- for (tag, list_of_tags2) in cc_structure.items():
- key = key_map[tag]
- weather_data["current_conditions"][key] = {}
- for tag2 in list_of_tags2:
- key2 = key_map[tag2]
- weather_data["current_conditions"][key][key2] = (
- cc_dom.getElementsByTagName(tag)[0]
- .getElementsByTagName(tag2)[0]
- .firstChild.data
- )
- forecasts = []
- time_of_day_map = {"d": "day", "n": "night"}
- for forecast in weather_dom.getElementsByTagName("dayf")[0].getElementsByTagName(
- "day"
- ):
- tmp_forecast = {}
- tmp_forecast["day_of_week"] = forecast.getAttribute("t")
- tmp_forecast["date"] = forecast.getAttribute("dt")
- for tag in ("hi", "low", "sunr", "suns"):
- key = key_map[tag]
- tmp_forecast[key] = forecast.getElementsByTagName(tag)[0].firstChild.data
- for part in forecast.getElementsByTagName("part"):
- time_of_day = time_of_day_map[part.getAttribute("p")]
- tmp_forecast[time_of_day] = {}
- for tag2 in ("icon", "t", "bt", "ppcp", "hmid"):
- key2 = key_map[tag2]
- tmp_forecast[time_of_day][key2] = part.getElementsByTagName(tag2)[
- 0
- ].firstChild.data
- tmp_forecast[time_of_day]["wind"] = {}
- for tag2 in ("s", "gust", "d", "t"):
- key2 = key_map[tag2]
- tmp_forecast[time_of_day]["wind"][key2] = (
- part.getElementsByTagName("wind")[0]
- .getElementsByTagName(tag2)[0]
- .firstChild.data
- )
- forecasts.append(tmp_forecast)
- weather_data["forecasts"] = forecasts
- dom.unlink()
- return weather_data
- def get_countries_from_google(hl=""):
- """
- Get list of countries in specified language from Google
-
- Parameters:
- hl: the language parameter (language code). Default value is empty string, in this case Google will use English.
- Returns:
- countries: a list of elements(all countries that exists in XML feed). Each element is a dictionary with 'name' and 'iso_code' keys.
- For example: [{'iso_code': 'US', 'name': 'USA'}, {'iso_code': 'FR', 'name': 'France'}]
- """
- url = GOOGLE_COUNTRIES_URL % hl
- try:
- handler = urlopen(url)
- except URLError:
- return [{"error": "Could not connect to Google"}]
- if sys.version > "3":
- # Python 3
- content_type = dict(handler.getheaders())["Content-Type"]
- else:
- # Python 2
- content_type = handler.info().dict["content-type"]
- charset = re.search("charset\=(.*)", content_type).group(1)
- if not charset:
- charset = "utf-8"
- if charset.lower() != "utf-8":
- xml_response = handler.read().decode(charset).encode("utf-8")
- else:
- xml_response = handler.read()
- dom = minidom.parseString(xml_response)
- handler.close()
- countries = []
- countries_dom = dom.getElementsByTagName("country")
- for country_dom in countries_dom:
- country = {}
- country["name"] = country_dom.getElementsByTagName("name")[0].getAttribute(
- "data"
- )
- country["iso_code"] = country_dom.getElementsByTagName("iso_code")[
- 0
- ].getAttribute("data")
- countries.append(country)
- dom.unlink()
- return countries
- def get_cities_from_google(country_code, hl=""):
- """
- Get list of cities of necessary country in specified language from Google
-
- Parameters:
- country_code: code of the necessary country. For example 'de' or 'fr'.
- hl: the language parameter (language code). Default value is empty string, in this case Google will use English.
- Returns:
- cities: a list of elements(all cities that exists in XML feed). Each element is a dictionary with 'name', 'latitude_e6' and 'longitude_e6' keys. For example: [{'longitude_e6': '1750000', 'name': 'Bourges', 'latitude_e6': '47979999'}]
- """
- url = GOOGLE_CITIES_URL % (country_code.lower(), hl)
- try:
- handler = urlopen(url)
- except URLError:
- return [{"error": "Could not connect to Google"}]
- if sys.version > "3":
- # Python 3
- content_type = dict(handler.getheaders())["Content-Type"]
- else:
- # Python 2
- content_type = handler.info().dict["content-type"]
- charset = re.search("charset\=(.*)", content_type).group(1)
- if not charset:
- charset = "utf-8"
- if charset.lower() != "utf-8":
- xml_response = handler.read().decode(charset).encode("utf-8")
- else:
- xml_response = handler.read()
- dom = minidom.parseString(xml_response)
- handler.close()
- cities = []
- cities_dom = dom.getElementsByTagName("city")
- for city_dom in cities_dom:
- city = {}
- city["name"] = city_dom.getElementsByTagName("name")[0].getAttribute("data")
- city["latitude_e6"] = city_dom.getElementsByTagName("latitude_e6")[
- 0
- ].getAttribute("data")
- city["longitude_e6"] = city_dom.getElementsByTagName("longitude_e6")[
- 0
- ].getAttribute("data")
- cities.append(city)
- dom.unlink()
- return cities
- def get_weather_from_yahoo(location_id, units="metric"):
- """
- Fetches weather report from Yahoo! Weather
- Parameters:
- location_id: A five digit US zip code or location ID. To find your location ID,
- browse or search for your city from the Yahoo! Weather home page (http://weather.yahoo.com/)
- The weather ID is in the URL for the forecast page for that city. You can also get
- the location ID by entering your zip code on the home page. For example, if you
- search for Los Angeles on the Weather home page, the forecast page for that city
- is http://weather.yahoo.com/forecast/USCA0638.html. The location ID is USCA0638.
- units: type of units. 'metric' for metric and '' for non-metric
- Note that choosing metric units changes all the weather units to metric,
- for example, wind speed will be reported as kilometers per hour and
- barometric pressure as millibars.
-
- Returns:
- weather_data: a dictionary of weather data that exists in XML feed.
- See http://developer.yahoo.com/weather/#channel
- """
- location_id = quote(location_id)
- if units == "metric":
- unit = "c"
- else:
- unit = "f"
- url = YAHOO_WEATHER_URL % (location_id, unit)
- try:
- handler = urlopen(url)
- except URLError:
- return {"error": "Could not connect to Yahoo! Weather"}
- dom = minidom.parse(handler)
- handler.close()
- weather_data = {}
- try:
- weather_data["title"] = dom.getElementsByTagName("title")[0].firstChild.data
- weather_data["link"] = dom.getElementsByTagName("link")[0].firstChild.data
- except IndexError:
- error_data = {
- "error": dom.getElementsByTagName("item")[0]
- .getElementsByTagName("title")[0]
- .firstChild.data
- }
- dom.unlink()
- return error_data
- ns_data_structure = {
- "location": ("city", "region", "country"),
- "units": ("temperature", "distance", "pressure", "speed"),
- "wind": ("chill", "direction", "speed"),
- "atmosphere": ("humidity", "visibility", "pressure", "rising"),
- "astronomy": ("sunrise", "sunset"),
- "condition": ("text", "code", "temp", "date"),
- }
- for (tag, attrs) in ns_data_structure.items():
- weather_data[tag] = xml_get_ns_yahoo_tag(dom, YAHOO_WEATHER_NS, tag, attrs)
- weather_data["geo"] = {}
- weather_data["geo"]["lat"] = dom.getElementsByTagName("geo:lat")[0].firstChild.data
- weather_data["geo"]["long"] = dom.getElementsByTagName("geo:long")[
- 0
- ].firstChild.data
- weather_data["condition"]["title"] = (
- dom.getElementsByTagName("item")[0]
- .getElementsByTagName("title")[0]
- .firstChild.data
- )
- weather_data["html_description"] = (
- dom.getElementsByTagName("item")[0]
- .getElementsByTagName("description")[0]
- .firstChild.data
- )
- forecasts = []
- for forecast in dom.getElementsByTagNameNS(YAHOO_WEATHER_NS, "forecast"):
- forecasts.append(
- xml_get_attrs(forecast, ("day", "date", "low", "high", "text", "code"))
- )
- weather_data["forecasts"] = forecasts
- dom.unlink()
- return weather_data
- def get_everything_from_yahoo(country_code, cities):
- """
- Get all weather data from yahoo for a specific country.
- Parameters:
- country_code: A four letter code of the necessary country. For example 'GMXX' or 'FRXX'.
- cities: The number of cities for which to get data
-
- Returns:
- weather_reports: A dictionary containing weather data for each city
- """
- city_codes = yield_all_country_city_codes_yahoo(country_code, cities)
- weather_reports = {}
- for city_c in city_codes:
- weather_data = get_weather_from_yahoo(city_c)
- if "error" in weather_data:
- return weather_data
- city = weather_data["location"]["city"]
- weather_reports[city] = weather_data
- return weather_reports
- def yield_all_country_city_codes_yahoo(country_code, cities):
- """
- Yield all cities codes for a specific country.
-
- Parameters:
- country_code: A four letter code of the necessary country. For example 'GMXX' or 'FRXX'.
- cities: The number of cities to yield
-
- Returns:
- country_city_codes: A generator containing the city codes
- """
- # cities stands for the number of available cities
- for i in range(1, cities + 1):
- yield "".join([country_code, (4 - len(str(i))) * "0", str(i)])
- def get_weather_from_noaa(station_id):
- """
- Fetches weather report from NOAA: National Oceanic and Atmospheric Administration (United States)
- Parameter:
- station_id: the ID of the weather station near the necessary location
- To find your station ID, perform the following steps:
- 1. Open this URL: http://www.weather.gov/xml/current_obs/seek.php?state=az&Find=Find
- 2. Select the necessary state state. Click 'Find'.
- 3. Find the necessary station in the 'Observation Location' column.
- 4. The station ID is in the URL for the weather page for that station.
- For example if the weather page is http://weather.noaa.gov/weather/current/KPEO.html -- the station ID is KPEO.
- Other way to get the station ID: use this library: http://code.google.com/p/python-weather/ and 'Weather.location2station' function.
- Returns:
- weather_data: a dictionary of weather data that exists in XML feed.
- (useful icons: http://www.weather.gov/xml/current_obs/weather.php)
- """
- station_id = quote(station_id)
- url = NOAA_WEATHER_URL % (station_id)
- try:
- handler = urlopen(url)
- except URLError:
- return {"error": "Could not connect to NOAA"}
- dom = minidom.parse(handler)
- handler.close()
- data_structure = (
- "suggested_pickup",
- "suggested_pickup_period",
- "location",
- "station_id",
- "latitude",
- "longitude",
- "observation_time",
- "observation_time_rfc822",
- "weather",
- "temperature_string",
- "temp_f",
- "temp_c",
- "relative_humidity",
- "wind_string",
- "wind_dir",
- "wind_degrees",
- "wind_mph",
- "wind_gust_mph",
- "pressure_string",
- "pressure_mb",
- "pressure_in",
- "dewpoint_string",
- "dewpoint_f",
- "dewpoint_c",
- "heat_index_string",
- "heat_index_f",
- "heat_index_c",
- "windchill_string",
- "windchill_f",
- "windchill_c",
- "icon_url_base",
- "icon_url_name",
- "two_day_history_url",
- "ob_url",
- )
- weather_data = {}
- current_observation = dom.getElementsByTagName("current_observation")[0]
- for tag in data_structure:
- try:
- weather_data[tag] = current_observation.getElementsByTagName(tag)[
- 0
- ].firstChild.data
- except IndexError:
- pass
- dom.unlink()
- return weather_data
- def xml_get_ns_yahoo_tag(dom, ns, tag, attrs):
- """
- Parses the necessary tag and returns the dictionary with values
-
- Parameters:
- dom: DOM
- ns: namespace
- tag: necessary tag
- attrs: tuple of attributes
- Returns:
- a dictionary of elements
- """
- element = dom.getElementsByTagNameNS(ns, tag)[0]
- return xml_get_attrs(element, attrs)
- def xml_get_attrs(xml_element, attrs):
- """
- Returns the list of necessary attributes
-
- Parameters:
- element: xml element
- attrs: tuple of attributes
- Returns:
- a dictionary of elements
- """
- result = {}
- for attr in attrs:
- result[attr] = xml_element.getAttribute(attr)
- return result
- def wind_direction(degrees):
- """ Convert wind degrees to direction """
- try:
- degrees = int(degrees)
- except ValueError:
- return ""
- if degrees < 23 or degrees >= 338:
- return "N"
- elif degrees < 68:
- return "NE"
- elif degrees < 113:
- return "E"
- elif degrees < 158:
- return "SE"
- elif degrees < 203:
- return "S"
- elif degrees < 248:
- return "SW"
- elif degrees < 293:
- return "W"
- elif degrees < 338:
- return "NW"
- def wind_beaufort_scale(km_per_hour):
- """ Convert km/h to beaufort """
- try:
- km_per_hour = int(km_per_hour)
- except ValueError:
- return ""
- if km_per_hour < 1:
- return "0"
- elif km_per_hour <= 5.5:
- return "1"
- elif km_per_hour <= 11:
- return "2"
- elif km_per_hour <= 19:
- return "3"
- elif km_per_hour <= 28:
- return "4"
- elif km_per_hour <= 38:
- return "5"
- elif km_per_hour <= 49:
- return "6"
- elif km_per_hour <= 61:
- return "7"
- elif km_per_hour <= 74:
- return "8"
- elif km_per_hour <= 88:
- return "9"
- elif km_per_hour <= 102:
- return "10"
- elif km_per_hour <= 117:
- return "11"
- else:
- return "12"
- def getText(nodelist):
- rc = ""
- for node in nodelist:
- if node.nodeType == node.TEXT_NODE:
- rc = rc + node.data
- return rc
|