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.
- 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
|