rubyweather 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +504 -0
- data/README +113 -0
- data/lib/forecast.rb +318 -0
- data/lib/service.rb +84 -0
- data/lib/util.rb +17 -0
- data/test/forecast_test.rb +77 -0
- data/test/service_test.rb +62 -0
- data/test/test_weather.xml +215 -0
- data/weather.rb +3 -0
- metadata +60 -0
data/README
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
= RubyWeather
|
2
|
+
|
3
|
+
Author:: Matt Zukowski (http://blog.roughest.net)
|
4
|
+
Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
5
|
+
License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
6
|
+
|
7
|
+
<b>RubyWeather is a Ruby[http://ruby-lang.org] library for fetching weather-related data from weather.com[http://www.weather.com/services/xmloap.html].</b>
|
8
|
+
|
9
|
+
Simple usage example:
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require_gem 'rubyweather'
|
13
|
+
|
14
|
+
require 'weather'
|
15
|
+
|
16
|
+
service = Weather::Service.new
|
17
|
+
service.partner_id = <b>your partner id</b>
|
18
|
+
service.license_key = <b>your license key</b>
|
19
|
+
|
20
|
+
locations = service.find_location('Toronto')
|
21
|
+
puts "Matching Locations: " + locations.inspect
|
22
|
+
|
23
|
+
The above will print out a list of available locations and their codes. We can
|
24
|
+
now use these codes to fetch the weather data for our city:
|
25
|
+
|
26
|
+
forecast = service.fetch_forecast("CAXX0504", 5)
|
27
|
+
|
28
|
+
puts "Location: %s" % forecast.location_name
|
29
|
+
|
30
|
+
puts "Current Temperature: %s" % forecast.current.temperature
|
31
|
+
puts "Current Windspeed: %s" % forecast.current.wind.speed
|
32
|
+
|
33
|
+
puts "Tomorrow's High: %s" % forecast.tomorrow.high
|
34
|
+
puts "Tomorrow's Outlook: %s" % forecast.tomorrow.outlook
|
35
|
+
puts "Tomorrow's Wind Direction: %s" % forecast.tomorrow.wind.direction
|
36
|
+
|
37
|
+
Forecasts for days in the future are accessed via <tt>forecast.day(#)</tt> where <tt>#</tt> is the number of day sinto the future
|
38
|
+
(assuming that you've fetched data for as many days in your <tt>service.fetch_forecast</tt> request):
|
39
|
+
|
40
|
+
puts "High 3 days from now: %s" % forecast.day(3).high
|
41
|
+
puts "Probability of precipitation 4 days from now: %s" % forecast.day(4).pop
|
42
|
+
|
43
|
+
There are a lot of attributes you can fetch for a forecast day. Here are just a few:
|
44
|
+
|
45
|
+
<tt>temp</tt>, <tt>temperature</tt>, <tt>temp</tt>::
|
46
|
+
The temperature. For future days this is equivalent to the low for nighttime and high for daytime.
|
47
|
+
<tt>icon</tt>::
|
48
|
+
The number of the icon gif file from the weather.com SDK[http://www.weather.com/services/xmloap.html] that
|
49
|
+
identifies the conditions (e.g. a little icon of a cloud with rain, or sun, or whatever).
|
50
|
+
<tt>outlook</tt>, <tt>outlook_brief</tt>::
|
51
|
+
Brief text describing the conditions (e.g. "Mostly Cloudy", "Rain", "Scattered T-Storms"). An abbreviated
|
52
|
+
version is available in <tt>outlook_brief</tt> (e.g. "M Cloudy", "Scat T-Storms").
|
53
|
+
<tt>low</tt>, <tt>lo</tt>::
|
54
|
+
The forecasted low temperature. (Not available for current conditions)
|
55
|
+
<tt>high</tt>, <tt>hi</tt>::
|
56
|
+
The forecasted high temperature. (Not available for current conditions)
|
57
|
+
<tt>wind</tt>, <tt>wind.direction</tt>, <tt>wind.speed</tt>, <tt>wind.heading</tt>::
|
58
|
+
Wind conditions. The <tt>wind</tt> attribute returns an object with sub-attributes.
|
59
|
+
<tt>pop</tt>, <tt>ppcp</tt>::
|
60
|
+
Probability of precipitation.
|
61
|
+
<tt>date</tt>::
|
62
|
+
The date that this forecast is for, returned as a ruby Time object.
|
63
|
+
<tt>sunrise</tt>::
|
64
|
+
The time of sunrise on the day of the forecast.
|
65
|
+
<tt>sunset</tt>::
|
66
|
+
The time of sunset on the day of the forecast.
|
67
|
+
|
68
|
+
Additionally, all (or at least most) of the attributes for a given day in the raw weather.com xml data are
|
69
|
+
also available. For example, you can call forecast.tomorrow.dewp to get the dewpoint, because the xml file
|
70
|
+
contains a <tt>dewp</tt> element for that day. Have a look at #test/test_weather.xml to see what data is
|
71
|
+
available in the xml file. Note though that raw xml elements will be returned as a string, without any nice
|
72
|
+
class casting or unit conversion.
|
73
|
+
|
74
|
+
I encourage other programmers to add more functionality to the lib/forecast.rb module to provide better
|
75
|
+
accessor methods for the underlying xml data.
|
76
|
+
|
77
|
+
== Download
|
78
|
+
|
79
|
+
You can download the latest stable version of RubyWeather from http://rubyforge.org/projects/rubyweather/.
|
80
|
+
Alternatively, the files <em>may</em> be available from http://roughest.net/rubyweather (but no promises -- the
|
81
|
+
rubyforge site is a better bet).
|
82
|
+
|
83
|
+
You can also check out the latest copy via subversion from svn://rubyforge.org//var/svn/rubyweather/trunk. If you would
|
84
|
+
like to contribute back your changes to the code, please contact me via the rubyforge project site to obtain a subversion
|
85
|
+
account.
|
86
|
+
|
87
|
+
== Note
|
88
|
+
|
89
|
+
To use this library you will need a partner id and license key from
|
90
|
+
weather.com. The service is completely free, but requires that you agree
|
91
|
+
to weather.com's legal stuff, which, among other things, requires your software
|
92
|
+
to include a link back to the weather.com website (although this is not actually
|
93
|
+
enforced in any way by the service).
|
94
|
+
|
95
|
+
To obtain the free license visit http://www.weather.com/services/xmloap.html.
|
96
|
+
This will also allow you to download an SDK that includes nice weather icons for
|
97
|
+
use with the data.
|
98
|
+
|
99
|
+
== License
|
100
|
+
|
101
|
+
This program is free software; you can redistribute it and/or modify
|
102
|
+
it under the terms of the GNU General Public License as published by
|
103
|
+
the Free Software Foundation; either version 2 of the License, or
|
104
|
+
(at your option) any later version.
|
105
|
+
|
106
|
+
This program is distributed in the hope that it will be useful,
|
107
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
108
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
109
|
+
GNU General Public License for more details.
|
110
|
+
|
111
|
+
You should have received a copy of the GNU General Public License
|
112
|
+
along with this program (see the file called LICENSE); if not, write to the
|
113
|
+
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
data/lib/forecast.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Zukowski (http://blog.roughest.net)
|
3
|
+
# Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
4
|
+
# License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
5
|
+
#
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/util'
|
8
|
+
|
9
|
+
module Weather
|
10
|
+
|
11
|
+
# Namespace module for weather data objects.
|
12
|
+
module Forecast
|
13
|
+
|
14
|
+
# Contains several days of weather data.
|
15
|
+
# The forecast object includes the Enumerable mixin, so that you can iterate
|
16
|
+
# over all of the days in the forecast using the standard ruby mechanisms
|
17
|
+
# (i.e. <tt>myforecast.each { |d| print d.outlook }</tt>).
|
18
|
+
class Forecast
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_reader :xml
|
22
|
+
|
23
|
+
# Instantiate a Forecast object from the specified Weather.com REXML::Document.
|
24
|
+
def initialize(weather_xmldoc)
|
25
|
+
if (not weather_xmldoc.kind_of? REXML::Document)
|
26
|
+
raise "The xml document given to the Forecast constructor must be a valid REXML::Document"
|
27
|
+
end
|
28
|
+
|
29
|
+
@xml = weather_xmldoc
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the current conditions as a Conditions object.
|
33
|
+
def current
|
34
|
+
CurrentConditions.new(xml.root.elements['cc'])
|
35
|
+
end
|
36
|
+
|
37
|
+
# Alias for day(1)
|
38
|
+
def tomorrow
|
39
|
+
day(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the conditions for the given day (0 = today, 1 = tomorrow, etc.)
|
43
|
+
# The maximum day number depends on the data available in the xml that was used to create this Forecast.
|
44
|
+
def day(num)
|
45
|
+
element = xml.root.elements['dayf'].elements["day[@d='#{num.to_s}']"]
|
46
|
+
if not element
|
47
|
+
case num
|
48
|
+
when 0 then daydisplay = "today"
|
49
|
+
when 1 then daydisplay = "tomorrow"
|
50
|
+
else daydisplay = "#{num} days from now"
|
51
|
+
end
|
52
|
+
raise "Sorry, there is no data available for #{daydisplay}"
|
53
|
+
else
|
54
|
+
Day.new(element)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the conditions for the given night (0 = tonight, 1 = tomorrow night, etc.)
|
59
|
+
# The maximum day number depends on the data available in the xml that was used to create this Forecast.
|
60
|
+
def night(num)
|
61
|
+
element = xml.root.elements['dayf'].elements["day[@d='#{num.to_s}']"]
|
62
|
+
Night.new(element)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Iterates over all of the days in this Forecast.
|
66
|
+
def each(&block)
|
67
|
+
first = true
|
68
|
+
REXML::XPath.match(xml, "//dayf/day").each do |dxml|
|
69
|
+
d = Day.new(dxml)
|
70
|
+
|
71
|
+
# if it is after 3 PM, use current conditions
|
72
|
+
if Time.now > Time.local(d.date.year, d.date.month, d.date.day, 15)
|
73
|
+
d = current
|
74
|
+
end
|
75
|
+
|
76
|
+
yield d
|
77
|
+
|
78
|
+
first = false if first
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the full human-readable name of the place that this Forecast is for.
|
83
|
+
def location_name
|
84
|
+
xml.root.elements['loc'].elements['dnam'].text
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the name of the city that this Forecast is for.
|
88
|
+
def location_city
|
89
|
+
xml.root.elements['loc'].elements['dnam'].text.split(",").first
|
90
|
+
end
|
91
|
+
|
92
|
+
# The location code of the weather station that this Forecast is for.
|
93
|
+
def location_code
|
94
|
+
xml.root.elements['loc'].attributes['id']
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns true if the units returned by this Forecast will be in the metric system (i.e. Celcius).
|
98
|
+
def metric?
|
99
|
+
xml.root.elements['head'].elements['ut'].text == "C"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Abstract class that all Forecast entities (roughly "days") are based on.
|
104
|
+
class Conditions
|
105
|
+
|
106
|
+
# For elements in the forecast that we have not defined an explicit accessor,
|
107
|
+
# this allows accessing the raw underlying data in the forecast xml.
|
108
|
+
def method_missing(symbol)
|
109
|
+
begin
|
110
|
+
return @xml.elements[symbol.to_s].text
|
111
|
+
rescue NoMethodError
|
112
|
+
return "N/A"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the wind conditions as an anonymous object (i.e. wind.d for wind
|
117
|
+
# direction, wind.s for wind speed, etc.) See the <wind> element in the
|
118
|
+
# weather.com XML data spec for more info.
|
119
|
+
def wind
|
120
|
+
fix_wind(complex_attribute(@xml.elements['wind']))
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
# Returns the element specified by name as a cleaned-up temperature value.
|
125
|
+
# That is, if the temperature is "N/A", then nil is returned; otherwise
|
126
|
+
# the value is converted to an integer.
|
127
|
+
def clean_temp(name)
|
128
|
+
temp = @xml.elements[name].text
|
129
|
+
|
130
|
+
if (temp == "N/A")
|
131
|
+
return nil
|
132
|
+
else
|
133
|
+
return temp.to_i
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return the given xml element as an anonymous object, with the text nodes of the element's
|
138
|
+
# immediate children available as accessor methods.
|
139
|
+
# This allows for accessing attributes that have child elements (i.e. wind, bar, etc.)
|
140
|
+
# as anonymous objects (i.e. wind.d for wind direction, wind.s for wind speed, etc.)
|
141
|
+
def complex_attribute(objxml)
|
142
|
+
obj = {}
|
143
|
+
class << obj
|
144
|
+
def method_missing(name)
|
145
|
+
return self[name.to_s]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
objxml.elements.each do |element|
|
150
|
+
obj[element.name] = element.text
|
151
|
+
end
|
152
|
+
|
153
|
+
return obj
|
154
|
+
end
|
155
|
+
|
156
|
+
def fix_wind(obj)
|
157
|
+
obj['heading'] = obj.t
|
158
|
+
obj['direction'] = obj.d.to_i
|
159
|
+
obj['speed'] = obj.s.to_i
|
160
|
+
|
161
|
+
return obj
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Represents the current weather conditions.
|
166
|
+
class CurrentConditions < Conditions
|
167
|
+
attr_reader :xml
|
168
|
+
|
169
|
+
@xml
|
170
|
+
|
171
|
+
def initialize(element)
|
172
|
+
if (not element.kind_of? REXML::Element)
|
173
|
+
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
174
|
+
end
|
175
|
+
@xml = element
|
176
|
+
end
|
177
|
+
|
178
|
+
def icon
|
179
|
+
xml.elements['icon'].text.to_i
|
180
|
+
end
|
181
|
+
|
182
|
+
def temp
|
183
|
+
clean_temp('tmp')
|
184
|
+
end
|
185
|
+
alias_method :tmp, :temp
|
186
|
+
alias_method :temperature, :temp
|
187
|
+
|
188
|
+
def outlook
|
189
|
+
xml.elements['t'].text
|
190
|
+
end
|
191
|
+
|
192
|
+
def outlook_brief
|
193
|
+
xml.elements['bt'].text
|
194
|
+
end
|
195
|
+
|
196
|
+
def pop
|
197
|
+
nil
|
198
|
+
end
|
199
|
+
alias_method :ppcp, :pop
|
200
|
+
|
201
|
+
def date
|
202
|
+
Time.now
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Abstract class representing either a day or night in the daily forecast (note that "future" can include today).
|
207
|
+
class FutureConditions < Conditions
|
208
|
+
attr_reader :xml
|
209
|
+
|
210
|
+
@xml
|
211
|
+
|
212
|
+
def initialize(element)
|
213
|
+
if (not element.kind_of? REXML::Element)
|
214
|
+
raise "The xml element given to the Day/Night constructor must be a valid REXML::Element"
|
215
|
+
end
|
216
|
+
@xml = element
|
217
|
+
end
|
218
|
+
|
219
|
+
def method_missing(name)
|
220
|
+
begin
|
221
|
+
return mypart.elements[name.to_s].text
|
222
|
+
rescue NoMethodError
|
223
|
+
return "N/A"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def wind
|
228
|
+
fix_wind(complex_attribute(mypart.elements['wind']))
|
229
|
+
end
|
230
|
+
|
231
|
+
def date
|
232
|
+
# FIXME: this will break if rolling over to next year (i.e. fetched 5 days into the future on Dec 30), since today's year is assumed
|
233
|
+
mon, day = @xml.attributes['dt'].split(" ")
|
234
|
+
Time.local(Time.now.year, mon, day)
|
235
|
+
end
|
236
|
+
|
237
|
+
def icon
|
238
|
+
mypart.elements['icon'].text.to_i
|
239
|
+
end
|
240
|
+
|
241
|
+
def high
|
242
|
+
clean_temp('hi')
|
243
|
+
end
|
244
|
+
alias_method :hi, :high
|
245
|
+
|
246
|
+
def low
|
247
|
+
clean_temp('low')
|
248
|
+
end
|
249
|
+
alias_method :lo, :low
|
250
|
+
|
251
|
+
|
252
|
+
def outlook
|
253
|
+
mypart.elements['t'].text
|
254
|
+
end
|
255
|
+
|
256
|
+
def outlook_brief
|
257
|
+
mypart.elements['bt'].text
|
258
|
+
end
|
259
|
+
|
260
|
+
def pop
|
261
|
+
mypart.elements['ppcp'].text.to_i
|
262
|
+
end
|
263
|
+
alias_method :ppcp, :pop
|
264
|
+
|
265
|
+
def sunrise
|
266
|
+
hour,minute = @xml.elements['sunr'].text.split(" ").first.split(":")
|
267
|
+
hour = hour.to_i
|
268
|
+
minute = minute.to_i
|
269
|
+
Time.local(date.year, date.month, date.day, hour, minute)
|
270
|
+
end
|
271
|
+
|
272
|
+
def sunset
|
273
|
+
hour,minute = @xml.elements['suns'].text.split(" ").first.split(":")
|
274
|
+
hour = hour.to_i + 12 # add 12 since we need 24 hour clock and sunset is always (?) in the PM
|
275
|
+
minute = minute.to_i
|
276
|
+
Time.local(date.year, date.month, date.day, hour, minute)
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
# The daytime part of the forecast for a given day.
|
282
|
+
class Day < FutureConditions
|
283
|
+
def initialize(element)
|
284
|
+
super(element)
|
285
|
+
end
|
286
|
+
|
287
|
+
def temp
|
288
|
+
high
|
289
|
+
end
|
290
|
+
alias_method :tmp, :temp
|
291
|
+
alias_method :temperature, :temp
|
292
|
+
|
293
|
+
private
|
294
|
+
def mypart
|
295
|
+
@xml.elements['part[@p="d"]']
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# The nighttime part of a forecast for a given day.
|
300
|
+
class Night < FutureConditions
|
301
|
+
def initialize(element)
|
302
|
+
super(element)
|
303
|
+
end
|
304
|
+
|
305
|
+
def temp
|
306
|
+
low
|
307
|
+
end
|
308
|
+
alias_method :tmp, :temp
|
309
|
+
alias_method :temperature, :temp
|
310
|
+
|
311
|
+
private
|
312
|
+
def mypart
|
313
|
+
@xml.elements['part[@p="n"]']
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
data/lib/service.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Zukowski (http://blog.roughest.net)
|
3
|
+
# Copyright:: Copyright (c) 2006 Urbacon Ltd.
|
4
|
+
# License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'net/http'
|
8
|
+
require 'rexml/document'
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/forecast'
|
11
|
+
|
12
|
+
module Weather
|
13
|
+
|
14
|
+
# Interface for interacting with the weather.com service.
|
15
|
+
class Service
|
16
|
+
attr_writer :partner_id, :license_key, :imperial
|
17
|
+
attr_reader :partner_id, :license_key, :imperial
|
18
|
+
|
19
|
+
# Returns the forecast data fetched from the weather.com xoap service for the given location and number of days.
|
20
|
+
def fetch_forecast(location_id, days = 5)
|
21
|
+
|
22
|
+
# try to pull the partner_id and license_key from the environment if not already set
|
23
|
+
partner_id = ENV['WEATHER_COM_PARTNER_ID'] unless partner_id
|
24
|
+
license_key = ENV['WEATHER_COM_LICENSE_KEY'] unless license_key
|
25
|
+
|
26
|
+
if imperial or (ENV.has_key? 'USE_IMPERIAL_UNITS' and ENV['USE_IMPERIAL_UNITS'])
|
27
|
+
imperial = true
|
28
|
+
else
|
29
|
+
imperial = false
|
30
|
+
end
|
31
|
+
|
32
|
+
# NOTE: Strangely enough, weather.com doesn't seem to be enforcing the partner_id/license_key stuff. You can specify blank values for both
|
33
|
+
# and the service will return the data just fine (actually, it will accept any value as valid). I'm commenting out these checks
|
34
|
+
# for now, but we may need to re-enable these once weather.com figures out what's going on.
|
35
|
+
#if not partner_id
|
36
|
+
# puts "WARNING: No partner ID has been set. Please obtain a partner ID from weather.com before attempting to fetch a forecast, otherwise the data you requested may not be available."
|
37
|
+
#end
|
38
|
+
#
|
39
|
+
#if not license_key
|
40
|
+
# puts "WARNING: No license key has been set. Please obtain a license key from weather.com before attempting to fetch a forecast, otherwise the data you requested may not be available"
|
41
|
+
#end
|
42
|
+
|
43
|
+
# default to metric (degrees fahrenheit are just silly :)
|
44
|
+
unit = imperial ? "s" : "m"
|
45
|
+
|
46
|
+
host = "xoap.weather.com"
|
47
|
+
url = "/weather/local/#{location_id}?cc=*&dayf=#{days}&prod=xoap&par=#{partner_id}&key=#{license_key}&unit=#{unit}"
|
48
|
+
|
49
|
+
# puts "Using url: "+url
|
50
|
+
|
51
|
+
xml = Net::HTTP.get(host, url);
|
52
|
+
doc = REXML::Document.new(xml)
|
53
|
+
|
54
|
+
Forecast::Forecast.new(doc)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the forecast data loaded from a file. This is useful for testing.
|
58
|
+
def load_forecast(filename)
|
59
|
+
file = File.new(filename)
|
60
|
+
doc = REXML::Document.new(file)
|
61
|
+
|
62
|
+
Forecast::Forecast.new(doc)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a hash containing location_code => location_name key-value pairs for the given location search string.
|
66
|
+
# In other words, you can use this to find a location code based on a city name, ZIP code, etc.
|
67
|
+
def find_location(search_string)
|
68
|
+
host = "xoap.weather.com"
|
69
|
+
# FIXME: need to do url encoding of the search string!
|
70
|
+
url = "/weather/search/search?where=#{search_string}"
|
71
|
+
|
72
|
+
xml = Net::HTTP.get(host, url);
|
73
|
+
doc = REXML::Document.new(xml)
|
74
|
+
|
75
|
+
locations = {}
|
76
|
+
|
77
|
+
REXML::XPath.match(doc.root, "//loc").each do |loc|
|
78
|
+
locations[loc.attributes['id']] = loc.text
|
79
|
+
end
|
80
|
+
|
81
|
+
return locations
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|