geokit 1.6.0 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.