dreamcat4-geokit 1.3.0

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.
@@ -0,0 +1,493 @@
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)
114
+ res = Geocoders::MultiGeocoder.geocode(location)
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
+ class LatLng
211
+ include Mappable
212
+
213
+ attr_accessor :lat, :lng
214
+
215
+ # Accepts latitude and longitude or instantiates an empty instance
216
+ # if lat and lng are not provided. Converted to floats if provided
217
+ def initialize(lat=nil, lng=nil)
218
+ lat = lat.to_f if lat && !lat.is_a?(Numeric)
219
+ lng = lng.to_f if lng && !lng.is_a?(Numeric)
220
+ @lat = lat
221
+ @lng = lng
222
+ end
223
+
224
+ # Latitude attribute setter; stored as a float.
225
+ def lat=(lat)
226
+ @lat = lat.to_f if lat
227
+ end
228
+
229
+ # Longitude attribute setter; stored as a float;
230
+ def lng=(lng)
231
+ @lng=lng.to_f if lng
232
+ end
233
+
234
+ # Returns the lat and lng attributes as a comma-separated string.
235
+ def ll
236
+ "#{lat},#{lng}"
237
+ end
238
+
239
+ #returns a string with comma-separated lat,lng values
240
+ def to_s
241
+ ll
242
+ end
243
+
244
+ #returns a two-element array
245
+ def to_a
246
+ [lat,lng]
247
+ end
248
+ # Returns true if the candidate object is logically equal. Logical equivalence
249
+ # is true if the lat and lng attributes are the same for both objects.
250
+ def ==(other)
251
+ other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
252
+ end
253
+
254
+ def hash
255
+ lat.hash + lng.hash
256
+ end
257
+
258
+ def eql?(other)
259
+ self == other
260
+ end
261
+
262
+ # A *class* method to take anything which can be inferred as a point and generate
263
+ # a LatLng from it. You should use this anything you're not sure what the input is,
264
+ # and want to deal with it as a LatLng if at all possible. Can take:
265
+ # 1) two arguments (lat,lng)
266
+ # 2) a string in the format "37.1234,-129.1234" or "37.1234 -129.1234"
267
+ # 3) a string which can be geocoded on the fly
268
+ # 4) an array in the format [37.1234,-129.1234]
269
+ # 5) a LatLng or GeoLoc (which is just passed through as-is)
270
+ # 6) anything which acts_as_mappable -- a LatLng will be extracted from it
271
+ def self.normalize(thing,other=nil)
272
+ # if an 'other' thing is supplied, normalize the input by creating an array of two elements
273
+ thing=[thing,other] if other
274
+
275
+ if thing.is_a?(String)
276
+ thing.strip!
277
+ if match=thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
278
+ return Geokit::LatLng.new(match[1],match[2])
279
+ else
280
+ res = Geokit::Geocoders::MultiGeocoder.geocode(thing)
281
+ return res if res.success?
282
+ raise Geokit::Geocoders::GeocodeError
283
+ end
284
+ elsif thing.is_a?(Array) && thing.size==2
285
+ return Geokit::LatLng.new(thing[0],thing[1])
286
+ elsif thing.is_a?(LatLng) # will also be true for GeoLocs
287
+ return thing
288
+ elsif thing.class.respond_to?(:acts_as_mappable) && thing.class.respond_to?(:distance_column_name)
289
+ return thing.to_lat_lng
290
+ end
291
+
292
+ 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.")
293
+ end
294
+
295
+ end
296
+
297
+ # This class encapsulates the result of a geocoding call.
298
+ # It's primary purpose is to homogenize the results of multiple
299
+ # geocoding providers. It also provides some additional functionality, such as
300
+ # the "full address" method for geocoders that do not provide a
301
+ # full address in their results (for example, Yahoo), and the "is_us" method.
302
+ #
303
+ # Some geocoders can return multple results. Geoloc can capture multiple results through
304
+ # its "all" method.
305
+ #
306
+ # For the geocoder setting the results, it would look something like this:
307
+ # geo=GeoLoc.new(first_result)
308
+ # geo.all.push(second_result)
309
+ # geo.all.push(third_result)
310
+ #
311
+ # Then, for the user of the result:
312
+ #
313
+ # puts geo.full_address # just like usual
314
+ # puts geo.all.size => 3 # there's three results total
315
+ # puts geo.all.first # all is just an array or additional geolocs,
316
+ # so do what you want with it
317
+ class GeoLoc < LatLng
318
+
319
+ # Location attributes. Full address is a concatenation of all values. For example:
320
+ # 100 Spear St, San Francisco, CA, 94101, US
321
+ attr_accessor :street_address, :city, :state, :zip, :country_code, :full_address, :all
322
+ # Attributes set upon return from geocoding. Success will be true for successful
323
+ # geocode lookups. The provider will be set to the name of the providing geocoder.
324
+ # Finally, precision is an indicator of the accuracy of the geocoding.
325
+ attr_accessor :success, :provider, :precision
326
+ # Street number and street name are extracted from the street address attribute.
327
+ attr_reader :street_number, :street_name
328
+ # accuracy is set for Yahoo and Google geocoders, it is a numeric value of the
329
+ # precision. see http://code.google.com/apis/maps/documentation/geocoding/#GeocodingAccuracy
330
+ attr_accessor :accuracy
331
+
332
+ # Constructor expects a hash of symbols to correspond with attributes.
333
+ def initialize(h={})
334
+ @all = [self]
335
+
336
+ @street_address=h[:street_address]
337
+ @city=h[:city]
338
+ @state=h[:state]
339
+ @zip=h[:zip]
340
+ @country_code=h[:country_code]
341
+ @success=false
342
+ @precision='unknown'
343
+ @full_address=nil
344
+ super(h[:lat],h[:lng])
345
+ end
346
+
347
+ # Returns true if geocoded to the United States.
348
+ def is_us?
349
+ country_code == 'US'
350
+ end
351
+
352
+ def success?
353
+ success == true
354
+ end
355
+
356
+ # full_address is provided by google but not by yahoo. It is intended that the google
357
+ # geocoding method will provide the full address, whereas for yahoo it will be derived
358
+ # from the parts of the address we do have.
359
+ def full_address
360
+ @full_address ? @full_address : to_geocodeable_s
361
+ end
362
+
363
+ # Extracts the street number from the street address if the street address
364
+ # has a value.
365
+ def street_number
366
+ street_address[/(\d*)/] if street_address
367
+ end
368
+
369
+ # Returns the street name portion of the street address.
370
+ def street_name
371
+ street_address[street_number.length, street_address.length].strip if street_address
372
+ end
373
+
374
+ # gives you all the important fields as key-value pairs
375
+ def hash
376
+ res={}
377
+ [:success,:lat,:lng,:country_code,:city,:state,:zip,:street_address,:provider,:full_address,:is_us?,:ll,:precision].each { |s| res[s] = self.send(s.to_s) }
378
+ res
379
+ end
380
+ alias to_hash hash
381
+
382
+ # Sets the city after capitalizing each word within the city name.
383
+ def city=(city)
384
+ @city = Geokit::Inflector::titleize(city) if city
385
+ end
386
+
387
+ # Sets the street address after capitalizing each word within the street address.
388
+ def street_address=(address)
389
+ @street_address = Geokit::Inflector::titleize(address) if address
390
+ end
391
+
392
+ # Returns a comma-delimited string consisting of the street address, city, state,
393
+ # zip, and country code. Only includes those attributes that are non-blank.
394
+ def to_geocodeable_s
395
+ a=[street_address, city, state, zip, country_code].compact
396
+ a.delete_if { |e| !e || e == '' }
397
+ a.join(', ')
398
+ end
399
+
400
+ def to_yaml_properties
401
+ (instance_variables - ['@all']).sort
402
+ end
403
+
404
+ # Returns a string representation of the instance.
405
+ def to_s
406
+ "Provider: #{provider}\n Street: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nSuccess: #{success}"
407
+ end
408
+ end
409
+
410
+ # Bounds represents a rectangular bounds, defined by the SW and NE corners
411
+ class Bounds
412
+ # sw and ne are LatLng objects
413
+ attr_accessor :sw, :ne
414
+
415
+ # provide sw and ne to instantiate a new Bounds instance
416
+ def initialize(sw,ne)
417
+ raise ArgumentError if !(sw.is_a?(Geokit::LatLng) && ne.is_a?(Geokit::LatLng))
418
+ @sw,@ne=sw,ne
419
+ end
420
+
421
+ #returns the a single point which is the center of the rectangular bounds
422
+ def center
423
+ @sw.midpoint_to(@ne)
424
+ end
425
+
426
+ # a simple string representation:sw,ne
427
+ def to_s
428
+ "#{@sw.to_s},#{@ne.to_s}"
429
+ end
430
+
431
+ # a two-element array of two-element arrays: sw,ne
432
+ def to_a
433
+ [@sw.to_a, @ne.to_a]
434
+ end
435
+
436
+ # Returns true if the bounds contain the passed point.
437
+ # allows for bounds which cross the meridian
438
+ def contains?(point)
439
+ point=Geokit::LatLng.normalize(point)
440
+ res = point.lat > @sw.lat && point.lat < @ne.lat
441
+ if crosses_meridian?
442
+ res &= point.lng < @ne.lng || point.lng > @sw.lng
443
+ else
444
+ res &= point.lng < @ne.lng && point.lng > @sw.lng
445
+ end
446
+ res
447
+ end
448
+
449
+ # returns true if the bounds crosses the international dateline
450
+ def crosses_meridian?
451
+ @sw.lng > @ne.lng
452
+ end
453
+
454
+ # Returns true if the candidate object is logically equal. Logical equivalence
455
+ # is true if the lat and lng attributes are the same for both objects.
456
+ def ==(other)
457
+ other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
458
+ end
459
+
460
+ class <<self
461
+
462
+ # returns an instance of bounds which completely encompases the given circle
463
+ def from_point_and_radius(point,radius,options={})
464
+ point=LatLng.normalize(point)
465
+ p0=point.endpoint(0,radius,options)
466
+ p90=point.endpoint(90,radius,options)
467
+ p180=point.endpoint(180,radius,options)
468
+ p270=point.endpoint(270,radius,options)
469
+ sw=Geokit::LatLng.new(p180.lat,p270.lng)
470
+ ne=Geokit::LatLng.new(p0.lat,p90.lng)
471
+ Geokit::Bounds.new(sw,ne)
472
+ end
473
+
474
+ # Takes two main combinations of arguments to create a bounds:
475
+ # point,point (this is the only one which takes two arguments
476
+ # [point,point]
477
+ # . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
478
+ #
479
+ # NOTE: everything combination is assumed to pass points in the order sw, ne
480
+ def normalize (thing,other=nil)
481
+ # maybe this will be simple -- an actual bounds object is passed, and we can all go home
482
+ return thing if thing.is_a? Bounds
483
+
484
+ # no? OK, if there's no "other," the thing better be a two-element array
485
+ thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
486
+
487
+ # Now that we're set with a thing and another thing, let LatLng do the heavy lifting.
488
+ # Exceptions may be thrown
489
+ Bounds.new(Geokit::LatLng.normalize(thing),Geokit::LatLng.normalize(other))
490
+ end
491
+ end
492
+ end
493
+ end
data/lib/geokit.rb ADDED
@@ -0,0 +1,30 @@
1
+ module Geokit
2
+ VERSION = '1.3.0'
3
+ # These defaults are used in Geokit::Mappable.distance_to and in acts_as_mappable
4
+ @@default_units = :miles
5
+ @@default_formula = :sphere
6
+
7
+ [:default_units, :default_formula].each do |sym|
8
+ class_eval <<-EOS, __FILE__, __LINE__
9
+ def self.#{sym}
10
+ if defined?(#{sym.to_s.upcase})
11
+ #{sym.to_s.upcase}
12
+ else
13
+ @@#{sym}
14
+ end
15
+ end
16
+
17
+ def self.#{sym}=(obj)
18
+ @@#{sym} = obj
19
+ end
20
+ EOS
21
+ end
22
+ end
23
+
24
+ path = File.expand_path(File.dirname(__FILE__))
25
+ $: << path unless $:.include?(path)
26
+ require 'geokit/geocoders'
27
+ require 'geokit/mappable'
28
+
29
+ # make old-style module name "GeoKit" equivalent to new-style "Geokit"
30
+ GeoKit=Geokit
@@ -0,0 +1,57 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+ require 'rubygems'
4
+ require 'mocha'
5
+ require 'lib/geokit'
6
+
7
+ class MockSuccess < Net::HTTPSuccess #:nodoc: all
8
+ def initialize
9
+ end
10
+ end
11
+
12
+ class MockFailure < Net::HTTPServiceUnavailable #:nodoc: all
13
+ def initialize
14
+ end
15
+ end
16
+
17
+ # Base class for testing geocoders.
18
+ class BaseGeocoderTest < Test::Unit::TestCase #:nodoc: all
19
+
20
+ class Geokit::Geocoders::TestGeocoder < Geokit::Geocoders::Geocoder
21
+ def self.do_get(url)
22
+ sleep(2)
23
+ end
24
+ end
25
+
26
+ # Defines common test fixtures.
27
+ def setup
28
+ @address = 'San Francisco, CA'
29
+ @full_address = '100 Spear St, San Francisco, CA, 94105-1522, US'
30
+ @full_address_short_zip = '100 Spear St, San Francisco, CA, 94105, US'
31
+
32
+ @success = Geokit::GeoLoc.new({:city=>"SAN FRANCISCO", :state=>"CA", :country_code=>"US", :lat=>37.7742, :lng=>-122.417068})
33
+ @success.success = true
34
+ end
35
+
36
+ def test_timeout_call_web_service
37
+ url = "http://www.anything.com"
38
+ Geokit::Geocoders::timeout = 1
39
+ assert_nil Geokit::Geocoders::TestGeocoder.call_geocoder_service(url)
40
+ end
41
+
42
+ def test_successful_call_web_service
43
+ url = "http://www.anything.com"
44
+ Geokit::Geocoders::Geocoder.expects(:do_get).with(url).returns("SUCCESS")
45
+ assert_equal "SUCCESS", Geokit::Geocoders::Geocoder.call_geocoder_service(url)
46
+ end
47
+
48
+ def test_find_geocoder_methods
49
+ public_methods = Geokit::Geocoders::Geocoder.public_methods.map { |m| m.to_s }
50
+ assert public_methods.include?("yahoo_geocoder")
51
+ assert public_methods.include?("google_geocoder")
52
+ assert public_methods.include?("ca_geocoder")
53
+ assert public_methods.include?("us_geocoder")
54
+ assert public_methods.include?("multi_geocoder")
55
+ assert public_methods.include?("ip_geocoder")
56
+ end
57
+ end
@@ -0,0 +1,74 @@
1
+ require 'test/unit'
2
+ require 'lib/geokit'
3
+
4
+ class BoundsTest < Test::Unit::TestCase #:nodoc: all
5
+
6
+ def setup
7
+ # This is the area in Texas
8
+ @sw = Geokit::LatLng.new(32.91663,-96.982841)
9
+ @ne = Geokit::LatLng.new(32.96302,-96.919495)
10
+ @bounds=Geokit::Bounds.new(@sw,@ne)
11
+ @loc_a=Geokit::LatLng.new(32.918593,-96.958444) # inside bounds
12
+ @loc_b=Geokit::LatLng.new(32.914144,-96.958444) # outside bouds
13
+
14
+ # this is a cross-meridan area
15
+ @cross_meridian=Geokit::Bounds.normalize([30,170],[40,-170])
16
+ @inside_cm=Geokit::LatLng.new(35,175)
17
+ @inside_cm_2=Geokit::LatLng.new(35,-175)
18
+ @east_of_cm=Geokit::LatLng.new(35,-165)
19
+ @west_of_cm=Geokit::LatLng.new(35,165)
20
+
21
+ end
22
+
23
+ def test_equality
24
+ assert_equal Geokit::Bounds.new(@sw,@ne), Geokit::Bounds.new(@sw,@ne)
25
+ end
26
+
27
+ def test_normalize
28
+ res=Geokit::Bounds.normalize(@sw,@ne)
29
+ assert_equal res,Geokit::Bounds.new(@sw,@ne)
30
+ res=Geokit::Bounds.normalize([@sw,@ne])
31
+ assert_equal res,Geokit::Bounds.new(@sw,@ne)
32
+ res=Geokit::Bounds.normalize([@sw.lat,@sw.lng],[@ne.lat,@ne.lng])
33
+ assert_equal res,Geokit::Bounds.new(@sw,@ne)
34
+ res=Geokit::Bounds.normalize([[@sw.lat,@sw.lng],[@ne.lat,@ne.lng]])
35
+ assert_equal res,Geokit::Bounds.new(@sw,@ne)
36
+ end
37
+
38
+ def test_point_inside_bounds
39
+ assert @bounds.contains?(@loc_a)
40
+ end
41
+
42
+ def test_point_outside_bounds
43
+ assert !@bounds.contains?(@loc_b)
44
+ end
45
+
46
+ def test_point_inside_bounds_cross_meridian
47
+ assert @cross_meridian.contains?(@inside_cm)
48
+ assert @cross_meridian.contains?(@inside_cm_2)
49
+ end
50
+
51
+ def test_point_outside_bounds_cross_meridian
52
+ assert !@cross_meridian.contains?(@east_of_cm)
53
+ assert !@cross_meridian.contains?(@west_of_cm)
54
+ end
55
+
56
+ def test_center
57
+ assert_in_delta 32.939828,@bounds.center.lat,0.00005
58
+ assert_in_delta(-96.9511763,@bounds.center.lng,0.00005)
59
+ end
60
+
61
+ def test_center_cross_meridian
62
+ assert_in_delta 35.41160, @cross_meridian.center.lat,0.00005
63
+ assert_in_delta 179.38112, @cross_meridian.center.lng,0.00005
64
+ end
65
+
66
+ def test_creation_from_circle
67
+ bounds=Geokit::Bounds.from_point_and_radius([32.939829, -96.951176],2.5)
68
+ inside=Geokit::LatLng.new 32.9695270000,-96.9901590000
69
+ outside=Geokit::LatLng.new 32.8951550000,-96.9584440000
70
+ assert bounds.contains?(inside)
71
+ assert !bounds.contains?(outside)
72
+ end
73
+
74
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), 'test_base_geocoder')
2
+
3
+ Geokit::Geocoders::geocoder_ca = "SOMEKEYVALUE"
4
+
5
+ class CaGeocoderTest < BaseGeocoderTest #:nodoc: all
6
+
7
+ CA_SUCCESS=<<-EOF
8
+ <?xml version="1.0" encoding="UTF-8" ?>
9
+ <geodata><latt>49.243086</latt><longt>-123.153684</longt></geodata>
10
+ EOF
11
+
12
+ def setup
13
+ @ca_full_hash = {:street_address=>"2105 West 32nd Avenue",:city=>"Vancouver", :state=>"BC"}
14
+ @ca_full_loc = Geokit::GeoLoc.new(@ca_full_hash)
15
+ end
16
+
17
+ def test_geocoder_with_geo_loc_with_account
18
+ response = MockSuccess.new
19
+ response.expects(:body).returns(CA_SUCCESS)
20
+ url = "http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml"
21
+ Geokit::Geocoders::CaGeocoder.expects(:call_geocoder_service).with(url).returns(response)
22
+ verify(Geokit::Geocoders::CaGeocoder.geocode(@ca_full_loc))
23
+ end
24
+
25
+ def test_service_unavailable
26
+ response = MockFailure.new
27
+ #Net::HTTP.expects(:get_response).with(URI.parse("http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml")).returns(response)
28
+ url = "http://geocoder.ca/?stno=2105&addresst=West+32nd+Avenue&city=Vancouver&prov=BC&auth=SOMEKEYVALUE&geoit=xml"
29
+ Geokit::Geocoders::CaGeocoder.expects(:call_geocoder_service).with(url).returns(response)
30
+ assert !Geokit::Geocoders::CaGeocoder.geocode(@ca_full_loc).success
31
+ end
32
+
33
+ private
34
+
35
+ def verify(location)
36
+ assert_equal "BC", location.state
37
+ assert_equal "Vancouver", location.city
38
+ assert_equal "49.243086,-123.153684", location.ll
39
+ assert !location.is_us?
40
+ end
41
+ end