geo_tools 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
@@ -0,0 +1 @@
1
+ 1.9.3-p0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # GeoTools
2
2
 
3
-
4
3
  You have lots of plugin choices if you want to geocode North American addresses, or find all the locations near somewhere. But few help you with forms and validation.
5
4
 
6
5
  This plugin does four things:
@@ -11,49 +10,56 @@ This plugin does four things:
11
10
  * Gives you a `within` named scope to find all lcoations within a given bounding box, such as you would have on a Google map.
12
11
 
13
12
 
13
+ ## Compatibility
14
+
15
+ Works on Ruby 1.8+ and 1.9+.
16
+
17
+ Designed for Rails 2.3.
18
+
19
+
14
20
  ## Assumptions
15
21
 
16
22
  * Any model that acts_as_location has integers defined for each component of the latitude and longitude:
17
23
 
18
- # In your model's migration's self.up method:
19
- create_table :thingies do |t|
20
- # Your model's various fields.
21
- t.string :name
22
- t.timestamps
23
- ...
24
-
25
- # Stuff GeoTools needs:
26
- t.integer :latitude_degrees, :latitude_minutes, :latitude_decimal_minutes, :latitude_decimal_minutes_width
27
- t.string :latitude_hemisphere
28
- t.integer :longitude_degrees, :longitude_minutes, :longitude_decimal_minutes, :longitude_decimal_minutes_width
29
- t.string :longitude_hemisphere
30
- end
24
+ # In your model's migration's self.up method:
25
+ create_table :thingies do |t|
26
+ # Your model's various fields.
27
+ t.string :name
28
+ t.timestamps
29
+ ...
30
+
31
+ # Stuff GeoTools needs:
32
+ t.integer :latitude_degrees, :latitude_minutes, :latitude_decimal_minutes, :latitude_decimal_minutes_width
33
+ t.string :latitude_hemisphere
34
+ t.integer :longitude_degrees, :longitude_minutes, :longitude_decimal_minutes, :longitude_decimal_minutes_width
35
+ t.string :longitude_hemisphere
36
+ end
31
37
 
32
- Storing the components separately like this avoids the round-trip rounding errors you get when using floating point numbers. If you need a floating point representation in the database, for example to use a mapping plugin, simply add an after_update callback to your model to write the float value to the database.
38
+ Storing the components separately like this avoids the round-trip rounding errors you get when using floating point numbers. If you need a floating point representation in the database, for example to use a mapping plugin, simply add an after_update callback to your model to write the float value to the database.
33
39
 
34
40
  * A latitude should be entered on a form like this:
35
41
 
36
- xx <degree symbol> yy <decimal point> zz h
42
+ xx <degree symbol> yy <decimal point> zz h
37
43
 
38
- where:
44
+ where:
39
45
 
40
- xx is degrees (0 <= integer <= 90; maximum length of 2 digits)
41
- yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
42
- zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
43
- h is hemisphere ('N' or 'S')
46
+ xx is degrees (0 <= integer <= 90; maximum length of 2 digits)
47
+ yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
48
+ zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
49
+ h is hemisphere ('N' or 'S')
44
50
 
45
- Note with decimal minutes 2, 20 and 200000 are equivalent. This is because 3.2, 3.20 and 3.200000 are equivalent.
51
+ Note with decimal minutes 2, 20 and 200000 are equivalent. This is because 3.2, 3.20 and 3.200000 are equivalent.
46
52
 
47
53
  * Similarly, a longitude should be entered on a form like this:
48
54
 
49
- xxx <degree symbol> yy <decimal point> zz h
55
+ xxx <degree symbol> yy <decimal point> zz h
50
56
 
51
- where:
57
+ where:
52
58
 
53
- xxx is degrees (0 <= integer <= 180; maximum length of 3 digits)
54
- yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
55
- zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
56
- h is hemisphere ('E' or 'W')
59
+ xxx is degrees (0 <= integer <= 180; maximum length of 3 digits)
60
+ yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
61
+ zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
62
+ h is hemisphere ('E' or 'W')
57
63
 
58
64
 
59
65
  ## Example
@@ -87,20 +93,14 @@ Here's an example script/console session:
87
93
  -12.576
88
94
 
89
95
 
90
- ## To Do
96
+ ## Someday / Maybe
91
97
 
92
- * Get tests to run transactionally so we don't have to clean out database in every single #setup method.
98
+ * Refactor the multiple string columns into a single string column (per lat. and per lng.), and use virtual attributes to map the single db field back and forth to multiple form fields. Add a float to 'cache' the lat/lng values. This will simplify the code significantly.
93
99
  * Add a validation for the overall latitude and longitude values (to catch for example 90°00.01′N).
94
100
  * Use `method` in the form helpers so user can give database columns different names (e.g. my_lat_degrees, etc).
95
101
  See the way Paperclip allows different attachment names.
96
102
  * DRY up form helper methods.
97
103
  * DRY up location.rb.
98
- * Investigate implementing with ActiveRecord's multiparameter assignment.
99
-
100
-
101
- ## Feedback
102
-
103
- Yes please! --> boss@airbladesoftware.com
104
104
 
105
105
 
106
106
  ## Intellectual Property
data/Rakefile CHANGED
@@ -1,37 +1 @@
1
- require 'rake'
2
- require 'rake/testtask'
3
- require 'rake/rdoctask'
4
-
5
- desc 'Default: run unit tests.'
6
- task :default => :test
7
-
8
- desc 'Test the geo_tools plugin.'
9
- Rake::TestTask.new(:test) do |t|
10
- t.libs << 'lib'
11
- t.libs << 'test'
12
- t.pattern = 'test/**/*_test.rb'
13
- t.verbose = true
14
- end
15
-
16
- desc 'Generate documentation for the geo_tools plugin.'
17
- Rake::RDocTask.new(:rdoc) do |rdoc|
18
- rdoc.rdoc_dir = 'rdoc'
19
- rdoc.title = 'GeoTools'
20
- rdoc.options << '--line-numbers' << '--inline-source'
21
- rdoc.rdoc_files.include('README')
22
- rdoc.rdoc_files.include('lib/**/*.rb')
23
- end
24
-
25
- begin
26
- require 'jeweler'
27
- Jeweler::Tasks.new do |gemspec|
28
- gemspec.name = 'geo_tools'
29
- gemspec.summary = 'View helpers, validations, and named scopes for locations.'
30
- gemspec.email = 'boss@airbladesoftware.com'
31
- gemspec.homepage = 'http://github.com/airblade/geo_tools'
32
- gemspec.authors = ['Andy Stewart']
33
- end
34
- Jeweler::GemcutterTasks.new
35
- rescue LoadError
36
- puts 'Jeweler not available. Install it with: gem install jeweler'
37
- end
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "geo_tools/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'geo_tools'
7
+ s.version = GeoTools::VERSION
8
+ s.authors = ['Andy Stewart']
9
+ s.email = ['boss@airbladesoftware.com']
10
+ s.homepage = 'https://github.com/airblade/geo_tools'
11
+ s.summary = 'Makes using latitudes and longitudes on forms easier.'
12
+ s.description = s.summary
13
+
14
+ s.rubyforge_project = 'geo_tools'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency 'rails', '~> 2.3'
22
+
23
+ s.add_development_dependency 'sqlite3-ruby'
24
+ s.add_development_dependency 'shoulda-context', '~> 1.0.0'
25
+ end
@@ -1,3 +1,4 @@
1
- require File.dirname(__FILE__) + '/air_blade/geo_tools/validations'
2
- require File.dirname(__FILE__) + '/air_blade/geo_tools/location'
3
- require File.dirname(__FILE__) + '/air_blade/geo_tools/form_helpers'
1
+ require 'geo_tools/validations'
2
+ require 'geo_tools/location'
3
+ require 'geo_tools/form_helpers'
4
+ require 'geo_tools/version'
@@ -0,0 +1,103 @@
1
+ # encoding: utf-8
2
+ module GeoTools
3
+ module FormHelpers
4
+
5
+ # Options:
6
+ # :latitude
7
+ # :degrees
8
+ # :symbol
9
+ # :minutes
10
+ # :symbol
11
+ # :decimal_minutes
12
+ # :symbol
13
+ # :maxlength
14
+ #
15
+ # Assumes the latitude field is called 'latitude'.
16
+ #
17
+ # The 'method' argument is for consistency with other field helpers. We don't use it
18
+ # when using the normal Rails form builder.
19
+ #
20
+ # 1/100th of a minute of latitude (or equitorial longitude) is approximately 20m.
21
+ def latitude_field(method, options = {})
22
+ opts = {
23
+ :degrees => { :symbol => '&deg;' },
24
+ :minutes => { :symbol => '.' },
25
+ :decimal_minutes => { :symbol => '&prime;', :maxlength => 2 },
26
+ }
27
+ lat_options = options.delete :latitude
28
+ opts.merge! lat_options if lat_options
29
+
30
+ output = []
31
+
32
+ # Degrees
33
+ width = 2
34
+ output << text_field("latitude_degrees",
35
+ options.merge(:maxlength => width,
36
+ :value => "%0#{width}d" % (@object.send("latitude_degrees") || 0)))
37
+ output << opts[:degrees][:symbol]
38
+
39
+ # Minutes
40
+ width = 2
41
+ output << text_field("latitude_minutes",
42
+ options.merge(:maxlength => width,
43
+ :value => "%0#{width}d" % (@object.send("latitude_minutes") || 0)))
44
+ output << opts[:minutes][:symbol]
45
+
46
+ # Decimal minutes
47
+ width = opts[:decimal_minutes][:maxlength]
48
+ output << text_field("latitude_decimal_minutes",
49
+ options.merge(:maxlength => width,
50
+ :value => @object.send("latitude_decimal_minutes_as_string").ljust(width, '0')))
51
+ output << opts[:decimal_minutes][:symbol]
52
+
53
+ # Hemisphere.
54
+ # Hmm, we pass the options in the html_options position.
55
+ output << select("latitude_hemisphere", %w( N S ), {}, options)
56
+
57
+ output.join "\n"
58
+ end
59
+
60
+ def longitude_field(method, options = {})
61
+ opts = {
62
+ :degrees => { :symbol => '&deg;' },
63
+ :minutes => { :symbol => '.' },
64
+ :decimal_minutes => { :symbol => '&prime;', :maxlength => 2 },
65
+ }
66
+ long_options = options.delete :longitude
67
+ opts.merge! long_options if long_options
68
+
69
+ output = []
70
+
71
+ # Degrees
72
+ width = 3
73
+ output << text_field("longitude_degrees",
74
+ options.merge(:maxlength => width,
75
+ :value => "%0#{width}d" % (@object.send("longitude_degrees") || 0)))
76
+ output << opts[:degrees][:symbol]
77
+
78
+ # Minutes
79
+ width = 2
80
+ output << text_field("longitude_minutes",
81
+ options.merge(:maxlength => width,
82
+ :value => "%0#{width}d" % (@object.send("longitude_minutes") || 0)))
83
+ output << opts[:minutes][:symbol]
84
+
85
+ # Decimal minutes
86
+ width = opts[:decimal_minutes][:maxlength]
87
+ output << text_field("longitude_decimal_minutes",
88
+ options.merge(:maxlength => width,
89
+ :value => @object.send("longitude_decimal_minutes_as_string").ljust(width, '0')))
90
+ output << opts[:decimal_minutes][:symbol]
91
+
92
+ # Hemisphere.
93
+ # Hmm, we pass the options in the html_options position.
94
+ output << select("longitude_hemisphere", %w( E W ), {}, options)
95
+
96
+ output.join "\n"
97
+ end
98
+
99
+ end
100
+ end
101
+
102
+
103
+ ActionView::Helpers::FormBuilder.send :include, GeoTools::FormHelpers
@@ -0,0 +1,259 @@
1
+ # encoding: utf-8
2
+ module GeoTools
3
+ module Location
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def acts_as_location
11
+ include InstanceMethodsOnActivation
12
+
13
+ validates_numericality_of_for :latitude_degrees,
14
+ :only_integer => true,
15
+ :greater_than_or_equal_to => 0,
16
+ :less_than_or_equal_to => 90,
17
+ :message => 'Degrees are invalid',
18
+ :for => :latitude
19
+
20
+ validates_numericality_of_for :latitude_minutes,
21
+ :only_integer => true,
22
+ :greater_than_or_equal_to => 0,
23
+ :less_than => 60,
24
+ :message => 'Minutes are invalid',
25
+ :for => :latitude
26
+
27
+ validates_numericality_of_for :latitude_decimal_minutes,
28
+ :only_integer => true,
29
+ :greater_than_or_equal_to => 0,
30
+ :message => 'Decimal minutes are invalid',
31
+ :for => :latitude
32
+
33
+ validates_numericality_of_for :latitude_decimal_minutes_width,
34
+ :only_integer => true,
35
+ :greater_than_or_equal_to => 0,
36
+ :for => :latitude
37
+
38
+ validates_inclusion_of_for :latitude_hemisphere,
39
+ :in => %w( N S ),
40
+ :message => 'Hemisphere is invalid',
41
+ :for => :latitude
42
+
43
+ validates_numericality_of_for :longitude_degrees,
44
+ :only_integer => true,
45
+ :greater_than_or_equal_to => 0,
46
+ :less_than_or_equal_to => 180,
47
+ :message => 'Degrees are invalid',
48
+ :for => :longitude
49
+
50
+ validates_numericality_of_for :longitude_minutes,
51
+ :only_integer => true,
52
+ :greater_than_or_equal_to => 0,
53
+ :less_than => 60,
54
+ :message => 'Minutes are invalid',
55
+ :for => :longitude
56
+
57
+ validates_numericality_of_for :longitude_decimal_minutes,
58
+ :only_integer => true,
59
+ :greater_than_or_equal_to => 0,
60
+ :message => 'Decimal minutes are invalid',
61
+ :for => :longitude
62
+
63
+ validates_numericality_of_for :longitude_decimal_minutes_width,
64
+ :only_integer => true,
65
+ :greater_than_or_equal_to => 0,
66
+ :for => :longitude
67
+
68
+ validates_inclusion_of_for :longitude_hemisphere,
69
+ :in => %w( E W ),
70
+ :message => 'Hemisphere is invalid',
71
+ :for => :longitude
72
+
73
+ before_validation :set_empty_values
74
+
75
+ # Returns all locations within the given bounding box, to an accuracy of 1 minute.
76
+ #
77
+ # This is useful for finding all locations within the area covered by a Google map.
78
+ #
79
+ # The parameters should be positive/negative floats.
80
+ named_scope :within, lambda { |sw_lat, sw_lng, ne_lat, ne_lng|
81
+ sw_lat_degs = sw_lat.to_i.abs
82
+ sw_lat_mins = ((sw_lat - sw_lat.to_i) * 60.0).round.abs
83
+ ne_lat_degs = ne_lat.to_i.abs
84
+ ne_lat_mins = ((ne_lat - ne_lat.to_i) * 60.0).round.abs
85
+
86
+ sw_lng_degs = sw_lng.to_i.abs
87
+ sw_lng_mins = ((sw_lng - sw_lng.to_i) * 60.0).round.abs
88
+ ne_lng_degs = ne_lng.to_i.abs
89
+ ne_lng_mins = ((ne_lng - ne_lng.to_i) * 60.0).round.abs
90
+
91
+ # Latitude conditions.
92
+ if sw_lat > 0 && ne_lat > 0 # northern hemisphere
93
+ condition_lat_h = 'latitude_hemisphere = "N"'
94
+ condition_lat_sw = ["(latitude_degrees > ?) OR (latitude_degrees = ? AND latitude_minutes >= ?)", sw_lat_degs, sw_lat_degs, sw_lat_mins]
95
+ condition_lat_ne = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", ne_lat_degs, ne_lat_degs, ne_lat_mins]
96
+ condition_lat = merge_conditions condition_lat_h, condition_lat_sw, condition_lat_ne
97
+
98
+ elsif sw_lat < 0 && ne_lat < 0 # southern hemisphere
99
+ condition_lat_h = 'latitude_hemisphere = "S"'
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 # straddles equator
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_s = merge_conditions condition_lat_h, condition_lat_sw
108
+
109
+ condition_lat_h = 'latitude_hemisphere = "N"'
110
+ condition_lat_ne = ["(latitude_degrees < ?) OR (latitude_degrees = ? AND latitude_minutes <= ?)", ne_lat_degs, ne_lat_degs, ne_lat_mins]
111
+ condition_lat_n = merge_conditions condition_lat_h, condition_lat_ne
112
+
113
+ condition_lat = merge_or_conditions condition_lat_s, condition_lat_n
114
+ end
115
+
116
+ # Longitude conditions.
117
+ if sw_lng > 0 && ne_lng > 0 # eastern hemisphere
118
+ condition_lng_h = 'longitude_hemisphere = "E"'
119
+ condition_lng_sw = ["(longitude_degrees > ?) OR (longitude_degrees = ? AND longitude_minutes >= ?)", sw_lng_degs, sw_lng_degs, sw_lng_mins]
120
+ condition_lng_ne = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", ne_lng_degs, ne_lng_degs, ne_lng_mins]
121
+ condition_lng = merge_conditions condition_lng_h, condition_lng_sw, condition_lng_ne
122
+
123
+ elsif sw_lng < 0 && ne_lng < 0 # western hemisphere
124
+ condition_lng_h = 'longitude_hemisphere = "W"'
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 # straddles prime meridian
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_w = merge_conditions condition_lng_h, condition_lng_sw
133
+
134
+ condition_lng_h = 'longitude_hemisphere = "E"'
135
+ condition_lng_ne = ["(longitude_degrees < ?) OR (longitude_degrees = ? AND longitude_minutes <= ?)", ne_lng_degs, ne_lng_degs, ne_lng_mins]
136
+ condition_lng_e = merge_conditions condition_lng_h, condition_lng_ne
137
+
138
+ condition_lng = merge_or_conditions condition_lng_w, condition_lng_e
139
+ end
140
+
141
+ # Combined latitude and longitude conditions.
142
+ {:conditions => merge_conditions(condition_lat, condition_lng)}
143
+ }
144
+
145
+ private
146
+
147
+ # Merges conditions so that the result is a valid +condition+.
148
+ # Adapted from ActiveRecord::Base#merge_conditions.
149
+ def merge_or_conditions(*conditions)
150
+ segments = []
151
+
152
+ conditions.each do |condition|
153
+ unless condition.blank?
154
+ sql = sanitize_sql(condition)
155
+ segments << sql unless sql.blank?
156
+ end
157
+ end
158
+
159
+ "(#{segments.join(') OR (')})" unless segments.empty?
160
+ end
161
+
162
+ end # def acts_as_location
163
+ end # module ClassMethods
164
+
165
+
166
+ module InstanceMethodsOnActivation
167
+ def latitude_decimal_minutes=(value)
168
+ unless value.nil?
169
+ width = value.to_s.length
170
+ value = value.to_i
171
+
172
+ write_attribute :latitude_decimal_minutes, value
173
+ write_attribute :latitude_decimal_minutes_width, width
174
+ end
175
+ end
176
+
177
+ def latitude_decimal_minutes_as_string
178
+ "%0#{latitude_decimal_minutes_width}d" % (latitude_decimal_minutes || 0)
179
+ end
180
+
181
+ def longitude_decimal_minutes=(value)
182
+ unless value.nil?
183
+ width = value.to_s.length
184
+ value = value.to_i
185
+
186
+ write_attribute :longitude_decimal_minutes, value
187
+ write_attribute :longitude_decimal_minutes_width, width
188
+ end
189
+ end
190
+
191
+ def longitude_decimal_minutes_as_string
192
+ "%0#{longitude_decimal_minutes_width}d" % (longitude_decimal_minutes || 0)
193
+ end
194
+
195
+ def latitude
196
+ to_float latitude_degrees, latitude_minutes, latitude_decimal_minutes,
197
+ latitude_decimal_minutes_width, latitude_hemisphere
198
+ end
199
+
200
+ def longitude
201
+ to_float longitude_degrees, longitude_minutes, longitude_decimal_minutes,
202
+ longitude_decimal_minutes_width, longitude_hemisphere
203
+ end
204
+
205
+ def to_s
206
+ # Unicode degree symbol, full stop, Unicode minute symbol.
207
+ units = [ "\xc2\xb0", '.', "\xe2\x80\xb2" ]
208
+
209
+ lat_fields = ["%02d" % (latitude_degrees || 0),
210
+ "%02d" % (latitude_minutes || 0),
211
+ latitude_decimal_minutes_as_string.ljust(2, '0'),
212
+ latitude_hemisphere]
213
+ lat = lat_fields.zip(units).map{ |f| f.join }.join
214
+
215
+ long_fields = ["%02d" % (longitude_degrees || 0),
216
+ "%02d" % (longitude_minutes || 0),
217
+ longitude_decimal_minutes_as_string.ljust(2, '0'),
218
+ longitude_hemisphere]
219
+ long = long_fields.zip(units).map{ |f| f.join }.join
220
+
221
+ "#{lat}, #{long}"
222
+ end
223
+
224
+ private
225
+
226
+ def to_float(degrees, minutes, decimal_minutes, decimal_minutes_width, hemisphere)
227
+ return nil if degrees.nil? and minutes.nil? and decimal_minutes.nil?
228
+ degrees ||= 0
229
+ minutes ||= 0
230
+ decimal_minutes ||= 0
231
+
232
+ f = degrees.to_f
233
+ f = f + (minutes.to_f + decimal_minutes.to_f / 10 ** decimal_minutes_width) / 60.0
234
+ f = f * -1 if hemisphere == 'S' or hemisphere == 'W'
235
+ f
236
+ end
237
+
238
+ # If some of the fields are empty, set them to zero. This is to speed up data entry.
239
+ # If all the fields are empty, leave them empty.
240
+ def set_empty_values
241
+ unless latitude_degrees.blank? and latitude_minutes.blank? and latitude_decimal_minutes.blank?
242
+ self.latitude_degrees = 0 if latitude_degrees.blank?
243
+ self.latitude_minutes = 0 if latitude_minutes.blank?
244
+ self.latitude_decimal_minutes = 0 if latitude_decimal_minutes.blank?
245
+ end
246
+
247
+ unless longitude_degrees.blank? and longitude_minutes.blank? and longitude_decimal_minutes.blank?
248
+ self.longitude_degrees = 0 if longitude_degrees.blank?
249
+ self.longitude_minutes = 0 if longitude_minutes.blank?
250
+ self.longitude_decimal_minutes = 0 if longitude_decimal_minutes.blank?
251
+ end
252
+ end
253
+ end # InstanceMethodsOnActivation
254
+
255
+ end # module Location
256
+ end # module GeoTools
257
+
258
+
259
+ ActiveRecord::Base.send :include, GeoTools::Location