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.
- data/.gitignore +4 -0
- data/.rbenv-version +1 -0
- data/Gemfile +2 -0
- data/README.md +36 -36
- data/Rakefile +1 -37
- data/geo_tools.gemspec +25 -0
- data/lib/geo_tools.rb +4 -3
- data/lib/geo_tools/form_helpers.rb +103 -0
- data/lib/geo_tools/location.rb +259 -0
- data/lib/geo_tools/validations.rb +79 -0
- data/lib/geo_tools/version.rb +3 -0
- data/test/geo_tools_test.rb +39 -29
- data/test/test_helper.rb +17 -4
- metadata +72 -47
- data/VERSION +0 -1
- data/init.rb +0 -1
- data/install.rb +0 -1
- data/lib/air_blade/geo_tools/form_helpers.rb +0 -166
- data/lib/air_blade/geo_tools/location.rb +0 -266
- data/lib/air_blade/geo_tools/validations.rb +0 -75
- data/tasks/geo_tools_tasks.rake +0 -4
- data/uninstall.rb +0 -1
@@ -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
|
data/tasks/geo_tools_tasks.rake
DELETED
data/uninstall.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Uninstall hook code here
|