barometer 0.1.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.
- data/LICENSE +20 -0
- data/README.rdoc +266 -0
- data/VERSION.yml +4 -0
- data/bin/barometer +63 -0
- data/lib/barometer.rb +52 -0
- data/lib/barometer/base.rb +52 -0
- data/lib/barometer/data.rb +15 -0
- data/lib/barometer/data/current.rb +93 -0
- data/lib/barometer/data/distance.rb +131 -0
- data/lib/barometer/data/forecast.rb +66 -0
- data/lib/barometer/data/geo.rb +98 -0
- data/lib/barometer/data/location.rb +20 -0
- data/lib/barometer/data/measurement.rb +161 -0
- data/lib/barometer/data/pressure.rb +133 -0
- data/lib/barometer/data/speed.rb +147 -0
- data/lib/barometer/data/sun.rb +35 -0
- data/lib/barometer/data/temperature.rb +164 -0
- data/lib/barometer/data/units.rb +55 -0
- data/lib/barometer/data/zone.rb +124 -0
- data/lib/barometer/extensions/graticule.rb +50 -0
- data/lib/barometer/extensions/httparty.rb +21 -0
- data/lib/barometer/query.rb +228 -0
- data/lib/barometer/services.rb +6 -0
- data/lib/barometer/services/google.rb +146 -0
- data/lib/barometer/services/noaa.rb +6 -0
- data/lib/barometer/services/service.rb +324 -0
- data/lib/barometer/services/weather_bug.rb +6 -0
- data/lib/barometer/services/weather_dot_com.rb +6 -0
- data/lib/barometer/services/wunderground.rb +285 -0
- data/lib/barometer/services/yahoo.rb +274 -0
- data/lib/barometer/weather.rb +187 -0
- data/spec/barometer_spec.rb +162 -0
- data/spec/data_current_spec.rb +225 -0
- data/spec/data_distance_spec.rb +336 -0
- data/spec/data_forecast_spec.rb +150 -0
- data/spec/data_geo_spec.rb +90 -0
- data/spec/data_location_spec.rb +59 -0
- data/spec/data_measurement_spec.rb +411 -0
- data/spec/data_pressure_spec.rb +336 -0
- data/spec/data_speed_spec.rb +374 -0
- data/spec/data_sun_spec.rb +76 -0
- data/spec/data_temperature_spec.rb +396 -0
- data/spec/data_zone_spec.rb +133 -0
- data/spec/fixtures/current_calgary_ab.xml +1 -0
- data/spec/fixtures/forecast_calgary_ab.xml +1 -0
- data/spec/fixtures/geocode_40_73.xml +1 -0
- data/spec/fixtures/geocode_90210.xml +1 -0
- data/spec/fixtures/geocode_T5B4M9.xml +1 -0
- data/spec/fixtures/geocode_calgary_ab.xml +1 -0
- data/spec/fixtures/geocode_newyork_ny.xml +1 -0
- data/spec/fixtures/google_calgary_ab.xml +1 -0
- data/spec/fixtures/yahoo_90210.xml +1 -0
- data/spec/query_spec.rb +469 -0
- data/spec/service_google_spec.rb +144 -0
- data/spec/service_wunderground_spec.rb +330 -0
- data/spec/service_yahoo_spec.rb +299 -0
- data/spec/services_spec.rb +1106 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/units_spec.rb +101 -0
- data/spec/weather_spec.rb +265 -0
- metadata +119 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Barometer
|
2
|
+
class Units
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_accessor :metric
|
6
|
+
|
7
|
+
def initialize(metric=true)
|
8
|
+
@metric = metric
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# HELPERS
|
13
|
+
#
|
14
|
+
|
15
|
+
def metric?; @metric; end
|
16
|
+
def metric!; @metric=true; end
|
17
|
+
def imperial!; @metric=false; end
|
18
|
+
|
19
|
+
# assigns a value to the right attribute based on metric setting
|
20
|
+
def <<(value)
|
21
|
+
return unless value
|
22
|
+
|
23
|
+
begin
|
24
|
+
if value.is_a?(Array)
|
25
|
+
value_m = value[0].to_f
|
26
|
+
value_i = value[1].to_f
|
27
|
+
value_b = nil
|
28
|
+
else
|
29
|
+
value_m = nil
|
30
|
+
value_i = nil
|
31
|
+
value_b = value.to_f
|
32
|
+
end
|
33
|
+
rescue
|
34
|
+
# do nothing
|
35
|
+
end
|
36
|
+
|
37
|
+
if self.metric?
|
38
|
+
self.metric_default = value_m || value_b
|
39
|
+
else
|
40
|
+
self.imperial_default = value_i || value_b
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# STUB: define this method to actually retireve the metric_default
|
45
|
+
def metric_default=(value)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
# STUB: define this method to actually retireve the imperial_default
|
50
|
+
def imperial_default=(value)
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'tzinfo'
|
3
|
+
|
4
|
+
module Barometer
|
5
|
+
#
|
6
|
+
# A simple Zone class
|
7
|
+
#
|
8
|
+
# Used for building and converting timezone aware date and times
|
9
|
+
# Really, these are just wrappers for TZInfo conversions.
|
10
|
+
#
|
11
|
+
class Zone
|
12
|
+
|
13
|
+
attr_accessor :timezone, :tz
|
14
|
+
|
15
|
+
def initialize(timezone)
|
16
|
+
@timezone = timezone
|
17
|
+
@tz = TZInfo::Timezone.get(timezone)
|
18
|
+
end
|
19
|
+
|
20
|
+
# what is the Timezone Short Code for the current timezone
|
21
|
+
def code
|
22
|
+
return "" unless @tz
|
23
|
+
@tz.period_for_utc(Time.now.utc).zone_identifier.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# is the current timezone in daylights savings mode?
|
27
|
+
def dst?
|
28
|
+
return nil unless @tz
|
29
|
+
@tz.period_for_utc(Time.now.utc).dst?
|
30
|
+
end
|
31
|
+
|
32
|
+
# return Time.now.utc for the set timezone
|
33
|
+
def now
|
34
|
+
Barometer::Zone.now(@timezone)
|
35
|
+
end
|
36
|
+
|
37
|
+
# return Date.today for the set timezone
|
38
|
+
def today
|
39
|
+
Barometer::Zone.today(@timezone)
|
40
|
+
end
|
41
|
+
|
42
|
+
def local_to_utc(local_time)
|
43
|
+
@tz.local_to_utc(local_time)
|
44
|
+
end
|
45
|
+
|
46
|
+
def utc_to_local(utc_time)
|
47
|
+
@tz.utc_to_local(utc_time)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Class Methods
|
52
|
+
#
|
53
|
+
|
54
|
+
# return the local current time, providing a timezone
|
55
|
+
# (ie 'Europe/Paris') will give the local time for the
|
56
|
+
# timezone, otherwise it will be Time.now
|
57
|
+
def self.now(timezone=nil)
|
58
|
+
if timezone
|
59
|
+
utc = Time.now.utc
|
60
|
+
tz = TZInfo::Timezone.get(timezone)
|
61
|
+
tz.utc_to_local(utc)
|
62
|
+
else
|
63
|
+
Time.now
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# return the local current date, providing a timezone
|
68
|
+
# (ie 'Europe/Paris') will give the local date for the
|
69
|
+
# timezone, otherwise it will be Date.today
|
70
|
+
def self.today(timezone=nil)
|
71
|
+
if timezone
|
72
|
+
utc = Time.now.utc
|
73
|
+
tz = TZInfo::Timezone.get(timezone)
|
74
|
+
now = tz.utc_to_local(utc)
|
75
|
+
Date.new(now.year, now.month, now.day)
|
76
|
+
else
|
77
|
+
Date.today
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# takes a time (any timezone), and a TimeZone Short Code (ie PST) and
|
82
|
+
# converts the time to UTC accorsing to that time_zone
|
83
|
+
# NOTE: No Tests
|
84
|
+
def self.code_to_utc(time, timezone_code)
|
85
|
+
raise ArgumentError unless time.is_a?(Time)
|
86
|
+
offset = Time.zone_offset(timezone_code) || 0
|
87
|
+
|
88
|
+
Time.utc(
|
89
|
+
time.year, time.month, time.day,
|
90
|
+
time.hour, time.min, time.sec, time.usec
|
91
|
+
) - offset
|
92
|
+
end
|
93
|
+
|
94
|
+
# takes a string with TIME only information and merges it with a string that
|
95
|
+
# has DATE only information and creates a UTC TIME object with time and date
|
96
|
+
# info. if you supply the timezone code (ie PST), it will apply the timezone
|
97
|
+
# offset to the final time
|
98
|
+
def self.merge(time, date, timezone_code=nil)
|
99
|
+
raise ArgumentError unless (time.is_a?(Time) || time.is_a?(String))
|
100
|
+
raise ArgumentError unless (date.is_a?(Time) || date.is_a?(Date) || date.is_a?(String))
|
101
|
+
|
102
|
+
if time.is_a?(String)
|
103
|
+
reference_time = Time.parse(time)
|
104
|
+
elsif time.is_a?(Time)
|
105
|
+
reference_time = time
|
106
|
+
end
|
107
|
+
|
108
|
+
if date.is_a?(String)
|
109
|
+
reference_date = Date.parse(date)
|
110
|
+
elsif date.is_a?(Time)
|
111
|
+
reference_date = Date.new(date.year, date.month, date.day)
|
112
|
+
elsif date.is_a?(Date)
|
113
|
+
reference_date = date
|
114
|
+
end
|
115
|
+
|
116
|
+
new_time = Time.utc(
|
117
|
+
reference_date.year, reference_date.month, reference_date.day,
|
118
|
+
reference_time.hour, reference_time.min, reference_time.sec
|
119
|
+
)
|
120
|
+
timezone_code ? Barometer::Zone.code_to_utc(new_time,timezone_code) : new_time
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Graticule
|
2
|
+
class Location
|
3
|
+
|
4
|
+
attr_accessor :country_code
|
5
|
+
|
6
|
+
def attributes
|
7
|
+
[:latitude, :longitude, :street, :locality, :region, :postal_code, :country, :precision, :cuntry_code].inject({}) do |result,attr|
|
8
|
+
result[attr] = self.send(attr) unless self.send(attr).blank?
|
9
|
+
result
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module Geocoder
|
16
|
+
|
17
|
+
class Google < Rest
|
18
|
+
|
19
|
+
# Locates +address+ returning a Location
|
20
|
+
# add ability to bias towards a country
|
21
|
+
def locate(address, country_bias=nil)
|
22
|
+
get :q => (address.is_a?(String) ? address : location_from_params(address).to_s),
|
23
|
+
:gl => country_bias
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Extracts a Location from +xml+.
|
29
|
+
def parse_response(xml) #:nodoc:
|
30
|
+
longitude, latitude, = xml.elements['/kml/Response/Placemark/Point/coordinates'].text.split(',').map { |v| v.to_f }
|
31
|
+
returning Location.new(:latitude => latitude, :longitude => longitude) do |l|
|
32
|
+
address = REXML::XPath.first(xml, '//xal:AddressDetails',
|
33
|
+
'xal' => "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0")
|
34
|
+
|
35
|
+
if address
|
36
|
+
l.street = value(address.elements['.//ThoroughfareName/text()'])
|
37
|
+
l.locality = value(address.elements['.//LocalityName/text()'])
|
38
|
+
l.region = value(address.elements['.//AdministrativeAreaName/text()'])
|
39
|
+
l.postal_code = value(address.elements['.//PostalCodeNumber/text()'])
|
40
|
+
l.country = value(address.elements['.//CountryName/text()'])
|
41
|
+
l.country_code = value(address.elements['.//CountryNameCode/text()'])
|
42
|
+
l.precision = PRECISION[address.attribute('Accuracy').value.to_i] || :unknown
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# extends HTTParty by adding configurable timeout support
|
3
|
+
#
|
4
|
+
module HTTParty
|
5
|
+
class Request
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def http
|
10
|
+
http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
|
11
|
+
http.use_ssl = (uri.port == 443)
|
12
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
13
|
+
if options[:timeout] && options[:timeout].is_a?(Integer)
|
14
|
+
http.open_timeout = options[:timeout]
|
15
|
+
http.read_timeout = options[:timeout]
|
16
|
+
end
|
17
|
+
http
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
module Barometer
|
2
|
+
#
|
3
|
+
# This class represents a query and can answer the
|
4
|
+
# questions that a Barometer will need to measure the weather
|
5
|
+
#
|
6
|
+
# Summary:
|
7
|
+
# When you create a new Query, you set the query string
|
8
|
+
# ie: "New York, NY" or "90210"
|
9
|
+
# The class will then determine the query string format
|
10
|
+
# ie: :zipcode, :postalcode, :geocode, :coordinates
|
11
|
+
# Now, when a Weather API driver asks for the query, it will prefer
|
12
|
+
# certain formats, and only permit certain formats. The Query class
|
13
|
+
# will attempt to either return the query string as-is if acceptable,
|
14
|
+
# or it will attempt to convert it to a format that is acceptable
|
15
|
+
# (most likely this conversion will in Googles geocoding service using
|
16
|
+
# the Graticule gem). Worst case scenario is that the Weather API will
|
17
|
+
# not accept the query string.
|
18
|
+
#
|
19
|
+
class Query
|
20
|
+
|
21
|
+
# OPTIONAL: key required by Google for geocoding
|
22
|
+
@@google_geocode_key = nil
|
23
|
+
def self.google_geocode_key; @@google_geocode_key || Barometer.google_geocode_key; end;
|
24
|
+
def self.google_geocode_key=(key); @@google_geocode_key = key; end;
|
25
|
+
|
26
|
+
attr_reader :format
|
27
|
+
attr_accessor :q, :preferred, :country_code, :geo
|
28
|
+
|
29
|
+
def initialize(query=nil)
|
30
|
+
@q = query
|
31
|
+
self.analyze!
|
32
|
+
end
|
33
|
+
|
34
|
+
# analyze the saved query to determine the format. for the format of
|
35
|
+
# :zipcode and :postalcode the country_code can also be set
|
36
|
+
def analyze!
|
37
|
+
return unless @q
|
38
|
+
if Barometer::Query.is_us_zipcode?(@q)
|
39
|
+
@format = :zipcode
|
40
|
+
elsif Barometer::Query.is_canadian_postcode?(@q)
|
41
|
+
@format = :postalcode
|
42
|
+
elsif Barometer::Query.is_coordinates?(@q)
|
43
|
+
@format = :coordinates
|
44
|
+
else
|
45
|
+
@format = :geocode
|
46
|
+
end
|
47
|
+
@country_code = Barometer::Query.format_to_country_code(@format)
|
48
|
+
end
|
49
|
+
|
50
|
+
# take a list of acceptable (and ordered by preference) formats and convert
|
51
|
+
# the current query (q) into the most preferred and acceptable format. as a
|
52
|
+
# side effect of some conversions, the country_code might be known, then save it
|
53
|
+
def convert!(preferred_formats=nil)
|
54
|
+
raise ArgumentError unless (preferred_formats && preferred_formats.size > 0)
|
55
|
+
# reset preferred
|
56
|
+
@preferred = nil
|
57
|
+
|
58
|
+
# go through each acceptable format and try to convert to that
|
59
|
+
converted = false
|
60
|
+
geocoded = false
|
61
|
+
preferred_formats.each do |preferred_format|
|
62
|
+
# we are already in this format, return this
|
63
|
+
if preferred_format == @format
|
64
|
+
converted = true
|
65
|
+
@preferred ||= @q
|
66
|
+
end
|
67
|
+
|
68
|
+
unless converted
|
69
|
+
case preferred_format
|
70
|
+
when :coordinates
|
71
|
+
geocoded = true
|
72
|
+
@preferred, @country_code, @geo = Barometer::Query.to_coordinates(@q, @format)
|
73
|
+
when :geocode
|
74
|
+
geocoded = true
|
75
|
+
@preferred, @country_code, @geo = Barometer::Query.to_geocode(@q, @format)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# if we haven't already geocoded and we are forcing it, do it now
|
81
|
+
if !geocoded && Barometer.force_geocode
|
82
|
+
not_used_coords, not_used_code, @geo = Barometer::Query.to_coordinates(@q, @format)
|
83
|
+
end
|
84
|
+
|
85
|
+
@preferred
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# HELPERS
|
90
|
+
#
|
91
|
+
|
92
|
+
def zipcode?; @format == :zipcode; end
|
93
|
+
def postalcode?; @format == :postalcode; end
|
94
|
+
def coordinates?; @format == :coordinates; end
|
95
|
+
def geocode?; @format == :geocode; end
|
96
|
+
|
97
|
+
def self.is_us_zipcode?(query)
|
98
|
+
us_zipcode_regex = /(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)/
|
99
|
+
return !(query =~ us_zipcode_regex).nil?
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.is_canadian_postcode?(query)
|
103
|
+
# Rules: no D, F, I, O, Q, or U anywhere
|
104
|
+
# Basic validation: ^[ABCEGHJ-NPRSTVXY]{1}[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
105
|
+
# Extended validation: ^(A(0[ABCEGHJ-NPR]|1[ABCEGHK-NSV-Y]|2[ABHNV]|5[A]|8[A])|B(0[CEHJ-NPRSTVW]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGHJNRSTV-Z]|3[ABEGHJ-NPRSTVZ]|4[ABCEGHNPRV]|5[A]|6[L]|9[A])|C(0[AB]|1[ABCEN])|E(1[ABCEGHJNVWX]|2[AEGHJ-NPRSV]|3[ABCELNVYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTV]|6[ABCEGHJKL]|7[ABCEGHJ-NP]|8[ABCEGJ-NPRST]|9[ABCEGH])|G(0[ACEGHJ-NPRSTV-Z]|1[ABCEGHJ-NPRSTV-Y]|2[ABCEGJ-N]|3[ABCEGHJ-NZ]|4[ARSTVWXZ]|5[ABCHJLMNRTVXYZ]|6[ABCEGHJKLPRSTVWXZ]|7[ABGHJKNPSTXYZ]|8[ABCEGHJ-NPTVWYZ]|9[ABCHNPRTX])|H(0[HM]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRSTV-Z]|4[ABCEGHJ-NPRSTV-Z]|5[AB]|7[ABCEGHJ-NPRSTV-Y]|8[NPRSTYZ]|9[ABCEGHJKPRSWX])|J(0[ABCEGHJ-NPRSTV-Z]|1[ACEGHJ-NRSTXZ]|2[ABCEGHJ-NRSTWXY]|3[ABEGHLMNPRTVXYZ]|4[BGHJ-NPRSTV-Z]|5[ABCJ-MRTV-Z]|6[AEJKNRSTVWYXZ]|7[ABCEGHJ-NPRTV-Z]|8[ABCEGHLMNPRTVXYZ]|9[ABEHJLNTVXYZ])|K(0[ABCEGHJ-M]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-MPRSTVW]|4[ABCKMPR]|6[AHJKTV]|7[ACGHK-NPRSV]|8[ABHNPRV]|9[AHJKLV])|L(0[[ABCEGHJ-NPRS]]|1[ABCEGHJ-NPRSTV-Z]|2[AEGHJMNPRSTVW]|3[BCKMPRSTVXYZ]|4[ABCEGHJ-NPRSTV-Z]|5[ABCEGHJ-NPRSTVW]|6[ABCEGHJ-MPRSTV-Z]|7[ABCEGJ-NPRST]|8[EGHJ-NPRSTVW]|9[ABCGHK-NPRSTVWYZ])|M(1[BCEGHJ-NPRSTVWX]|2[HJ-NPR]|3[ABCHJ-N]|4[ABCEGHJ-NPRSTV-Y]|5[ABCEGHJ-NPRSTVWX]|6[ABCEGHJ-NPRS]|7[AY]|8[V-Z]|9[ABCLMNPRVW])|N(0[ABCEGHJ-NPR]|1[ACEGHKLMPRST]|2[ABCEGHJ-NPRTVZ]|3[ABCEHLPRSTVWY]|4[BGKLNSTVWXZ]|5[ACHLPRV-Z]|6[ABCEGHJ-NP]|7[AGLMSTVWX]|8[AHMNPRSTV-Y]|9[ABCEGHJKVY])|P(0[ABCEGHJ-NPRSTV-Y]|1[ABCHLP]|2[ABN]|3[ABCEGLNPY]|4[NPR]|5[AEN]|6[ABC]|7[ABCEGJKL]|8[NT]|9[AN])|R(0[ABCEGHJ-M]|1[ABN]|2[CEGHJ-NPRV-Y]|3[ABCEGHJ-NPRSTV-Y]|4[AHJKL]|5[AGH]|6[MW]|7[ABCN]|8[AN]|9[A])|S(0[ACEGHJ-NP]|2[V]|3[N]|4[AHLNPRSTV-Z]|6[HJKVWX]|7[HJ-NPRSTVW]|9[AHVX])|T(0[ABCEGHJ-MPV]|1[ABCGHJ-MPRSV-Y]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NPRZ]|4[ABCEGHJLNPRSTVX]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTVWX]|7[AENPSVXYZ]|8[ABCEGHLNRSVWX]|9[ACEGHJKMNSVWX])|V(0[ABCEGHJ-NPRSTVWX]|1[ABCEGHJ-NPRSTV-Z]|2[ABCEGHJ-NPRSTV-Z]|3[ABCEGHJ-NRSTV-Y]|4[ABCEGK-NPRSTVWXZ]|5[ABCEGHJ-NPRSTV-Z]|6[ABCEGHJ-NPRSTV-Z]|7[ABCEGHJ-NPRSTV-Y]|8[ABCGJ-NPRSTV-Z]|9[ABCEGHJ-NPRSTV-Z])|X(0[ABCGX]|1[A])|Y(0[AB]|1[A]))[ ]?[0-9]{1}[ABCEGHJ-NPRSTV-Z]{1}[0-9]{1}$
|
106
|
+
ca_postcode_regex = /^[A-Z]{1}[\d]{1}[A-Z]{1}[ ]?[\d]{1}[A-Z]{1}[\d]{1}$/
|
107
|
+
return !(query =~ ca_postcode_regex).nil?
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.is_coordinates?(query)
|
111
|
+
coordinates_regex = /^[-]?[0-9\.]+[,]{1}[-]?[0-9\.]+$/
|
112
|
+
return !(query =~ coordinates_regex).nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# CONVERTERS
|
117
|
+
#
|
118
|
+
|
119
|
+
# this will take all query formats and convert them to coordinates
|
120
|
+
# accepts- :zipcode, :postalcode, :geocode
|
121
|
+
# returns- :coordinates
|
122
|
+
# if the conversion fails, return nil
|
123
|
+
def self.to_coordinates(query, format)
|
124
|
+
country_code = self.format_to_country_code(format)
|
125
|
+
geo = self.geocode(query, country_code)
|
126
|
+
country_code ||= geo.country_code if geo
|
127
|
+
return nil unless geo && geo.longitude && geo.latitude
|
128
|
+
["#{geo.latitude},#{geo.longitude}", country_code, geo]
|
129
|
+
end
|
130
|
+
|
131
|
+
# this will take all query formats and convert them to coorinates
|
132
|
+
# accepts- :zipcode, :postalcode, :coordinates
|
133
|
+
# returns- :geocode
|
134
|
+
def self.to_geocode(query, format)
|
135
|
+
perform_geocode = false
|
136
|
+
perform_geocode = true if self.has_geocode_key?
|
137
|
+
|
138
|
+
# some formats can't convert, no need to geocode then
|
139
|
+
skip_formats = [:postalcode]
|
140
|
+
perform_geocode = false if skip_formats.include?(format)
|
141
|
+
|
142
|
+
country_code = self.format_to_country_code(format)
|
143
|
+
if perform_geocode
|
144
|
+
geo = self.geocode(query, country_code)
|
145
|
+
country_code ||= geo.country_code if geo
|
146
|
+
return nil unless geo && geo.locality && geo.region && geo.country
|
147
|
+
return ["#{geo.locality}, #{geo.region}, #{geo.country}", country_code, geo]
|
148
|
+
else
|
149
|
+
# without geocoding, the best we can do is just make use the given query as
|
150
|
+
# the query for the "geocode" format
|
151
|
+
return [query, country_code, nil]
|
152
|
+
end
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# --- TODO ---
|
158
|
+
# The following methods need more coverage tests
|
159
|
+
#
|
160
|
+
|
161
|
+
def self.has_geocode_key?
|
162
|
+
# quick check to see that the Google API key exists for geocoding
|
163
|
+
self.google_geocode_key && !self.google_geocode_key.nil?
|
164
|
+
end
|
165
|
+
|
166
|
+
# if Graticule exists, use it, otherwise use HTTParty
|
167
|
+
def self.geocode(query, country_code=nil)
|
168
|
+
use_graticule = false
|
169
|
+
unless Barometer::skip_graticule
|
170
|
+
begin
|
171
|
+
require 'rubygems'
|
172
|
+
require 'graticule'
|
173
|
+
$:.unshift(File.dirname(__FILE__))
|
174
|
+
# load some changes to Graticule
|
175
|
+
# TODO: attempt to get changes into Graticule gem
|
176
|
+
require 'extensions/graticule'
|
177
|
+
use_graticule = true
|
178
|
+
rescue LoadError
|
179
|
+
# do nothing, we will use HTTParty
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
if use_graticule
|
184
|
+
geo = self.geocode_graticule(query, country_code)
|
185
|
+
else
|
186
|
+
geo = self.geocode_httparty(query, country_code)
|
187
|
+
end
|
188
|
+
geo
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.geocode_graticule(query, country_code=nil)
|
192
|
+
return nil unless self.has_geocode_key?
|
193
|
+
geocoder = Graticule.service(:google).new(self.google_geocode_key)
|
194
|
+
location = geocoder.locate(query, country_code)
|
195
|
+
geo = Barometer::Geo.new(location)
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.geocode_httparty(query, country_code=nil)
|
199
|
+
return nil unless self.has_geocode_key?
|
200
|
+
location = Barometer::Service.get(
|
201
|
+
"http://maps.google.com/maps/geo",
|
202
|
+
:query => {
|
203
|
+
:gl => country_code,
|
204
|
+
:key => self.google_geocode_key,
|
205
|
+
:output => "xml",
|
206
|
+
:q => query
|
207
|
+
},
|
208
|
+
:format => :xml
|
209
|
+
)['kml']['Response']
|
210
|
+
#puts location.inspect
|
211
|
+
geo = Barometer::Geo.new(location)
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.format_to_country_code(format)
|
215
|
+
return nil unless format
|
216
|
+
case format
|
217
|
+
when :zipcode
|
218
|
+
country_code = "US"
|
219
|
+
when :postalcode
|
220
|
+
country_code = "CA"
|
221
|
+
else
|
222
|
+
country_code = nil
|
223
|
+
end
|
224
|
+
country_code
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|