geokit 1.6.0 → 1.6.5

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.
@@ -1,15 +1,15 @@
1
1
  #require 'forwardable'
2
2
 
3
- module Geokit
3
+ module Geokit
4
4
  # Contains class and instance methods providing distance calcuation services. This
5
5
  # module is meant to be mixed into classes containing lat and lng attributes where
6
- # distance calculation is desired.
7
- #
6
+ # distance calculation is desired.
7
+ #
8
8
  # At present, two forms of distance calculations are provided:
9
- #
9
+ #
10
10
  # * Pythagorean Theory (flat Earth) - which assumes the world is flat and loses accuracy over long distances.
11
11
  # * Haversine (sphere) - which is fairly accurate, but at a performance cost.
12
- #
12
+ #
13
13
  # Distance units supported are :miles, :kms, and :nms.
14
14
  module Mappable
15
15
  PI_DIV_RAD = 0.0174
@@ -21,13 +21,13 @@ module Geokit
21
21
  MILES_PER_LATITUDE_DEGREE = 69.1
22
22
  KMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * KMS_PER_MILE
23
23
  NMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * NMS_PER_MILE
24
- LATITUDE_DEGREES = EARTH_RADIUS_IN_MILES / MILES_PER_LATITUDE_DEGREE
25
-
24
+ LATITUDE_DEGREES = EARTH_RADIUS_IN_MILES / MILES_PER_LATITUDE_DEGREE
25
+
26
26
  # Mix below class methods into the includer.
27
27
  def self.included(receiver) # :nodoc:
28
28
  receiver.extend ClassMethods
29
- end
30
-
29
+ end
30
+
31
31
  module ClassMethods #:nodoc:
32
32
  # Returns the distance between two points. The from and to parameters are
33
33
  # required to have lat and lng attributes. Valid options are:
@@ -42,21 +42,21 @@ module Geokit
42
42
  case formula
43
43
  when :sphere
44
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)) *
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
48
  Math.cos(deg2rad(to.lng) - deg2rad(from.lng)))
49
49
  rescue Errno::EDOM
50
50
  0.0
51
51
  end
52
52
  when :flat
53
- Math.sqrt((units_per_latitude_degree(units)*(from.lat-to.lat))**2 +
53
+ Math.sqrt((units_per_latitude_degree(units)*(from.lat-to.lat))**2 +
54
54
  (units_per_longitude_degree(from.lat, units)*(from.lng-to.lng))**2)
55
55
  end
56
56
  end
57
57
 
58
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
59
+ # from the first point to the second point. Typicaly, the instance methods will be used
60
60
  # instead of this method.
61
61
  def heading_between(from,to)
62
62
  from=Geokit::LatLng.normalize(from)
@@ -64,12 +64,12 @@ module Geokit
64
64
 
65
65
  d_lng=deg2rad(to.lng-from.lng)
66
66
  from_lat=deg2rad(from.lat)
67
- to_lat=deg2rad(to.lat)
67
+ to_lat=deg2rad(to.lat)
68
68
  y=Math.sin(d_lng) * Math.cos(to_lat)
69
69
  x=Math.cos(from_lat)*Math.sin(to_lat)-Math.sin(from_lat)*Math.cos(to_lat)*Math.cos(d_lng)
70
70
  heading=to_heading(Math.atan2(y,x))
71
71
  end
72
-
72
+
73
73
  # Given a start point, distance, and heading (in degrees), provides
74
74
  # an endpoint. Returns a LatLng instance. Typically, the instance method
75
75
  # will be used instead of this method.
@@ -80,12 +80,12 @@ module Geokit
80
80
  when :nms; EARTH_RADIUS_IN_NMS
81
81
  else EARTH_RADIUS_IN_MILES
82
82
  end
83
- start=Geokit::LatLng.normalize(start)
83
+ start=Geokit::LatLng.normalize(start)
84
84
  lat=deg2rad(start.lat)
85
85
  lng=deg2rad(start.lng)
86
86
  heading=deg2rad(heading)
87
87
  distance=distance.to_f
88
-
88
+
89
89
  end_lat=Math.asin(Math.sin(lat)*Math.cos(distance/radius) +
90
90
  Math.cos(lat)*Math.sin(distance/radius)*Math.cos(heading))
91
91
 
@@ -95,7 +95,7 @@ module Geokit
95
95
  LatLng.new(rad2deg(end_lat),rad2deg(end_lng))
96
96
  end
97
97
 
98
- # Returns the midpoint, given two points. Returns a LatLng.
98
+ # Returns the midpoint, given two points. Returns a LatLng.
99
99
  # Typically, the instance method will be used instead of this method.
100
100
  # Valid option:
101
101
  # :units - valid values are :miles, :kms, or :nms (:miles is the default)
@@ -103,29 +103,29 @@ module Geokit
103
103
  from=Geokit::LatLng.normalize(from)
104
104
 
105
105
  units = options[:units] || Geokit::default_units
106
-
106
+
107
107
  heading=from.heading_to(to)
108
108
  distance=from.distance_to(to,options)
109
109
  midpoint=from.endpoint(heading,distance/2,options)
110
110
  end
111
-
111
+
112
112
  # Geocodes a location using the multi geocoder.
113
113
  def geocode(location, options = {})
114
114
  res = Geocoders::MultiGeocoder.geocode(location, options)
115
115
  return res if res.success?
116
- raise Geokit::Geocoders::GeocodeError
116
+ raise Geokit::Geocoders::GeocodeError
117
117
  end
118
-
118
+
119
119
  protected
120
-
120
+
121
121
  def deg2rad(degrees)
122
122
  degrees.to_f / 180.0 * Math::PI
123
123
  end
124
-
124
+
125
125
  def rad2deg(rad)
126
- rad.to_f * 180.0 / Math::PI
126
+ rad.to_f * 180.0 / Math::PI
127
127
  end
128
-
128
+
129
129
  def to_heading(rad)
130
130
  (rad2deg(rad)+360)%360
131
131
  end
@@ -147,7 +147,7 @@ module Geokit
147
147
  else MILES_PER_LATITUDE_DEGREE
148
148
  end
149
149
  end
150
-
150
+
151
151
  # Returns the number units per longitude degree.
152
152
  def units_per_longitude_degree(lat, units)
153
153
  miles_per_longitude_degree = (LATITUDE_DEGREES * Math.cos(lat * PI_DIV_RAD)).abs
@@ -156,13 +156,13 @@ module Geokit
156
156
  when :nms; miles_per_longitude_degree * NMS_PER_MILE
157
157
  else miles_per_longitude_degree
158
158
  end
159
- end
159
+ end
160
160
  end
161
-
161
+
162
162
  # -----------------------------------------------------------------------------------------------
163
163
  # Instance methods below here
164
164
  # -----------------------------------------------------------------------------------------------
165
-
165
+
166
166
  # Extracts a LatLng instance. Use with models that are acts_as_mappable
167
167
  def to_lat_lng
168
168
  return self if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
@@ -176,38 +176,38 @@ module Geokit
176
176
  # :formula - valid values are :flat or :sphere (:sphere is the default)
177
177
  def distance_to(other, options={})
178
178
  self.class.distance_between(self, other, options)
179
- end
179
+ end
180
180
  alias distance_from distance_to
181
181
 
182
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
183
+ # to the given point. The given point can be a LatLng or a string to be Geocoded
184
184
  def heading_to(other)
185
185
  self.class.heading_between(self,other)
186
186
  end
187
187
 
188
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
189
+ # FROM the given point. The given point can be a LatLng or a string to be Geocoded
190
190
  def heading_from(other)
191
191
  self.class.heading_between(other,self)
192
192
  end
193
-
194
- # Returns the endpoint, given a heading (in degrees) and distance.
193
+
194
+ # Returns the endpoint, given a heading (in degrees) and distance.
195
195
  # Valid option:
196
196
  # :units - valid values are :miles, :kms, or :nms (:miles is the default)
197
197
  def endpoint(heading,distance,options={})
198
- self.class.endpoint(self,heading,distance,options)
198
+ self.class.endpoint(self,heading,distance,options)
199
199
  end
200
200
 
201
- # Returns the midpoint, given another point on the map.
201
+ # Returns the midpoint, given another point on the map.
202
202
  # Valid option:
203
- # :units - valid values are :miles, :kms, or :nms (:miles is the default)
203
+ # :units - valid values are :miles, :kms, or :nms (:miles is the default)
204
204
  def midpoint_to(other, options={})
205
205
  self.class.midpoint_between(self,other,options)
206
206
  end
207
-
207
+
208
208
  end
209
209
 
210
- class LatLng
210
+ class LatLng
211
211
  include Mappable
212
212
 
213
213
  attr_accessor :lat, :lng
@@ -219,7 +219,7 @@ module Geokit
219
219
  lng = lng.to_f if lng && !lng.is_a?(Numeric)
220
220
  @lat = lat
221
221
  @lng = lng
222
- end
222
+ end
223
223
 
224
224
  # Latitude attribute setter; stored as a float.
225
225
  def lat=(lat)
@@ -229,18 +229,18 @@ module Geokit
229
229
  # Longitude attribute setter; stored as a float;
230
230
  def lng=(lng)
231
231
  @lng=lng.to_f if lng
232
- end
232
+ end
233
233
 
234
234
  # Returns the lat and lng attributes as a comma-separated string.
235
235
  def ll
236
236
  "#{lat},#{lng}"
237
237
  end
238
-
238
+
239
239
  #returns a string with comma-separated lat,lng values
240
240
  def to_s
241
241
  ll
242
242
  end
243
-
243
+
244
244
  #returns a two-element array
245
245
  def to_a
246
246
  [lat,lng]
@@ -250,15 +250,20 @@ module Geokit
250
250
  def ==(other)
251
251
  other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
252
252
  end
253
-
253
+
254
254
  def hash
255
255
  lat.hash + lng.hash
256
256
  end
257
-
257
+
258
258
  def eql?(other)
259
259
  self == other
260
260
  end
261
-
261
+
262
+ # Returns true if both lat and lng attributes are defined
263
+ def valid?
264
+ self.lat and self.lng
265
+ end
266
+
262
267
  # A *class* method to take anything which can be inferred as a point and generate
263
268
  # a LatLng from it. You should use this anything you're not sure what the input is,
264
269
  # and want to deal with it as a LatLng if at all possible. Can take:
@@ -271,7 +276,7 @@ module Geokit
271
276
  def self.normalize(thing,other=nil)
272
277
  # if an 'other' thing is supplied, normalize the input by creating an array of two elements
273
278
  thing=[thing,other] if other
274
-
279
+
275
280
  if thing.is_a?(String)
276
281
  thing.strip!
277
282
  if match=thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
@@ -279,7 +284,7 @@ module Geokit
279
284
  else
280
285
  res = Geokit::Geocoders::MultiGeocoder.geocode(thing)
281
286
  return res if res.success?
282
- raise Geokit::Geocoders::GeocodeError
287
+ raise Geokit::Geocoders::GeocodeError
283
288
  end
284
289
  elsif thing.is_a?(Array) && thing.size==2
285
290
  return Geokit::LatLng.new(thing[0],thing[1])
@@ -290,16 +295,16 @@ module Geokit
290
295
  elsif thing.respond_to? :to_lat_lng
291
296
  return thing.to_lat_lng
292
297
  end
293
-
298
+
294
299
  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.")
295
300
  end
296
-
301
+
297
302
  # Reverse geocodes a LatLng object using the MultiGeocoder (default), or optionally
298
303
  # using a geocoder of your choosing. Returns a new Geokit::GeoLoc object
299
- #
304
+ #
300
305
  # ==== Options
301
306
  # * :using - Specifies the geocoder to use for reverse geocoding. Defaults to
302
- # MultiGeocoder. Can be either the geocoder class (or any class that
307
+ # MultiGeocoder. Can be either the geocoder class (or any class that
303
308
  # implements do_reverse_geocode for that matter), or the name of
304
309
  # the class without the "Geocoder" part (e.g. :google)
305
310
  #
@@ -315,19 +320,19 @@ module Geokit
315
320
  else
316
321
  raise ArgumentError.new("#{options[:using]} is not a valid geocoder.")
317
322
  end
318
-
323
+
319
324
  provider.send(:reverse_geocode, self)
320
325
  end
321
326
  end
322
327
 
323
328
  # This class encapsulates the result of a geocoding call.
324
329
  # It's primary purpose is to homogenize the results of multiple
325
- # geocoding providers. It also provides some additional functionality, such as
326
- # the "full address" method for geocoders that do not provide a
330
+ # geocoding providers. It also provides some additional functionality, such as
331
+ # the "full address" method for geocoders that do not provide a
327
332
  # full address in their results (for example, Yahoo), and the "is_us" method.
328
333
  #
329
334
  # Some geocoders can return multple results. Geoloc can capture multiple results through
330
- # its "all" method.
335
+ # its "all" method.
331
336
  #
332
337
  # For the geocoder setting the results, it would look something like this:
333
338
  # geo=GeoLoc.new(first_result)
@@ -345,28 +350,30 @@ module Geokit
345
350
  # Location attributes. Full address is a concatenation of all values. For example:
346
351
  # 100 Spear St, San Francisco, CA, 94101, US
347
352
  # Street number and street name are extracted from the street address attribute if they don't exist
348
- attr_accessor :street_number,:street_name,:street_address, :city, :state, :zip, :country_code, :country, :full_address, :all, :district, :province
353
+ attr_accessor :street_number, :street_name, :street_address, :city, :state, :zip, :country_code, :country
354
+ attr_accessor :full_address, :all, :district, :province, :sub_premise
349
355
  # Attributes set upon return from geocoding. Success will be true for successful
350
356
  # geocode lookups. The provider will be set to the name of the providing geocoder.
351
357
  # Finally, precision is an indicator of the accuracy of the geocoding.
352
358
  attr_accessor :success, :provider, :precision, :suggested_bounds
353
- # accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
359
+ # accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
354
360
  # precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
355
361
  attr_accessor :accuracy
356
362
  # FCC Attributes
357
363
  attr_accessor :district_fips, :state_fips, :block_fips
358
-
364
+
359
365
 
360
366
  # Constructor expects a hash of symbols to correspond with attributes.
361
367
  def initialize(h={})
362
368
  @all = [self]
363
-
364
- @street_address=h[:street_address]
369
+
370
+ @street_address=h[:street_address]
371
+ @sub_premise=nil
365
372
  @street_number=nil
366
373
  @street_name=nil
367
- @city=h[:city]
368
- @state=h[:state]
369
- @zip=h[:zip]
374
+ @city=h[:city]
375
+ @state=h[:state]
376
+ @zip=h[:zip]
370
377
  @country_code=h[:country_code]
371
378
  @province = h[:province]
372
379
  @success=false
@@ -379,7 +386,7 @@ module Geokit
379
386
  def is_us?
380
387
  country_code == 'US'
381
388
  end
382
-
389
+
383
390
  def success?
384
391
  success == true
385
392
  end
@@ -406,7 +413,9 @@ module Geokit
406
413
  # gives you all the important fields as key-value pairs
407
414
  def hash
408
415
  res={}
409
- [:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:province,:district,:provider,:full_address,:is_us?,:ll,:precision,:district_fips,:state_fips,:block_fips].each { |s| res[s] = self.send(s.to_s) }
416
+ [:success, :lat, :lng, :country_code, :city, :state, :zip, :street_address, :province,
417
+ :district, :provider, :full_address, :is_us?, :ll, :precision, :district_fips, :state_fips,
418
+ :block_fips, :sub_premise].each { |s| res[s] = self.send(s.to_s) }
410
419
  res
411
420
  end
412
421
  alias to_hash hash
@@ -419,20 +428,20 @@ module Geokit
419
428
  # Sets the street address after capitalizing each word within the street address.
420
429
  def street_address=(address)
421
430
  if address and not ['google','google3'].include?(self.provider)
422
- @street_address = Geokit::Inflector::titleize(address)
431
+ @street_address = Geokit::Inflector::titleize(address)
423
432
  else
424
433
  @street_address = address
425
434
  end
426
- end
427
-
435
+ end
436
+
428
437
  # Returns a comma-delimited string consisting of the street address, city, state,
429
438
  # zip, and country code. Only includes those attributes that are non-blank.
430
439
  def to_geocodeable_s
431
440
  a=[street_address, district, city, province, state, zip, country_code].compact
432
441
  a.delete_if { |e| !e || e == '' }
433
- a.join(', ')
442
+ a.join(', ')
434
443
  end
435
-
444
+
436
445
  def to_yaml_properties
437
446
  (instance_variables - ['@all']).sort
438
447
  end
@@ -442,33 +451,33 @@ module Geokit
442
451
  "Provider: #{provider}\nStreet: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
443
452
  end
444
453
  end
445
-
454
+
446
455
  # Bounds represents a rectangular bounds, defined by the SW and NE corners
447
456
  class Bounds
448
457
  # sw and ne are LatLng objects
449
458
  attr_accessor :sw, :ne
450
-
459
+
451
460
  # provide sw and ne to instantiate a new Bounds instance
452
461
  def initialize(sw,ne)
453
462
  raise ArgumentError if !(sw.is_a?(Geokit::LatLng) && ne.is_a?(Geokit::LatLng))
454
463
  @sw,@ne=sw,ne
455
464
  end
456
-
465
+
457
466
  #returns the a single point which is the center of the rectangular bounds
458
467
  def center
459
468
  @sw.midpoint_to(@ne)
460
469
  end
461
-
470
+
462
471
  # a simple string representation:sw,ne
463
472
  def to_s
464
- "#{@sw.to_s},#{@ne.to_s}"
473
+ "#{@sw.to_s},#{@ne.to_s}"
465
474
  end
466
-
475
+
467
476
  # a two-element array of two-element arrays: sw,ne
468
477
  def to_a
469
478
  [@sw.to_a, @ne.to_a]
470
479
  end
471
-
480
+
472
481
  # Returns true if the bounds contain the passed point.
473
482
  # allows for bounds which cross the meridian
474
483
  def contains?(point)
@@ -481,10 +490,10 @@ module Geokit
481
490
  end
482
491
  res
483
492
  end
484
-
493
+
485
494
  # returns true if the bounds crosses the international dateline
486
495
  def crosses_meridian?
487
- @sw.lng > @ne.lng
496
+ @sw.lng > @ne.lng
488
497
  end
489
498
 
490
499
  # Returns true if the candidate object is logically equal. Logical equivalence
@@ -492,7 +501,7 @@ module Geokit
492
501
  def ==(other)
493
502
  other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
494
503
  end
495
-
504
+
496
505
  # Equivalent to Google Maps API's .toSpan() method on GLatLng's.
497
506
  #
498
507
  # Returns a LatLng object, whose coordinates represent the size of a rectangle
@@ -502,9 +511,9 @@ module Geokit
502
511
  lng_span = (crosses_meridian? ? 360 + @ne.lng - @sw.lng : @ne.lng - @sw.lng).abs
503
512
  Geokit::LatLng.new(lat_span, lng_span)
504
513
  end
505
-
514
+
506
515
  class <<self
507
-
516
+
508
517
  # returns an instance of bounds which completely encompases the given circle
509
518
  def from_point_and_radius(point,radius,options={})
510
519
  point=LatLng.normalize(point)
@@ -516,18 +525,18 @@ module Geokit
516
525
  ne=Geokit::LatLng.new(p0.lat,p90.lng)
517
526
  Geokit::Bounds.new(sw,ne)
518
527
  end
519
-
528
+
520
529
  # Takes two main combinations of arguments to create a bounds:
521
530
  # point,point (this is the only one which takes two arguments
522
531
  # [point,point]
523
532
  # . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
524
533
  #
525
534
  # NOTE: everything combination is assumed to pass points in the order sw, ne
526
- def normalize (thing,other=nil)
535
+ def normalize (thing,other=nil)
527
536
  # maybe this will be simple -- an actual bounds object is passed, and we can all go home
528
537
  return thing if thing.is_a? Bounds
529
-
530
- # no? OK, if there's no "other," the thing better be a two-element array
538
+
539
+ # no? OK, if there's no "other," the thing better be a two-element array
531
540
  thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
532
541
 
533
542
  # Now that we're set with a thing and another thing, let LatLng do the heavy lifting.