abuiles-geokit 1.6.1
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/.bundle/config +2 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +19 -0
- data/History.txt +77 -0
- data/Manifest.txt +21 -0
- data/README.markdown +273 -0
- data/Rakefile +13 -0
- data/geokit.gemspec +24 -0
- data/lib/geokit.rb +55 -0
- data/lib/geokit/bounds.rb +95 -0
- data/lib/geokit/geo_loc.rb +115 -0
- data/lib/geokit/geocoders.rb +68 -0
- data/lib/geokit/geocoders/ca_geocoder.rb +54 -0
- data/lib/geokit/geocoders/geo_plugin_geocoder.rb +30 -0
- data/lib/geokit/geocoders/geocode_error.rb +7 -0
- data/lib/geokit/geocoders/geocoder.rb +75 -0
- data/lib/geokit/geocoders/geonames_geocoder.rb +53 -0
- data/lib/geokit/geocoders/google_geocoder.rb +145 -0
- data/lib/geokit/geocoders/google_premier_geocoder.rb +147 -0
- data/lib/geokit/geocoders/ip_geocoder.rb +76 -0
- data/lib/geokit/geocoders/multi_geocoder.rb +60 -0
- data/lib/geokit/geocoders/us_geocoder.rb +50 -0
- data/lib/geokit/geocoders/yahoo_geocoder.rb +49 -0
- data/lib/geokit/inflector.rb +39 -0
- data/lib/geokit/lat_lng.rb +112 -0
- data/lib/geokit/mappable.rb +210 -0
- data/lib/geokit/too_many_queries_error.rb +4 -0
- data/lib/geokit/version.rb +3 -0
- data/test/test_base_geocoder.rb +58 -0
- data/test/test_bounds.rb +97 -0
- data/test/test_ca_geocoder.rb +39 -0
- data/test/test_geoloc.rb +72 -0
- data/test/test_geoplugin_geocoder.rb +58 -0
- data/test/test_google_geocoder.rb +225 -0
- data/test/test_google_premier_geocoder.rb +88 -0
- data/test/test_google_reverse_geocoder.rb +47 -0
- data/test/test_inflector.rb +24 -0
- data/test/test_ipgeocoder.rb +109 -0
- data/test/test_latlng.rb +209 -0
- data/test/test_multi_geocoder.rb +91 -0
- data/test/test_multi_ip_geocoder.rb +36 -0
- data/test/test_us_geocoder.rb +54 -0
- data/test/test_yahoo_geocoder.rb +103 -0
- metadata +141 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Geocoder Us geocoder implementation. Requires the Geokit::Geocoders::GEOCODER_US variable to
|
4
|
+
# contain true or false based upon whether authentication is to occur. Conforms to the
|
5
|
+
# interface set by the Geocoder class.
|
6
|
+
class UsGeocoder < Geocoder
|
7
|
+
|
8
|
+
private
|
9
|
+
def self.do_geocode(address, options = {})
|
10
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
11
|
+
|
12
|
+
query = (address_str =~ /^\d{5}(?:-\d{4})?$/ ? "zip" : "address") + "=#{Geokit::Inflector::url_escape(address_str)}"
|
13
|
+
url = if Geokit::Geocoders::geocoder_us
|
14
|
+
"http://#{Geokit::Geocoders::geocoder_us}@geocoder.us/member/service/csv/geocode"
|
15
|
+
else
|
16
|
+
"http://geocoder.us/service/csv/geocode"
|
17
|
+
end
|
18
|
+
|
19
|
+
url = "#{url}?#{query}"
|
20
|
+
res = self.call_geocoder_service(url)
|
21
|
+
|
22
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
23
|
+
data = res.body
|
24
|
+
logger.debug "Geocoder.us geocoding. Address: #{address}. Result: #{data}"
|
25
|
+
array = data.chomp.split(',')
|
26
|
+
|
27
|
+
if array.length == 5
|
28
|
+
res=GeoLoc.new
|
29
|
+
res.lat,res.lng,res.city,res.state,res.zip=array
|
30
|
+
res.country_code='US'
|
31
|
+
res.success=true
|
32
|
+
return res
|
33
|
+
elsif array.length == 6
|
34
|
+
res=GeoLoc.new
|
35
|
+
res.lat,res.lng,res.street_address,res.city,res.state,res.zip=array
|
36
|
+
res.country_code='US'
|
37
|
+
res.success=true
|
38
|
+
return res
|
39
|
+
else
|
40
|
+
logger.info "geocoder.us was unable to geocode address: "+address
|
41
|
+
return GeoLoc.new
|
42
|
+
end
|
43
|
+
rescue
|
44
|
+
logger.error "Caught an error during geocoder.us geocoding call: "+$!
|
45
|
+
return GeoLoc.new
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Geocoders
|
3
|
+
# Yahoo geocoder implementation. Requires the Geokit::Geocoders::YAHOO variable to
|
4
|
+
# contain a Yahoo API key. Conforms to the interface set by the Geocoder class.
|
5
|
+
class YahooGeocoder < Geocoder
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# Template method which does the geocode lookup.
|
10
|
+
def self.do_geocode(address, options = {})
|
11
|
+
address_str = address.is_a?(GeoLoc) ? address.to_geocodeable_s : address
|
12
|
+
url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=#{Geokit::Geocoders::yahoo}&location=#{Geokit::Inflector::url_escape(address_str)}"
|
13
|
+
res = self.call_geocoder_service(url)
|
14
|
+
return GeoLoc.new if !res.is_a?(Net::HTTPSuccess)
|
15
|
+
xml = res.body
|
16
|
+
doc = REXML::Document.new(xml)
|
17
|
+
logger.debug "Yahoo geocoding. Address: #{address}. Result: #{xml}"
|
18
|
+
|
19
|
+
if doc.elements['//ResultSet']
|
20
|
+
res=GeoLoc.new
|
21
|
+
|
22
|
+
#basic
|
23
|
+
res.lat=doc.elements['//Latitude'].text
|
24
|
+
res.lng=doc.elements['//Longitude'].text
|
25
|
+
res.country_code=doc.elements['//Country'].text
|
26
|
+
res.provider='yahoo'
|
27
|
+
|
28
|
+
#extended - false if not available
|
29
|
+
res.city=doc.elements['//City'].text if doc.elements['//City'] && doc.elements['//City'].text != nil
|
30
|
+
res.state=doc.elements['//State'].text if doc.elements['//State'] && doc.elements['//State'].text != nil
|
31
|
+
res.zip=doc.elements['//Zip'].text if doc.elements['//Zip'] && doc.elements['//Zip'].text != nil
|
32
|
+
res.street_address=doc.elements['//Address'].text if doc.elements['//Address'] && doc.elements['//Address'].text != nil
|
33
|
+
res.precision=doc.elements['//Result'].attributes['precision'] if doc.elements['//Result']
|
34
|
+
# set the accuracy as google does (added by Andruby)
|
35
|
+
res.accuracy=%w{unknown country state state city zip zip+4 street address building}.index(res.precision)
|
36
|
+
res.success=true
|
37
|
+
return res
|
38
|
+
else
|
39
|
+
logger.info "Yahoo was unable to geocode address: "+address
|
40
|
+
return GeoLoc.new
|
41
|
+
end
|
42
|
+
|
43
|
+
rescue
|
44
|
+
logger.info "Caught an error during Yahoo geocoding call: "+$!
|
45
|
+
return GeoLoc.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Geokit
|
2
|
+
module Inflector
|
3
|
+
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def titleize(word)
|
7
|
+
humanize(underscore(word)).gsub(/\b([a-z])/u) { $1.capitalize }
|
8
|
+
end
|
9
|
+
|
10
|
+
def underscore(camel_cased_word)
|
11
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
12
|
+
gsub(/([A-Z]+)([A-Z][a-z])/u,'\1_\2').
|
13
|
+
gsub(/([a-z\d])([A-Z])/u,'\1_\2').
|
14
|
+
tr("-", "_").
|
15
|
+
downcase
|
16
|
+
end
|
17
|
+
|
18
|
+
def humanize(lower_case_and_underscored_word)
|
19
|
+
lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
|
20
|
+
end
|
21
|
+
|
22
|
+
def snake_case(s)
|
23
|
+
return s.downcase if s =~ /^[A-Z]+$/u
|
24
|
+
s.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/u, '_\&') =~ /_*(.*)/
|
25
|
+
return $+.downcase
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def url_escape(s)
|
30
|
+
s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) do
|
31
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
32
|
+
end.tr(' ', '+')
|
33
|
+
end
|
34
|
+
|
35
|
+
def camelize(str)
|
36
|
+
str.split('_').map {|w| w.capitalize}.join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Geokit
|
2
|
+
class LatLng
|
3
|
+
include Mappable
|
4
|
+
|
5
|
+
attr_accessor :lat, :lng
|
6
|
+
|
7
|
+
# Accepts latitude and longitude or instantiates an empty instance
|
8
|
+
# if lat and lng are not provided. Converted to floats if provided
|
9
|
+
def initialize(lat=nil, lng=nil)
|
10
|
+
lat = lat.to_f if lat && !lat.is_a?(Numeric)
|
11
|
+
lng = lng.to_f if lng && !lng.is_a?(Numeric)
|
12
|
+
@lat = lat
|
13
|
+
@lng = lng
|
14
|
+
end
|
15
|
+
|
16
|
+
# Latitude attribute setter; stored as a float.
|
17
|
+
def lat=(lat)
|
18
|
+
@lat = lat.to_f if lat
|
19
|
+
end
|
20
|
+
|
21
|
+
# Longitude attribute setter; stored as a float;
|
22
|
+
def lng=(lng)
|
23
|
+
@lng=lng.to_f if lng
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the lat and lng attributes as a comma-separated string.
|
27
|
+
def ll
|
28
|
+
"#{lat},#{lng}"
|
29
|
+
end
|
30
|
+
|
31
|
+
#returns a string with comma-separated lat,lng values
|
32
|
+
def to_s
|
33
|
+
ll
|
34
|
+
end
|
35
|
+
|
36
|
+
#returns a two-element array
|
37
|
+
def to_a
|
38
|
+
[lat,lng]
|
39
|
+
end
|
40
|
+
# Returns true if the candidate object is logically equal. Logical equivalence
|
41
|
+
# is true if the lat and lng attributes are the same for both objects.
|
42
|
+
def ==(other)
|
43
|
+
other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
|
44
|
+
end
|
45
|
+
|
46
|
+
def hash
|
47
|
+
lat.hash + lng.hash
|
48
|
+
end
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
self == other
|
52
|
+
end
|
53
|
+
|
54
|
+
# A *class* method to take anything which can be inferred as a point and generate
|
55
|
+
# a LatLng from it. You should use this anything you're not sure what the input is,
|
56
|
+
# and want to deal with it as a LatLng if at all possible. Can take:
|
57
|
+
# 1) two arguments (lat,lng)
|
58
|
+
# 2) a string in the format "37.1234,-129.1234" or "37.1234 -129.1234"
|
59
|
+
# 3) a string which can be geocoded on the fly
|
60
|
+
# 4) an array in the format [37.1234,-129.1234]
|
61
|
+
# 5) a LatLng or GeoLoc (which is just passed through as-is)
|
62
|
+
# 6) anything which acts_as_mappable -- a LatLng will be extracted from it
|
63
|
+
def self.normalize(thing,other=nil)
|
64
|
+
# if an 'other' thing is supplied, normalize the input by creating an array of two elements
|
65
|
+
thing=[thing,other] if other
|
66
|
+
|
67
|
+
if thing.is_a?(String)
|
68
|
+
thing.strip!
|
69
|
+
if match=thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
|
70
|
+
return Geokit::LatLng.new(match[1],match[2])
|
71
|
+
else
|
72
|
+
res = Geokit::Geocoders::MultiGeocoder.geocode(thing)
|
73
|
+
return res if res.success?
|
74
|
+
raise Geokit::Geocoders::GeocodeError
|
75
|
+
end
|
76
|
+
elsif thing.is_a?(Array) && thing.size==2
|
77
|
+
return Geokit::LatLng.new(thing[0],thing[1])
|
78
|
+
elsif thing.is_a?(LatLng) # will also be true for GeoLocs
|
79
|
+
return thing
|
80
|
+
elsif thing.class.respond_to?(:acts_as_mappable) && thing.class.respond_to?(:distance_column_name)
|
81
|
+
return thing.to_lat_lng
|
82
|
+
end
|
83
|
+
|
84
|
+
raise ArgumentError.new("#{thing} (#{thing.class}) cannot be normalized to a LatLng. We tried interpreting it as an array, string, Mappable, etc., but no dice.")
|
85
|
+
end
|
86
|
+
|
87
|
+
# Reverse geocodes a LatLng object using the MultiGeocoder (default), or optionally
|
88
|
+
# using a geocoder of your choosing. Returns a new Geokit::GeoLoc object
|
89
|
+
#
|
90
|
+
# ==== Options
|
91
|
+
# * :using - Specifies the geocoder to use for reverse geocoding. Defaults to
|
92
|
+
# MultiGeocoder. Can be either the geocoder class (or any class that
|
93
|
+
# implements do_reverse_geocode for that matter), or the name of
|
94
|
+
# the class without the "Geocoder" part (e.g. :google)
|
95
|
+
#
|
96
|
+
# ==== Examples
|
97
|
+
# LatLng.new(51.4578329, 7.0166848).reverse_geocode # => #<Geokit::GeoLoc:0x12dac20 @state...>
|
98
|
+
# LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using => :google) # => #<Geokit::GeoLoc:0x12dac20 @state...>
|
99
|
+
# LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using => Geokit::Geocoders::GoogleGeocoder) # => #<Geokit::GeoLoc:0x12dac20 @state...>
|
100
|
+
def reverse_geocode(options = { :using => Geokit::Geocoders::MultiGeocoder })
|
101
|
+
if options[:using].is_a?(String) or options[:using].is_a?(Symbol)
|
102
|
+
provider = Geokit::Geocoders.const_get("#{Geokit::Inflector::camelize(options[:using].to_s)}Geocoder")
|
103
|
+
elsif options[:using].respond_to?(:do_reverse_geocode)
|
104
|
+
provider = options[:using]
|
105
|
+
else
|
106
|
+
raise ArgumentError.new("#{options[:using]} is not a valid geocoder.")
|
107
|
+
end
|
108
|
+
|
109
|
+
provider.send(:reverse_geocode, self)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
#require 'forwardable'
|
2
|
+
|
3
|
+
module Geokit
|
4
|
+
# Contains class and instance methods providing distance calcuation services. This
|
5
|
+
# module is meant to be mixed into classes containing lat and lng attributes where
|
6
|
+
# distance calculation is desired.
|
7
|
+
#
|
8
|
+
# At present, two forms of distance calculations are provided:
|
9
|
+
#
|
10
|
+
# * Pythagorean Theory (flat Earth) - which assumes the world is flat and loses accuracy over long distances.
|
11
|
+
# * Haversine (sphere) - which is fairly accurate, but at a performance cost.
|
12
|
+
#
|
13
|
+
# Distance units supported are :miles, :kms, and :nms.
|
14
|
+
module Mappable
|
15
|
+
PI_DIV_RAD = 0.0174
|
16
|
+
KMS_PER_MILE = 1.609
|
17
|
+
NMS_PER_MILE = 0.868976242
|
18
|
+
EARTH_RADIUS_IN_MILES = 3963.19
|
19
|
+
EARTH_RADIUS_IN_KMS = EARTH_RADIUS_IN_MILES * KMS_PER_MILE
|
20
|
+
EARTH_RADIUS_IN_NMS = EARTH_RADIUS_IN_MILES * NMS_PER_MILE
|
21
|
+
MILES_PER_LATITUDE_DEGREE = 69.1
|
22
|
+
KMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * KMS_PER_MILE
|
23
|
+
NMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * NMS_PER_MILE
|
24
|
+
LATITUDE_DEGREES = EARTH_RADIUS_IN_MILES / MILES_PER_LATITUDE_DEGREE
|
25
|
+
|
26
|
+
# Mix below class methods into the includer.
|
27
|
+
def self.included(receiver) # :nodoc:
|
28
|
+
receiver.extend ClassMethods
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods #:nodoc:
|
32
|
+
# Returns the distance between two points. The from and to parameters are
|
33
|
+
# required to have lat and lng attributes. Valid options are:
|
34
|
+
# :units - valid values are :miles, :kms, :nms (Geokit::default_units is the default)
|
35
|
+
# :formula - valid values are :flat or :sphere (Geokit::default_formula is the default)
|
36
|
+
def distance_between(from, to, options={})
|
37
|
+
from=Geokit::LatLng.normalize(from)
|
38
|
+
to=Geokit::LatLng.normalize(to)
|
39
|
+
return 0.0 if from == to # fixes a "zero-distance" bug
|
40
|
+
units = options[:units] || Geokit::default_units
|
41
|
+
formula = options[:formula] || Geokit::default_formula
|
42
|
+
case formula
|
43
|
+
when :sphere
|
44
|
+
begin
|
45
|
+
units_sphere_multiplier(units) *
|
46
|
+
Math.acos( Math.sin(deg2rad(from.lat)) * Math.sin(deg2rad(to.lat)) +
|
47
|
+
Math.cos(deg2rad(from.lat)) * Math.cos(deg2rad(to.lat)) *
|
48
|
+
Math.cos(deg2rad(to.lng) - deg2rad(from.lng)))
|
49
|
+
rescue Errno::EDOM
|
50
|
+
0.0
|
51
|
+
end
|
52
|
+
when :flat
|
53
|
+
Math.sqrt((units_per_latitude_degree(units)*(from.lat-to.lat))**2 +
|
54
|
+
(units_per_longitude_degree(from.lat, units)*(from.lng-to.lng))**2)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
|
59
|
+
# from the first point to the second point. Typicaly, the instance methods will be used
|
60
|
+
# instead of this method.
|
61
|
+
def heading_between(from,to)
|
62
|
+
from=Geokit::LatLng.normalize(from)
|
63
|
+
to=Geokit::LatLng.normalize(to)
|
64
|
+
|
65
|
+
d_lng=deg2rad(to.lng-from.lng)
|
66
|
+
from_lat=deg2rad(from.lat)
|
67
|
+
to_lat=deg2rad(to.lat)
|
68
|
+
y=Math.sin(d_lng) * Math.cos(to_lat)
|
69
|
+
x=Math.cos(from_lat)*Math.sin(to_lat)-Math.sin(from_lat)*Math.cos(to_lat)*Math.cos(d_lng)
|
70
|
+
heading=to_heading(Math.atan2(y,x))
|
71
|
+
end
|
72
|
+
|
73
|
+
# Given a start point, distance, and heading (in degrees), provides
|
74
|
+
# an endpoint. Returns a LatLng instance. Typically, the instance method
|
75
|
+
# will be used instead of this method.
|
76
|
+
def endpoint(start,heading, distance, options={})
|
77
|
+
units = options[:units] || Geokit::default_units
|
78
|
+
radius = case units
|
79
|
+
when :kms; EARTH_RADIUS_IN_KMS
|
80
|
+
when :nms; EARTH_RADIUS_IN_NMS
|
81
|
+
else EARTH_RADIUS_IN_MILES
|
82
|
+
end
|
83
|
+
start=Geokit::LatLng.normalize(start)
|
84
|
+
lat=deg2rad(start.lat)
|
85
|
+
lng=deg2rad(start.lng)
|
86
|
+
heading=deg2rad(heading)
|
87
|
+
distance=distance.to_f
|
88
|
+
|
89
|
+
end_lat=Math.asin(Math.sin(lat)*Math.cos(distance/radius) +
|
90
|
+
Math.cos(lat)*Math.sin(distance/radius)*Math.cos(heading))
|
91
|
+
|
92
|
+
end_lng=lng+Math.atan2(Math.sin(heading)*Math.sin(distance/radius)*Math.cos(lat),
|
93
|
+
Math.cos(distance/radius)-Math.sin(lat)*Math.sin(end_lat))
|
94
|
+
|
95
|
+
LatLng.new(rad2deg(end_lat),rad2deg(end_lng))
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the midpoint, given two points. Returns a LatLng.
|
99
|
+
# Typically, the instance method will be used instead of this method.
|
100
|
+
# Valid option:
|
101
|
+
# :units - valid values are :miles, :kms, or :nms (:miles is the default)
|
102
|
+
def midpoint_between(from,to,options={})
|
103
|
+
from=Geokit::LatLng.normalize(from)
|
104
|
+
|
105
|
+
units = options[:units] || Geokit::default_units
|
106
|
+
|
107
|
+
heading=from.heading_to(to)
|
108
|
+
distance=from.distance_to(to,options)
|
109
|
+
midpoint=from.endpoint(heading,distance/2,options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Geocodes a location using the multi geocoder.
|
113
|
+
def geocode(location, options = {})
|
114
|
+
res = Geocoders::MultiGeocoder.geocode(location, options)
|
115
|
+
return res if res.success?
|
116
|
+
raise Geokit::Geocoders::GeocodeError
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
|
121
|
+
def deg2rad(degrees)
|
122
|
+
degrees.to_f / 180.0 * Math::PI
|
123
|
+
end
|
124
|
+
|
125
|
+
def rad2deg(rad)
|
126
|
+
rad.to_f * 180.0 / Math::PI
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_heading(rad)
|
130
|
+
(rad2deg(rad)+360)%360
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the multiplier used to obtain the correct distance units.
|
134
|
+
def units_sphere_multiplier(units)
|
135
|
+
case units
|
136
|
+
when :kms; EARTH_RADIUS_IN_KMS
|
137
|
+
when :nms; EARTH_RADIUS_IN_NMS
|
138
|
+
else EARTH_RADIUS_IN_MILES
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the number of units per latitude degree.
|
143
|
+
def units_per_latitude_degree(units)
|
144
|
+
case units
|
145
|
+
when :kms; KMS_PER_LATITUDE_DEGREE
|
146
|
+
when :nms; NMS_PER_LATITUDE_DEGREE
|
147
|
+
else MILES_PER_LATITUDE_DEGREE
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the number units per longitude degree.
|
152
|
+
def units_per_longitude_degree(lat, units)
|
153
|
+
miles_per_longitude_degree = (LATITUDE_DEGREES * Math.cos(lat * PI_DIV_RAD)).abs
|
154
|
+
case units
|
155
|
+
when :kms; miles_per_longitude_degree * KMS_PER_MILE
|
156
|
+
when :nms; miles_per_longitude_degree * NMS_PER_MILE
|
157
|
+
else miles_per_longitude_degree
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# -----------------------------------------------------------------------------------------------
|
163
|
+
# Instance methods below here
|
164
|
+
# -----------------------------------------------------------------------------------------------
|
165
|
+
|
166
|
+
# Extracts a LatLng instance. Use with models that are acts_as_mappable
|
167
|
+
def to_lat_lng
|
168
|
+
return self if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
|
169
|
+
return LatLng.new(send(self.class.lat_column_name),send(self.class.lng_column_name)) if self.class.respond_to?(:acts_as_mappable)
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns the distance from another point. The other point parameter is
|
174
|
+
# required to have lat and lng attributes. Valid options are:
|
175
|
+
# :units - valid values are :miles, :kms, :or :nms (:miles is the default)
|
176
|
+
# :formula - valid values are :flat or :sphere (:sphere is the default)
|
177
|
+
def distance_to(other, options={})
|
178
|
+
self.class.distance_between(self, other, options)
|
179
|
+
end
|
180
|
+
alias distance_from distance_to
|
181
|
+
|
182
|
+
# Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
|
183
|
+
# to the given point. The given point can be a LatLng or a string to be Geocoded
|
184
|
+
def heading_to(other)
|
185
|
+
self.class.heading_between(self,other)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
|
189
|
+
# FROM the given point. The given point can be a LatLng or a string to be Geocoded
|
190
|
+
def heading_from(other)
|
191
|
+
self.class.heading_between(other,self)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns the endpoint, given a heading (in degrees) and distance.
|
195
|
+
# Valid option:
|
196
|
+
# :units - valid values are :miles, :kms, or :nms (:miles is the default)
|
197
|
+
def endpoint(heading,distance,options={})
|
198
|
+
self.class.endpoint(self,heading,distance,options)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns the midpoint, given another point on the map.
|
202
|
+
# Valid option:
|
203
|
+
# :units - valid values are :miles, :kms, or :nms (:miles is the default)
|
204
|
+
def midpoint_to(other, options={})
|
205
|
+
self.class.midpoint_between(self,other,options)
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|