barometer 0.6.7 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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>