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
data/.gitignore
ADDED
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p0
|
data/Gemfile
ADDED
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
42
|
+
xx <degree symbol> yy <decimal point> zz h
|
37
43
|
|
38
|
-
|
44
|
+
where:
|
39
45
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
55
|
+
xxx <degree symbol> yy <decimal point> zz h
|
50
56
|
|
51
|
-
|
57
|
+
where:
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
##
|
96
|
+
## Someday / Maybe
|
91
97
|
|
92
|
-
*
|
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 '
|
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'
|
data/geo_tools.gemspec
ADDED
@@ -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
|
data/lib/geo_tools.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
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 => '°' },
|
24
|
+
:minutes => { :symbol => '.' },
|
25
|
+
:decimal_minutes => { :symbol => '′', :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 => '°' },
|
63
|
+
:minutes => { :symbol => '.' },
|
64
|
+
:decimal_minutes => { :symbol => '′', :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
|