geo_tools 1.0.1 → 1.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.
@@ -1,266 +0,0 @@
1
- module AirBlade
2
- module GeoTools
3
- module Location
4
-
5
- def self.included(base)
6
- # Lazy loading pattern.
7
- base.extend ActMethods
8
- end
9
-
10
- module ActMethods
11
- def acts_as_location
12
- unless included_modules.include? InstanceMethods
13
- extend ClassMethods
14
- include InstanceMethods
15
-
16
- code = <<-END
17
- validates_numericality_of_for :latitude_degrees,
18
- :only_integer => true,
19
- :greater_than_or_equal_to => 0,
20
- :less_than_or_equal_to => 90,
21
- :message => 'Degrees are invalid',
22
- :for => :latitude
23
-
24
- validates_numericality_of_for :latitude_minutes,
25
- :only_integer => true,
26
- :greater_than_or_equal_to => 0,
27
- :less_than => 60,
28
- :message => 'Minutes are invalid',
29
- :for => :latitude
30
-
31
- validates_numericality_of_for :latitude_decimal_minutes,
32
- :only_integer => true,
33
- :greater_than_or_equal_to => 0,
34
- :message => 'Decimal minutes are invalid',
35
- :for => :latitude
36
-
37
- validates_numericality_of_for :latitude_decimal_minutes_width,
38
- :only_integer => true,
39
- :greater_than_or_equal_to => 0,
40
- :for => :latitude
41
-
42
- validates_inclusion_of_for :latitude_hemisphere,
43
- :in => %w( N S ),
44
- :message => 'Hemisphere is invalid',
45
- :for => :latitude
46
-
47
- validates_numericality_of_for :longitude_degrees,
48
- :only_integer => true,
49
- :greater_than_or_equal_to => 0,
50
- :less_than_or_equal_to => 180,
51
- :message => 'Degrees are invalid',
52
- :for => :longitude
53
-
54
- validates_numericality_of_for :longitude_minutes,
55
- :only_integer => true,
56
- :greater_than_or_equal_to => 0,
57
- :less_than => 60,
58
- :message => 'Minutes are invalid',
59
- :for => :longitude
60
-
61
- validates_numericality_of_for :longitude_decimal_minutes,
62
- :only_integer => true,
63
- :greater_than_or_equal_to => 0,
64
- :message => 'Decimal minutes are invalid',
65
- :for => :longitude
66
-
67
- validates_numericality_of_for :longitude_decimal_minutes_width,
68
- :only_integer => true,
69
- :greater_than_or_equal_to => 0,
70
- :for => :longitude
71
-
72
- validates_inclusion_of_for :longitude_hemisphere,
73
- :in => %w( E W ),
74
- :message => 'Hemisphere is invalid',
75
- :for => :longitude
76
-
77
- before_validation :set_empty_values
78
- END
79
- class_eval code, __FILE__, __LINE__
80
-
81
- # Returns all locations within the given bounding box, to an accuracy of 1 minute.
82
- #
83
- # This is useful for finding all locations within the area covered by a Google map.
84
- #
85
- # The parameters should be positive/negative floats.
86
- named_scope :within, lambda { |sw_lat, sw_lng, ne_lat, ne_lng|
87
- sw_lat_degs = sw_lat.to_i.abs
88
- sw_lat_mins = ((sw_lat - sw_lat.to_i) * 60.0).round.abs
89
- ne_lat_degs = ne_lat.to_i.abs
90
- ne_lat_mins = ((ne_lat - ne_lat.to_i) * 60.0).round.abs
91
-
92
- sw_lng_degs = sw_lng.to_i.abs
93
- sw_lng_mins = ((sw_lng - sw_lng.to_i) * 60.0).round.abs
94
- ne_lng_degs = ne_lng.to_i.abs
95
- ne_lng_mins = ((ne_lng - ne_lng.to_i) * 60.0).round.abs
96
-
97
- # Latitude conditions.
98
- if sw_lat > 0 && ne_lat > 0 # northern hemisphere
99
- condition_lat_h = 'latitude_hemisphere = "N"'
100
- condition_lat_sw = ["(latitude_degrees > ?) OR (latitude_degrees = ? AND latitude_minutes >= ?)", sw_lat_degs, sw_lat_degs, sw_lat_mins]
101
- condition_lat_ne = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", ne_lat_degs, ne_lat_degs, ne_lat_mins]
102
- condition_lat = merge_conditions condition_lat_h, condition_lat_sw, condition_lat_ne
103
-
104
- elsif sw_lat < 0 && ne_lat < 0 # southern hemisphere
105
- condition_lat_h = 'latitude_hemisphere = "S"'
106
- condition_lat_sw = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", sw_lat_degs, sw_lat_degs, sw_lat_mins]
107
- condition_lat_ne = ["(latitude_degrees > ?) OR (latitude_degrees = ? AND latitude_minutes >= ?)", ne_lat_degs, ne_lat_degs, ne_lat_mins]
108
- condition_lat = merge_conditions condition_lat_h, condition_lat_sw, condition_lat_ne
109
-
110
- elsif sw_lat <= 0 && ne_lat >= 0 # straddles equator
111
- condition_lat_h = 'latitude_hemisphere = "S"'
112
- condition_lat_sw = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", sw_lat_degs, sw_lat_degs, sw_lat_mins]
113
- condition_lat_s = merge_conditions condition_lat_h, condition_lat_sw
114
-
115
- condition_lat_h = 'latitude_hemisphere = "N"'
116
- condition_lat_ne = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", ne_lat_degs, ne_lat_degs, ne_lat_mins]
117
- condition_lat_n = merge_conditions condition_lat_h, condition_lat_ne
118
-
119
- condition_lat = merge_or_conditions condition_lat_s, condition_lat_n
120
- end
121
-
122
- # Longitude conditions.
123
- if sw_lng > 0 && ne_lng > 0 # eastern hemisphere
124
- condition_lng_h = 'longitude_hemisphere = "E"'
125
- condition_lng_sw = ["(longitude_degrees > ?) OR (longitude_degrees = ? AND longitude_minutes >= ?)", sw_lng_degs, sw_lng_degs, sw_lng_mins]
126
- condition_lng_ne = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", ne_lng_degs, ne_lng_degs, ne_lng_mins]
127
- condition_lng = merge_conditions condition_lng_h, condition_lng_sw, condition_lng_ne
128
-
129
- elsif sw_lng < 0 && ne_lng < 0 # western hemisphere
130
- condition_lng_h = 'longitude_hemisphere = "W"'
131
- condition_lng_sw = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", sw_lng_degs, sw_lng_degs, sw_lng_mins]
132
- condition_lng_ne = ["(longitude_degrees > ?) OR (longitude_degrees = ? AND longitude_minutes >= ?)", ne_lng_degs, ne_lng_degs, ne_lng_mins]
133
- condition_lng = merge_conditions condition_lng_h, condition_lng_sw, condition_lng_ne
134
-
135
- elsif sw_lng <= 0 && ne_lng >= 0 # straddles prime meridian
136
- condition_lng_h = 'longitude_hemisphere = "W"'
137
- condition_lng_sw = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", sw_lng_degs, sw_lng_degs, sw_lng_mins]
138
- condition_lng_w = merge_conditions condition_lng_h, condition_lng_sw
139
-
140
- condition_lng_h = 'longitude_hemisphere = "E"'
141
- condition_lng_ne = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", ne_lng_degs, ne_lng_degs, ne_lng_mins]
142
- condition_lng_e = merge_conditions condition_lng_h, condition_lng_ne
143
-
144
- condition_lng = merge_or_conditions condition_lng_w, condition_lng_e
145
- end
146
-
147
- # Combined latitude and longitude conditions.
148
- {:conditions => merge_conditions(condition_lat, condition_lng)}
149
- }
150
-
151
- end
152
- end
153
- end
154
-
155
- module ClassMethods
156
- # Merges conditions so that the result is a valid +condition+.
157
- # Adapted from ActiveRecord::Base#merge_conditions.
158
- def merge_or_conditions(*conditions)
159
- segments = []
160
-
161
- conditions.each do |condition|
162
- unless condition.blank?
163
- sql = sanitize_sql(condition)
164
- segments << sql unless sql.blank?
165
- end
166
- end
167
-
168
- "(#{segments.join(') OR (')})" unless segments.empty?
169
- end
170
- end
171
-
172
- module InstanceMethods
173
-
174
- def latitude_decimal_minutes=(value)
175
- unless value.nil?
176
- width = value.to_s.length
177
- value = value.to_i
178
-
179
- write_attribute :latitude_decimal_minutes, value
180
- write_attribute :latitude_decimal_minutes_width, width
181
- end
182
- end
183
-
184
- def latitude_decimal_minutes_as_string
185
- "%0#{latitude_decimal_minutes_width}d" % latitude_decimal_minutes
186
- end
187
-
188
- def longitude_decimal_minutes=(value)
189
- unless value.nil?
190
- width = value.to_s.length
191
- value = value.to_i
192
-
193
- write_attribute :longitude_decimal_minutes, value
194
- write_attribute :longitude_decimal_minutes_width, width
195
- end
196
- end
197
-
198
- def longitude_decimal_minutes_as_string
199
- "%0#{longitude_decimal_minutes_width}d" % longitude_decimal_minutes
200
- end
201
-
202
- def latitude
203
- to_float latitude_degrees, latitude_minutes, latitude_decimal_minutes,
204
- latitude_decimal_minutes_width, latitude_hemisphere
205
- end
206
-
207
- def longitude
208
- to_float longitude_degrees, longitude_minutes, longitude_decimal_minutes,
209
- longitude_decimal_minutes_width, longitude_hemisphere
210
- end
211
-
212
- def to_s
213
- # Unicode degree symbol, full stop, Unicode minute symbol.
214
- units = [ "\xc2\xb0", '.', "\xe2\x80\xb2" ]
215
-
216
- lat_fields = ["%02d" % latitude_degrees,
217
- "%02d" % latitude_minutes,
218
- latitude_decimal_minutes_as_string.ljust(2, '0'),
219
- latitude_hemisphere]
220
- lat = lat_fields.zip(units).map{ |f| f.join }.join
221
-
222
- long_fields = ["%02d" % longitude_degrees,
223
- "%02d" % longitude_minutes,
224
- longitude_decimal_minutes_as_string.ljust(2, '0'),
225
- longitude_hemisphere]
226
- long = long_fields.zip(units).map{ |f| f.join }.join
227
-
228
- "#{lat}, #{long}"
229
- end
230
-
231
- private
232
-
233
- def to_float(degrees, minutes, decimal_minutes, decimal_minutes_width, hemisphere)
234
- return nil if degrees.nil? and minutes.nil? and decimal_minutes.nil?
235
- degrees ||= 0
236
- minutes ||= 0
237
- decimal_minutes ||= 0
238
-
239
- f = degrees.to_f
240
- f = f + (minutes.to_f + decimal_minutes.to_f / 10 ** decimal_minutes_width) / 60.0
241
- f = f * -1 if hemisphere == 'S' or hemisphere == 'W'
242
- f
243
- end
244
-
245
- # If some of the fields are empty, set them to zero. This is to speed up data entry.
246
- # If all the fields are empty, leave them empty.
247
- def set_empty_values
248
- unless latitude_degrees.blank? and latitude_minutes.blank? and latitude_decimal_minutes.blank?
249
- self.latitude_degrees = 0 if latitude_degrees.blank?
250
- self.latitude_minutes = 0 if latitude_minutes.blank?
251
- self.latitude_decimal_minutes = 0 if latitude_decimal_minutes.blank?
252
- end
253
-
254
- unless longitude_degrees.blank? and longitude_minutes.blank? and longitude_decimal_minutes.blank?
255
- self.longitude_degrees = 0 if longitude_degrees.blank?
256
- self.longitude_minutes = 0 if longitude_minutes.blank?
257
- self.longitude_decimal_minutes = 0 if longitude_decimal_minutes.blank?
258
- end
259
- end
260
- end
261
-
262
- end
263
- end
264
- end
265
-
266
- ActiveRecord::Base.send :include, AirBlade::GeoTools::Location
@@ -1,75 +0,0 @@
1
- module AirBlade
2
- module GeoTools
3
- module Validations
4
-
5
- # Sames as validates_numericality_of but additionally supports :for option
6
- # which lets you attach an error to a different attribute.
7
- def validates_inclusion_of_for(*attr_names)
8
- configuration = { :on => :save }
9
- configuration.update(attr_names.extract_options!)
10
-
11
- enum = configuration[:in] || configuration[:within]
12
-
13
- raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?(:include?)
14
-
15
- validates_each(attr_names, configuration) do |record, attr_name, value|
16
- unless enum.include?(value)
17
- attr_for = configuration[:for] || attr_name
18
- record.errors.add(attr_for, :inclusion, :default => configuration[:message], :value => value)
19
- end
20
- end
21
- end
22
-
23
- # Sames as validates_numericality_of but additionally supports :for option
24
- # which lets you attach an error to a different attribute.
25
- def validates_numericality_of_for(*attr_names)
26
- configuration = { :on => :save, :only_integer => false, :allow_nil => false }
27
- configuration.update(attr_names.extract_options!)
28
-
29
- numericality_options = ActiveRecord::Validations::ClassMethods::ALL_NUMERICALITY_CHECKS.keys & configuration.keys
30
-
31
- (numericality_options - [ :odd, :even ]).each do |option|
32
- raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric)
33
- end
34
-
35
- validates_each(attr_names,configuration) do |record, attr_name, value|
36
- raw_value = record.send("#{attr_name}_before_type_cast") || value
37
-
38
- next if configuration[:allow_nil] and raw_value.nil?
39
-
40
- attr_for = configuration[:for] || attr_name
41
-
42
- if configuration[:only_integer]
43
- unless raw_value.to_s =~ /\A[+-]?\d+\Z/
44
- record.errors.add(attr_for, :not_a_number, :value => raw_value, :default => configuration[:message])
45
- next
46
- end
47
- raw_value = raw_value.to_i
48
- else
49
- begin
50
- raw_value = Kernel.Float(raw_value)
51
- rescue ArgumentError, TypeError
52
- record.errors.add(attr_for, :not_a_number, :value => raw_value, :default => configuration[:message])
53
- next
54
- end
55
- end
56
-
57
- numericality_options.each do |option|
58
- case option
59
- when :odd, :even
60
- unless raw_value.to_i.method( ActiveRecord::Validations::ClassMethods::ALL_NUMERICALITY_CHECKS[option])[]
61
- record.errors.add(attr_for, option, :value => raw_value, :default => configuration[:message])
62
- end
63
- else
64
- record.errors.add(attr_for, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method( ActiveRecord::Validations::ClassMethods::ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
65
- end
66
- end
67
- end
68
- end
69
-
70
- end
71
- end
72
- end
73
-
74
-
75
- ActiveRecord::Base.send :extend, AirBlade::GeoTools::Validations
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :geo_tools do
3
- # # Task goes here
4
- # end
@@ -1 +0,0 @@
1
- # Uninstall hook code here