barometer 0.6.7 → 0.7.0

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.
Files changed (50) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +1 -1
  4. data/README.rdoc +36 -8
  5. data/Rakefile +7 -28
  6. data/TODO +4 -0
  7. data/VERSION.yml +2 -2
  8. data/barometer.gemspec +20 -193
  9. data/bin/barometer +8 -5
  10. data/lib/barometer.rb +5 -1
  11. data/lib/barometer/data/local_datetime.rb +11 -7
  12. data/lib/barometer/formats.rb +2 -1
  13. data/lib/barometer/formats/coordinates.rb +15 -3
  14. data/lib/barometer/formats/format.rb +18 -0
  15. data/lib/barometer/formats/geocode.rb +12 -5
  16. data/lib/barometer/formats/weather_id.rb +0 -15
  17. data/lib/barometer/formats/woe_id.rb +150 -0
  18. data/lib/barometer/query.rb +20 -4
  19. data/lib/barometer/services.rb +2 -1
  20. data/lib/barometer/weather_services/service.rb +0 -4
  21. data/lib/barometer/weather_services/weather_bug.rb +2 -2
  22. data/lib/barometer/weather_services/yahoo.rb +14 -3
  23. data/lib/barometer/web_services/placemaker.rb +95 -0
  24. data/lib/barometer/web_services/web_service.rb +1 -4
  25. data/spec/barometer_spec.rb +14 -32
  26. data/spec/data/local_datetime_spec.rb +7 -2
  27. data/spec/data/zone_spec.rb +5 -2
  28. data/spec/fakeweb_helper.rb +158 -0
  29. data/spec/fixtures/services/placemaker/T5B4M9.xml +65 -0
  30. data/spec/fixtures/services/placemaker/atlanta.xml +65 -0
  31. data/spec/fixtures/services/placemaker/coords.xml +65 -0
  32. data/spec/fixtures/services/placemaker/ksfo.xml +65 -0
  33. data/spec/fixtures/services/placemaker/new_york.xml +65 -0
  34. data/spec/fixtures/services/placemaker/the_hills.xml +65 -0
  35. data/spec/fixtures/services/placemaker/w615702.xml +46 -0
  36. data/spec/fixtures/services/weather_bug/90210_forecast.xml +1 -1
  37. data/spec/formats/coordinates_spec.rb +16 -0
  38. data/spec/formats/format_spec.rb +9 -0
  39. data/spec/formats/woe_id_spec.rb +215 -0
  40. data/spec/query_spec.rb +38 -0
  41. data/spec/spec_helper.rb +7 -160
  42. data/spec/weather_services/google_spec.rb +0 -8
  43. data/spec/weather_services/services_spec.rb +1 -1
  44. data/spec/weather_services/weather_bug_spec.rb +0 -36
  45. data/spec/weather_services/weather_dot_com_spec.rb +0 -20
  46. data/spec/weather_services/wunderground_spec.rb +0 -30
  47. data/spec/weather_services/yahoo_spec.rb +1 -17
  48. data/spec/web_services/placemaker_spec.rb +46 -0
  49. metadata +136 -18
  50. data/lib/barometer/extensions/httparty.rb +0 -21
@@ -27,6 +27,7 @@ module Barometer
27
27
  # where query can be:
28
28
  # - zipcode (US)
29
29
  # - Yahoo! Location ID [actually weather.com id] (International)
30
+ # - Yahoo! 'Where on Earth ID [WOEID] (International)
30
31
  #
31
32
  # = Yahoo! terms of use
32
33
  # The feeds are provided free of charge for use by individuals and non-profit
@@ -42,6 +43,11 @@ module Barometer
42
43
  #
43
44
  # == notes
44
45
  # - the Yahoo! Location ID is a propreitary number (shared with weather.com)
46
+ # - the Yahoo! WOEID is only used by Yahoo!, and is a 32-bit number. Unfortunately
47
+ # this number confilcts with US Zipcodes (ie the zipcode=90210 and the
48
+ # WOEID=90210 cannot be destinguished and do not mean the same thing). To
49
+ # solve this, any 5 digit number will be dtected as a ZIPCODE. To have a
50
+ # 5 digit query be detected as a WOEID, prepend it with a 'w' (ie: w90210).
45
51
  #
46
52
  class WeatherService::Yahoo < WeatherService
47
53
 
@@ -51,8 +57,8 @@ module Barometer
51
57
  #
52
58
 
53
59
  def self._source_name; :yahoo; end
54
- def self._accepted_formats; [:zipcode, :weather_id]; end
55
-
60
+ def self._accepted_formats; [:zipcode, :weather_id, :woe_id]; end
61
+
56
62
  def self._wet_icon_codes
57
63
  codes = [1] + (3..18).to_a + [35] + (37..43).to_a + (45..47).to_a
58
64
  codes.collect {|c| c.to_s}
@@ -185,9 +191,14 @@ module Barometer
185
191
  def self._fetch(query, metric=true)
186
192
  return unless query
187
193
  puts "fetch yahoo: #{query.q}" if Barometer::debug?
194
+ options = {
195
+ :p => query.format == :woe_id ? nil : query.q,
196
+ :w => query.format == :woe_id ? query.q : nil,
197
+ :u => (metric ? 'c' : 'f')
198
+ }.delete_if {|k,v| v.nil? }
188
199
  self.get(
189
200
  "http://weather.yahooapis.com/forecastrss",
190
- :query => {:p => query.q, :u => (metric ? 'c' : 'f')},
201
+ :query => options,
191
202
  :format => :xml,
192
203
  :timeout => Barometer.timeout
193
204
  )['rss']['channel']
@@ -0,0 +1,95 @@
1
+ module Barometer
2
+ #
3
+ # Web Service: Placemaker (by Yahoo!)
4
+ #
5
+ # Yahoo! Placemaker is a geoparsing web service,
6
+ # this uses Placemaker to obtain a WOEID, as only Yahoo knows them
7
+ #
8
+ class WebService::Placemaker < WebService
9
+
10
+ # get the woe_id for a given query
11
+ #
12
+ def self.fetch(query)
13
+
14
+ begin
15
+ require 'nokogiri'
16
+ rescue LoadError
17
+ puts "\n****\nTo use this functionality you will need to install Nokogiri >= 1.3.3\n****\n\n"
18
+ return nil
19
+ end
20
+
21
+ puts "fetch woe_id: #{query.q}" if Barometer::debug?
22
+ return nil unless query
23
+ return nil unless _has_geocode_key?
24
+ raise ArgumentError unless _is_a_query?(query)
25
+
26
+ # BUG: httparty doesn't seem to post correctly
27
+ # self.post(
28
+ # 'http://wherein.yahooapis.com/v1/document',
29
+ # :query => {
30
+ # :documentType => "html",
31
+ # :outputType => 'xml',
32
+ # :documentContent => _adjust_query(query),
33
+ # :appid => Barometer.yahoo_placemaker_app_id
34
+ # }, :format => :xml, :timeout => Barometer.timeout
35
+ # )
36
+
37
+ # TODO: include timeout
38
+ # :timeout => Barometer.timeout
39
+ #
40
+ res = Net::HTTP.post_form(
41
+ URI.parse("http://wherein.yahooapis.com/v1/document"),
42
+ {
43
+ 'documentType' => 'text/html',
44
+ 'outputType' => 'xml',
45
+ 'documentContent' => _adjust_query(query),
46
+ 'appid' => Barometer.yahoo_placemaker_app_id
47
+ }
48
+ )
49
+
50
+ Nokogiri::HTML.parse(res.body)
51
+ end
52
+
53
+ # get the location_data (geocode) for a given woe_id
54
+ #
55
+ def self.reverse(query)
56
+ puts "reverse woe_id: #{query.q}" if Barometer::debug?
57
+ return nil unless query
58
+ raise ArgumentError unless _is_a_query?(query)
59
+ self.get(
60
+ "http://weather.yahooapis.com/forecastrss",
61
+ :query => { :w => query.q },
62
+ :format => :xml,
63
+ :timeout => Barometer.timeout
64
+ )['rss']['channel']["yweather:location"]
65
+ end
66
+
67
+ # parses a Nokogori doc object
68
+ #
69
+ def self.parse_woe_id(doc)
70
+ doc.search('woeid').first.content
71
+ end
72
+
73
+ private
74
+
75
+ # convert coordinates to a microformat version of coordinates
76
+ # so that Placemaker uses them correctly
77
+ #
78
+ def self._adjust_query(query)
79
+ output = query.q
80
+ if query.format == :coordinates
81
+ microformat = "<html><body><div class=\"geo\"><span class=\"latitude\">%s</span><span class=\"longitude\">%s</span></div></body></html>"
82
+ output = sprintf(microformat, query.latitude, query.longitude)
83
+ end
84
+ puts "placemaker adjusted query: #{output}" if Barometer::debug?
85
+ output
86
+ end
87
+
88
+ def self._has_geocode_key?
89
+ !Barometer.yahoo_placemaker_app_id.nil?
90
+ end
91
+
92
+ end
93
+ end
94
+
95
+
@@ -1,9 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'httparty'
3
3
 
4
- $:.unshift(File.dirname(__FILE__))
5
- require 'extensions/httparty'
6
-
7
4
  module Barometer
8
5
  #
9
6
  # Web Service Class
@@ -20,7 +17,7 @@ module Barometer
20
17
 
21
18
  # STUB: define this method to actually retireve the data
22
19
  def self.fetch(query=nil); raise NotImplementedError; end
23
-
20
+
24
21
  private
25
22
 
26
23
  def self._is_a_query?(object=nil)
@@ -4,7 +4,8 @@ describe "Barometer" do
4
4
 
5
5
  before(:each) do
6
6
  @config_hash = { 1 => [:wunderground] }
7
- @key = KEY
7
+ @google_key = KEY
8
+ @yahoo_key = YAHOO_KEY
8
9
  end
9
10
 
10
11
  describe "and class methods" do
@@ -23,9 +24,18 @@ describe "Barometer" do
23
24
 
24
25
  it "sets the Graticule Google geocoding API key" do
25
26
  Barometer.respond_to?("google_geocode_key").should be_true
27
+ Barometer.google_geocode_key = nil
26
28
  Barometer.google_geocode_key.should be_nil
27
- Barometer.google_geocode_key = @key
28
- Barometer.google_geocode_key.should == @key
29
+ Barometer.google_geocode_key = @google_key
30
+ Barometer.google_geocode_key.should == @google_key
31
+ end
32
+
33
+ it "sets the Placemaker Yahoo! app ID" do
34
+ Barometer.respond_to?("yahoo_placemaker_app_id").should be_true
35
+ Barometer.yahoo_placemaker_app_id = nil
36
+ Barometer.yahoo_placemaker_app_id.should be_nil
37
+ Barometer.yahoo_placemaker_app_id = @yahoo_key
38
+ Barometer.yahoo_placemaker_app_id.should == @yahoo_key
29
39
  end
30
40
 
31
41
  it "forces the geocoding of queries" do
@@ -93,38 +103,10 @@ describe "Barometer" do
93
103
  describe "when measuring" do
94
104
 
95
105
  before(:each) do
96
- Barometer.google_geocode_key = @key
106
+ Barometer.google_geocode_key = @google_key
97
107
  query_term = "Calgary,AB"
98
108
  @barometer = Barometer::Base.new(query_term)
99
109
  @time = Time.now
100
- FakeWeb.register_uri(:get,
101
- "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=#{CGI.escape(query_term)}",
102
- :body => File.read(File.join(File.dirname(__FILE__),
103
- 'fixtures/services/wunderground',
104
- 'current_calgary_ab.xml')
105
- )
106
- )
107
- FakeWeb.register_uri(:get,
108
- "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=#{CGI.escape(query_term)}",
109
- :body => File.read(File.join(File.dirname(__FILE__),
110
- 'fixtures/services/wunderground',
111
- 'forecast_calgary_ab.xml')
112
- )
113
- )
114
- # FakeWeb.register_uri(:get,
115
- # "http://maps.google.com/maps/geo?gl=&key=#{@key}&q=Calgary%2CAB&sensor=false&output=xml",
116
- # :body => File.read(File.join(File.dirname(__FILE__),
117
- # 'fixtures/geocode',
118
- # 'calgary_ab.xml')
119
- # )
120
- # )
121
- FakeWeb.register_uri(:get,
122
- "http://maps.google.com/maps/geo?gl=&key=#{@key}&q=Calgary%2CAB&sensor=false&output=json",
123
- :body => File.read(File.join(File.dirname(__FILE__),
124
- 'fixtures/geocode',
125
- 'calgary_ab.json')
126
- )
127
- )
128
110
  end
129
111
 
130
112
  it "responds to measure" do
@@ -144,7 +144,12 @@ describe "Data::LocalDateTime" do
144
144
  datetime.min.should == @m
145
145
  datetime.sec.should == @s
146
146
  end
147
-
147
+
148
+ it "returns nil when string contains no date/time info" do
149
+ string = "Last Updated on , "
150
+ datetime = Data::LocalDateTime.parse(string)
151
+ datetime.should be_nil
152
+ end
148
153
  end
149
154
 
150
155
  describe "storing" do
@@ -266,4 +271,4 @@ describe "Data::LocalDateTime" do
266
271
 
267
272
  end
268
273
 
269
- end
274
+ end
@@ -65,7 +65,10 @@ describe "Data::Zone" do
65
65
  zone.code.should be_nil
66
66
 
67
67
  zone = Data::Zone.new(@timezone)
68
- zone.code.should == "CET"
68
+ # the expected result of this depends on the time of year
69
+ # when summer expect "CEST", otherwise "CET"
70
+ # just let TZINFO handle this
71
+ zone.code.should == TZInfo::Timezone.get(@timezone).period_for_utc(Time.now.utc).zone_identifier.to_s
69
72
  end
70
73
 
71
74
  it "responds to dst?" do
@@ -360,4 +363,4 @@ describe "Data::Zone" do
360
363
 
361
364
  end
362
365
 
363
- end
366
+ end
@@ -0,0 +1,158 @@
1
+ require 'fakeweb'
2
+ FakeWeb.allow_net_connect = false
3
+
4
+ #
5
+ # Set random test keys
6
+ #
7
+ KEY = "ABC123"
8
+ WEATHER_PARTNER_KEY = "1234"
9
+ WEATHER_LICENSE_KEY = "12345"
10
+ WEATHERBUG_CODE = "A9999"
11
+ YAHOO_KEY = "YAHOO"
12
+
13
+ #
14
+ # for geocoding
15
+ #
16
+ geo_url = "http://maps.google.com/maps/geo?"
17
+ FakeWeb.register_uri(:get,
18
+ "#{geo_url}gl=US&key=#{KEY}&sensor=false&q=90210&output=json",
19
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/90210.json')
20
+ )
21
+
22
+ FakeWeb.register_uri(:get,
23
+ "#{geo_url}gl=&q=#{CGI.escape("40.756054,-73.986951")}&output=json&key=#{KEY}&sensor=false",
24
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/40_73.json')
25
+ )
26
+
27
+ FakeWeb.register_uri(:get,
28
+ "#{geo_url}gl=&q=New%20York%2C%20NY&output=json&key=#{KEY}&sensor=false",
29
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/newyork_ny.json')
30
+ )
31
+ FakeWeb.register_uri(:get,
32
+ "#{geo_url}gl=CA&key=#{KEY}&output=json&q=T5B%204M9&sensor=false",
33
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/T5B4M9.json')
34
+ )
35
+ FakeWeb.register_uri(:get,
36
+ "#{geo_url}gl=US&q=KSFO&output=json&key=#{KEY}&sensor=false",
37
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/ksfo.json')
38
+ )
39
+ FakeWeb.register_uri(:get,
40
+ "#{geo_url}gl=&q=Atlanta%2C%20GA%2C%20US&output=json&key=#{KEY}&sensor=false",
41
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/atlanta.json')
42
+ )
43
+ FakeWeb.register_uri(:get,
44
+ "#{geo_url}output=xml&q=Atlanta%2C%20GA%2C%20US&gl=US&key=#{KEY}",
45
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/atlanta.xml')
46
+ )
47
+ FakeWeb.register_uri(:get,
48
+ "#{geo_url}gl=&key=#{KEY}&q=Calgary%2CAB&sensor=false&output=json",
49
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/geocode/calgary_ab.json')
50
+ )
51
+ #
52
+ # for weather.com searches
53
+ #
54
+ FakeWeb.register_uri(:get,
55
+ "http://xoap.weather.com:80/search/search?where=Beverly%20Hills%2C%20CA%2C%20USA",
56
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/90210.xml')
57
+ )
58
+ FakeWeb.register_uri(:get,
59
+ "http://xoap.weather.com:80/search/search?where=New%20York%2C%20NY",
60
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/new_york.xml')
61
+ )
62
+ FakeWeb.register_uri(:get,
63
+ "http://xoap.weather.com:80/search/search?where=New%20York%2C%20NY%2C%20USA",
64
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/new_york.xml')
65
+ )
66
+ FakeWeb.register_uri(:get,
67
+ "http://xoap.weather.com:80/search/search?where=90210",
68
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/90210.xml')
69
+ )
70
+ FakeWeb.register_uri(:get,
71
+ "http://xoap.weather.com:80/search/search?where=Millbrae%2C%20CA%2C%20USA",
72
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/ksfo.xml')
73
+ )
74
+ #
75
+ # for yahoo.com searches
76
+ #
77
+ FakeWeb.register_uri(:get,
78
+ "http://weather.yahooapis.com:80/forecastrss?p=USGA0028",
79
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/formats/weather_id/from_USGA0028.xml')
80
+ )
81
+ #
82
+ # for Google weather
83
+ #
84
+ FakeWeb.register_uri(:get,
85
+ "http://google.com/ig/api?weather=#{CGI.escape('Calgary,AB')}&hl=en-GB",
86
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/google/calgary_ab.xml')
87
+ )
88
+ #
89
+ # for WeatherBug weather
90
+ #
91
+ bug_url_current = "http://#{WEATHERBUG_CODE}.api.wxbug.net:80/getLiveWeatherRSS.aspx?"
92
+ FakeWeb.register_uri(:get,
93
+ "#{bug_url_current}ACode=#{WEATHERBUG_CODE}&OutputType=1&UnitType=1&zipCode=90210",
94
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/weather_bug/90210_current.xml')
95
+ )
96
+
97
+ bug_url_future = "http://#{WEATHERBUG_CODE}.api.wxbug.net:80/getForecastRSS.aspx?"
98
+ FakeWeb.register_uri(:get,
99
+ "#{bug_url_future}ACode=#{WEATHERBUG_CODE}&OutputType=1&UnitType=1&zipCode=90210",
100
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/weather_bug/90210_forecast.xml')
101
+ )
102
+ #
103
+ # for weather.com weather
104
+ #
105
+ weather_com_url = "http://xoap.weather.com:80/weather/local/"
106
+ FakeWeb.register_uri(:get,
107
+ "#{weather_com_url}90210?dayf=5&unit=m&link=xoap&par=#{WEATHER_PARTNER_KEY}&prod=xoap&key=#{WEATHER_LICENSE_KEY}&cc=*",
108
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/weather_dot_com/90210.xml')
109
+ )
110
+ #
111
+ # for yahoo weather
112
+ #
113
+ FakeWeb.register_uri(:get,
114
+ "http://weather.yahooapis.com:80/forecastrss?u=c&p=90210",
115
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/yahoo/90210.xml')
116
+ )
117
+
118
+ #
119
+ # For wunderground weather
120
+ #
121
+ FakeWeb.register_uri(:get,
122
+ "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=51.055149%2C-114.062438",
123
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/wunderground/current_calgary_ab.xml')
124
+ )
125
+ FakeWeb.register_uri(:get,
126
+ "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=51.055149%2C-114.062438",
127
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/wunderground/forecast_calgary_ab.xml')
128
+ )
129
+ FakeWeb.register_uri(:get,
130
+ "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=#{CGI.escape('Calgary,AB')}",
131
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/wunderground/current_calgary_ab.xml')
132
+ )
133
+ FakeWeb.register_uri(:get,
134
+ "http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query=#{CGI.escape('Calgary,AB')}",
135
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/wunderground/forecast_calgary_ab.xml')
136
+ )
137
+
138
+ #
139
+ # For Placemaker
140
+ #
141
+ FakeWeb.register_uri(:post,
142
+ "http://wherein.yahooapis.com/v1/document",
143
+ [
144
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/the_hills.xml')},
145
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/the_hills.xml')},
146
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/the_hills.xml')},
147
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/T5B4M9.xml')},
148
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/coords.xml')},
149
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/new_york.xml')},
150
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/atlanta.xml')},
151
+ {:body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/ksfo.xml')}
152
+ ]
153
+ )
154
+
155
+ FakeWeb.register_uri(:get,
156
+ "http://weather.yahooapis.com/forecastrss?w=615702",
157
+ :body => File.read(File.dirname(__FILE__) + '/fixtures/services/placemaker/w615702.xml')
158
+ )
@@ -0,0 +1,65 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <contentlocation xmlns:yahoo="http://www.yahooapis.com/v1/base.rng" xmlns="http://wherein.yahooapis.com/v1/schema" xml:lang="en">
3
+ <processingTime>0.000986</processingTime>
4
+ <version> build 091119</version>
5
+ <documentLength>7</documentLength>
6
+ <document>
7
+ <administrativeScope>
8
+ <woeId>8676</woeId>
9
+ <type>Town</type>
10
+ <name><![CDATA[Edmonton, Alberta, CA]]></name>
11
+ <centroid>
12
+ <latitude>53.5462</latitude>
13
+ <longitude>-113.49</longitude>
14
+ </centroid>
15
+ </administrativeScope>
16
+ <geographicScope>
17
+ <woeId>24354344</woeId>
18
+ <type>Zip</type>
19
+ <name><![CDATA[T5B 4M9, Edmonton, Alberta, CA]]></name>
20
+ <centroid>
21
+ <latitude>53.5705</latitude>
22
+ <longitude>-113.458</longitude>
23
+ </centroid>
24
+ </geographicScope>
25
+ <extents>
26
+ <center>
27
+ <latitude>53.5705</latitude>
28
+ <longitude>-113.458</longitude>
29
+ </center>
30
+ <southWest>
31
+ <latitude>53.5695</latitude>
32
+ <longitude>-113.46</longitude>
33
+ </southWest>
34
+ <northEast>
35
+ <latitude>53.5715</latitude>
36
+ <longitude>-113.456</longitude>
37
+ </northEast>
38
+ </extents>
39
+ <placeDetails>
40
+ <place>
41
+ <woeId>24354344</woeId>
42
+ <type>Zip</type>
43
+ <name><![CDATA[T5B 4M9, Edmonton, Alberta, CA]]></name>
44
+ <centroid>
45
+ <latitude>53.5705</latitude>
46
+ <longitude>-113.458</longitude>
47
+ </centroid>
48
+ </place>
49
+ <matchType>0</matchType>
50
+ <weight>1</weight>
51
+ <confidence>7</confidence>
52
+ </placeDetails>
53
+ <referenceList>
54
+ <reference>
55
+ <woeIds>24354344</woeIds>
56
+ <start>0</start>
57
+ <end>7</end>
58
+ <isPlaintextMarker>1</isPlaintextMarker>
59
+ <text><![CDATA[T5B 4M9]]></text>
60
+ <type>plaintext</type>
61
+ <xpath><![CDATA[]]></xpath>
62
+ </reference>
63
+ </referenceList>
64
+ </document>
65
+ </contentlocation>