/
lat_lng.rb
165 lines (145 loc) · 5.15 KB
/
lat_lng.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
module Geokit
class LatLng
include Mappable
attr_accessor :lat, :lng
# Provide users with the ability to use :latitude and :longitude
# to access the lat/lng instance variables.
# Alias the attr_accessor :lat to :latitude
alias_method :latitude, :lat
alias_method :latitude=, :lat=
# Alias the attr_accessor :lng to :longitude
alias_method :longitude, :lng
alias_method :longitude=, :lng=
# Accepts latitude and longitude or instantiates an empty instance
# if lat and lng are not provided. Converted to floats if provided
def initialize(lat = nil, lng = nil)
lat = lat.to_f if lat && !lat.is_a?(Numeric)
lng = lng.to_f if lng && !lng.is_a?(Numeric)
@lat = lat
@lng = lng
end
def self.from_json(json)
new(json['lat'], json['lng'])
end
# Latitude attribute setter; stored as a float.
def lat=(lat)
@lat = lat.to_f if lat
end
# Longitude attribute setter; stored as a float;
def lng=(lng)
@lng = lng.to_f if lng
end
# Returns the lat and lng attributes as a comma-separated string.
def ll
"#{lat},#{lng}"
end
# returns latitude as [ degree, minute, second ] array
def lat_dms
self.class.decimal_to_dms(lat)
end
# returns longitude as [ degree, minute, second ] array
def lng_dms
self.class.decimal_to_dms(lng)
end
# returns a string with comma-separated lat,lng values
def to_s
ll
end
# returns a two-element array
def to_a
[lat, lng]
end
# Returns true if the candidate object is logically equal. Logical
# equivalence is true if the lat and lng attributes are the same for both
# objects.
def ==(other)
return false unless other.is_a?(LatLng)
lat == other.lat && lng == other.lng
end
def hash
lat.hash + lng.hash
end
def eql?(other)
self == other
end
# Returns true if both lat and lng attributes are defined
def valid?
lat && lng
end
# A *class* method to take anything which can be inferred as a point and
# generate a LatLng from it. You should use this anything you're not sure
# what the input is, and want to deal with it as a LatLng if at all
# possible. Can take:
# 1) two arguments (lat,lng)
# 2) a string in the format "37.1234,-129.1234" or "37.1234 -129.1234"
# 3) a string which can be geocoded on the fly
# 4) an array in the format [37.1234,-129.1234]
# 5) a LatLng or GeoLoc (which is just passed through as-is)
# 6) anything responding to to_lat_lng -- a LatLng will be extracted from
# it
def self.normalize(thing, other = nil)
return Geokit::LatLng.new(thing, other) if other
case thing
when String
from_string(thing)
when Array
thing.size == 2 or
raise(ArgumentError,
'Must initialize with an Array with both latitude and longitude')
Geokit::LatLng.new(thing[0], thing[1])
when LatLng # will also be true for GeoLocs
thing
else
if thing.respond_to? :to_lat_lng
thing.to_lat_lng
else
raise(ArgumentError,
"#{thing} (#{thing.class}) cannot be normalized to a LatLng. " \
'We tried interpreting it as an array, string, etc., but no dice.')
end
end
end
def self.from_string(thing)
thing = thing.strip
match = thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
if match
Geokit::LatLng.new(match[1], match[2])
else
res = Geokit::Geocoders::MultiGeocoder.geocode(thing)
return res if res.success?
raise Geokit::Geocoders::GeocodeError
end
end
# Reverse geocodes a LatLng object using the MultiGeocoder (default), or
# optionally using a geocoder of your choosing. Returns a new Geokit::GeoLoc
# object
#
# ==== Options
# * :using - Specifies the geocoder to use for reverse geocoding. Defaults
# to MultiGeocoder. Can be either the geocoder class (or any
# class that implements do_reverse_geocode for that matter), or
# the name of the class without the "Geocoder" part
# (e.g. :google)
#
# ==== Examples
# LatLng.new(51.4578329, 7.0166848).reverse_geocode
# => #<Geokit::GeoLoc:0x12dac20 @state...>
# LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using => :google)
# => #<Geokit::GeoLoc:0x12dac20 @state...>
# LatLng.new(51.4578329, 7.0166848).reverse_geocode(:using =>
# Geokit::Geocoders::GoogleGeocoder)
# => #<Geokit::GeoLoc:0x12dac20 @state...>
def reverse_geocode(options = {using: Geokit::Geocoders::MultiGeocoder})
if options[:using].is_a?(String) || options[:using].is_a?(Symbol)
class_name =
"#{Geokit::Inflector.camelize(options[:using].to_s)}Geocoder"
provider = Geokit::Geocoders.const_get(class_name)
elsif options[:using].respond_to?(:do_reverse_geocode)
provider = options[:using]
else
raise ArgumentError.new("#{options[:using]} is not a valid geocoder.")
end
provider.send(:reverse_geocode, self)
end
end
end