soey-geokit 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,459 @@
1
+
2
+ module Geokit
3
+ # Contains class and instance methods providing distance calcuation services. This
4
+ # module is meant to be mixed into classes containing lat and lng attributes where
5
+ # distance calculation is desired.
6
+ #
7
+ # At present, two forms of distance calculations are provided:
8
+ #
9
+ # * Pythagorean Theory (flat Earth) - which assumes the world is flat and loses accuracy over long distances.
10
+ # * Haversine (sphere) - which is fairly accurate, but at a performance cost.
11
+ #
12
+ # Distance units supported are :miles, :kms, and :nms.
13
+ module Mappable
14
+ PI_DIV_RAD = 0.0174
15
+ KMS_PER_MILE = 1.609
16
+ NMS_PER_MILE = 0.868976242
17
+ EARTH_RADIUS_IN_MILES = 3963.19
18
+ EARTH_RADIUS_IN_KMS = EARTH_RADIUS_IN_MILES * KMS_PER_MILE
19
+ EARTH_RADIUS_IN_NMS = EARTH_RADIUS_IN_MILES * NMS_PER_MILE
20
+ MILES_PER_LATITUDE_DEGREE = 69.1
21
+ KMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * KMS_PER_MILE
22
+ NMS_PER_LATITUDE_DEGREE = MILES_PER_LATITUDE_DEGREE * NMS_PER_MILE
23
+ LATITUDE_DEGREES = EARTH_RADIUS_IN_MILES / MILES_PER_LATITUDE_DEGREE
24
+
25
+ # Mix below class methods into the includer.
26
+ def self.included(receiver) # :nodoc:
27
+ receiver.extend ClassMethods
28
+ end
29
+
30
+ module ClassMethods #:nodoc:
31
+ # Returns the distance between two points. The from and to parameters are
32
+ # required to have lat and lng attributes. Valid options are:
33
+ # :units - valid values are :miles, :kms, :nms (Geokit::default_units is the default)
34
+ # :formula - valid values are :flat or :sphere (Geokit::default_formula is the default)
35
+ def distance_between(from, to, options={})
36
+ from=Geokit::LatLng.normalize(from)
37
+ to=Geokit::LatLng.normalize(to)
38
+ return 0.0 if from == to # fixes a "zero-distance" bug
39
+ units = options[:units] || Geokit::default_units
40
+ formula = options[:formula] || Geokit::default_formula
41
+ case formula
42
+ when :sphere
43
+ begin
44
+ units_sphere_multiplier(units) *
45
+ Math.acos( Math.sin(deg2rad(from.lat)) * Math.sin(deg2rad(to.lat)) +
46
+ Math.cos(deg2rad(from.lat)) * Math.cos(deg2rad(to.lat)) *
47
+ Math.cos(deg2rad(to.lng) - deg2rad(from.lng)))
48
+ rescue Errno::EDOM
49
+ 0.0
50
+ end
51
+ when :flat
52
+ Math.sqrt((units_per_latitude_degree(units)*(from.lat-to.lat))**2 +
53
+ (units_per_longitude_degree(from.lat, units)*(from.lng-to.lng))**2)
54
+ end
55
+ end
56
+
57
+ # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
58
+ # from the first point to the second point. Typicaly, the instance methods will be used
59
+ # instead of this method.
60
+ def heading_between(from,to)
61
+ from=Geokit::LatLng.normalize(from)
62
+ to=Geokit::LatLng.normalize(to)
63
+
64
+ d_lng=deg2rad(to.lng-from.lng)
65
+ from_lat=deg2rad(from.lat)
66
+ to_lat=deg2rad(to.lat)
67
+ y=Math.sin(d_lng) * Math.cos(to_lat)
68
+ x=Math.cos(from_lat)*Math.sin(to_lat)-Math.sin(from_lat)*Math.cos(to_lat)*Math.cos(d_lng)
69
+ heading=to_heading(Math.atan2(y,x))
70
+ end
71
+
72
+ # Given a start point, distance, and heading (in degrees), provides
73
+ # an endpoint. Returns a LatLng instance. Typically, the instance method
74
+ # will be used instead of this method.
75
+ def endpoint(start,heading, distance, options={})
76
+ units = options[:units] || Geokit::default_units
77
+ radius = case units
78
+ when :kms; EARTH_RADIUS_IN_KMS
79
+ when :nms; EARTH_RADIUS_IN_NMS
80
+ else EARTH_RADIUS_IN_MILES
81
+ end
82
+ start=Geokit::LatLng.normalize(start)
83
+ lat=deg2rad(start.lat)
84
+ lng=deg2rad(start.lng)
85
+ heading=deg2rad(heading)
86
+ distance=distance.to_f
87
+
88
+ end_lat=Math.asin(Math.sin(lat)*Math.cos(distance/radius) +
89
+ Math.cos(lat)*Math.sin(distance/radius)*Math.cos(heading))
90
+
91
+ end_lng=lng+Math.atan2(Math.sin(heading)*Math.sin(distance/radius)*Math.cos(lat),
92
+ Math.cos(distance/radius)-Math.sin(lat)*Math.sin(end_lat))
93
+
94
+ LatLng.new(rad2deg(end_lat),rad2deg(end_lng))
95
+ end
96
+
97
+ # Returns the midpoint, given two points. Returns a LatLng.
98
+ # Typically, the instance method will be used instead of this method.
99
+ # Valid option:
100
+ # :units - valid values are :miles, :kms, or :nms (:miles is the default)
101
+ def midpoint_between(from,to,options={})
102
+ from=Geokit::LatLng.normalize(from)
103
+
104
+ units = options[:units] || Geokit::default_units
105
+
106
+ heading=from.heading_to(to)
107
+ distance=from.distance_to(to,options)
108
+ midpoint=from.endpoint(heading,distance/2,options)
109
+ end
110
+
111
+ # Geocodes a location using the multi geocoder.
112
+ def geocode(location)
113
+ res = Geocoders::MultiGeocoder.geocode(location)
114
+ return res if res.success
115
+ raise Geokit::Geocoders::GeocodeError
116
+ end
117
+
118
+ protected
119
+
120
+ def deg2rad(degrees)
121
+ degrees.to_f / 180.0 * Math::PI
122
+ end
123
+
124
+ def rad2deg(rad)
125
+ rad.to_f * 180.0 / Math::PI
126
+ end
127
+
128
+ def to_heading(rad)
129
+ (rad2deg(rad)+360)%360
130
+ end
131
+
132
+ # Returns the multiplier used to obtain the correct distance units.
133
+ def units_sphere_multiplier(units)
134
+ case units
135
+ when :kms; EARTH_RADIUS_IN_KMS
136
+ when :nms; EARTH_RADIUS_IN_NMS
137
+ else EARTH_RADIUS_IN_MILES
138
+ end
139
+ end
140
+
141
+ # Returns the number of units per latitude degree.
142
+ def units_per_latitude_degree(units)
143
+ case units
144
+ when :kms; KMS_PER_LATITUDE_DEGREE
145
+ when :nms; NMS_PER_LATITUDE_DEGREE
146
+ else MILES_PER_LATITUDE_DEGREE
147
+ end
148
+ end
149
+
150
+ # Returns the number units per longitude degree.
151
+ def units_per_longitude_degree(lat, units)
152
+ miles_per_longitude_degree = (LATITUDE_DEGREES * Math.cos(lat * PI_DIV_RAD)).abs
153
+ case units
154
+ when :kms; miles_per_longitude_degree * KMS_PER_MILE
155
+ when :nms; miles_per_longitude_degree * NMS_PER_MILE
156
+ else miles_per_longitude_degree
157
+ end
158
+ end
159
+ end
160
+
161
+ # -----------------------------------------------------------------------------------------------
162
+ # Instance methods below here
163
+ # -----------------------------------------------------------------------------------------------
164
+
165
+ # Extracts a LatLng instance. Use with models that are acts_as_mappable
166
+ def to_lat_lng
167
+ return self if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
168
+ return LatLng.new(send(self.class.lat_column_name),send(self.class.lng_column_name)) if self.class.respond_to?(:acts_as_mappable)
169
+ nil
170
+ end
171
+
172
+ # Returns the distance from another point. The other point parameter is
173
+ # required to have lat and lng attributes. Valid options are:
174
+ # :units - valid values are :miles, :kms, :or :nms (:miles is the default)
175
+ # :formula - valid values are :flat or :sphere (:sphere is the default)
176
+ def distance_to(other, options={})
177
+ self.class.distance_between(self, other, options)
178
+ end
179
+ alias distance_from distance_to
180
+
181
+ # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
182
+ # to the given point. The given point can be a LatLng or a string to be Geocoded
183
+ def heading_to(other)
184
+ self.class.heading_between(self,other)
185
+ end
186
+
187
+ # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
188
+ # FROM the given point. The given point can be a LatLng or a string to be Geocoded
189
+ def heading_from(other)
190
+ self.class.heading_between(other,self)
191
+ end
192
+
193
+ # Returns the endpoint, given a heading (in degrees) and distance.
194
+ # Valid option:
195
+ # :units - valid values are :miles, :kms, or :nms (:miles is the default)
196
+ def endpoint(heading,distance,options={})
197
+ self.class.endpoint(self,heading,distance,options)
198
+ end
199
+
200
+ # Returns the midpoint, given another point on the map.
201
+ # Valid option:
202
+ # :units - valid values are :miles, :kms, or :nms (:miles is the default)
203
+ def midpoint_to(other, options={})
204
+ self.class.midpoint_between(self,other,options)
205
+ end
206
+
207
+ end
208
+
209
+ class LatLng
210
+ include Mappable
211
+
212
+ attr_accessor :lat, :lng
213
+
214
+ # Accepts latitude and longitude or instantiates an empty instance
215
+ # if lat and lng are not provided. Converted to floats if provided
216
+ def initialize(lat=nil, lng=nil)
217
+ lat = lat.to_f if lat && !lat.is_a?(Numeric)
218
+ lng = lng.to_f if lng && !lng.is_a?(Numeric)
219
+ @lat = lat
220
+ @lng = lng
221
+ end
222
+
223
+ # Latitude attribute setter; stored as a float.
224
+ def lat=(lat)
225
+ @lat = lat.to_f if lat
226
+ end
227
+
228
+ # Longitude attribute setter; stored as a float;
229
+ def lng=(lng)
230
+ @lng=lng.to_f if lng
231
+ end
232
+
233
+ # Returns the lat and lng attributes as a comma-separated string.
234
+ def ll
235
+ "#{lat},#{lng}"
236
+ end
237
+
238
+ #returns a string with comma-separated lat,lng values
239
+ def to_s
240
+ ll
241
+ end
242
+
243
+ #returns a two-element array
244
+ def to_a
245
+ [lat,lng]
246
+ end
247
+ # Returns true if the candidate object is logically equal. Logical equivalence
248
+ # is true if the lat and lng attributes are the same for both objects.
249
+ def ==(other)
250
+ other.is_a?(LatLng) ? self.lat == other.lat && self.lng == other.lng : false
251
+ end
252
+
253
+ # A *class* method to take anything which can be inferred as a point and generate
254
+ # a LatLng from it. You should use this anything you're not sure what the input is,
255
+ # and want to deal with it as a LatLng if at all possible. Can take:
256
+ # 1) two arguments (lat,lng)
257
+ # 2) a string in the format "37.1234,-129.1234" or "37.1234 -129.1234"
258
+ # 3) a string which can be geocoded on the fly
259
+ # 4) an array in the format [37.1234,-129.1234]
260
+ # 5) a LatLng or GeoLoc (which is just passed through as-is)
261
+ # 6) anything which acts_as_mappable -- a LatLng will be extracted from it
262
+ def self.normalize(thing,other=nil)
263
+ # if an 'other' thing is supplied, normalize the input by creating an array of two elements
264
+ thing=[thing,other] if other
265
+
266
+ if thing.is_a?(String)
267
+ thing.strip!
268
+ if match=thing.match(/(\-?\d+\.?\d*)[, ] ?(\-?\d+\.?\d*)$/)
269
+ return Geokit::LatLng.new(match[1],match[2])
270
+ else
271
+ res = Geokit::Geocoders::MultiGeocoder.geocode(thing)
272
+ return res if res.success
273
+ raise Geokit::Geocoders::GeocodeError
274
+ end
275
+ elsif thing.is_a?(Array) && thing.size==2
276
+ return Geokit::LatLng.new(thing[0],thing[1])
277
+ elsif thing.is_a?(LatLng) # will also be true for GeoLocs
278
+ return thing
279
+ elsif thing.class.respond_to?(:acts_as_mappable) && thing.class.respond_to?(:distance_column_name)
280
+ return thing.to_lat_lng
281
+ end
282
+
283
+ 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.")
284
+ end
285
+
286
+ end
287
+
288
+ # This class encapsulates the result of a geocoding call
289
+ # It's primary purpose is to homogenize the results of multiple
290
+ # geocoding providers. It also provides some additional functionality, such as
291
+ # the "full address" method for geocoders that do not provide a
292
+ # full address in their results (for example, Yahoo), and the "is_us" method.
293
+ class GeoLoc < LatLng
294
+ # Location attributes. Full address is a concatenation of all values. For example:
295
+ # 100 Spear St, San Francisco, CA, 94101, US
296
+ attr_accessor :street_address, :city, :state, :zip, :country_code, :full_address, :bounds
297
+ # Attributes set upon return from geocoding. Success will be true for successful
298
+ # geocode lookups. The provider will be set to the name of the providing geocoder.
299
+ # Finally, precision is an indicator of the accuracy of the geocoding.
300
+ attr_accessor :success, :provider, :precision
301
+ # Street number and street name are extracted from the street address attribute.
302
+ attr_reader :street_number, :street_name
303
+
304
+ # Constructor expects a hash of symbols to correspond with attributes.
305
+ def initialize(h={})
306
+ @street_address=h[:street_address]
307
+ @city=h[:city]
308
+ @state=h[:state]
309
+ @zip=h[:zip]
310
+ @country_code=h[:country_code]
311
+ @success=false
312
+ @precision='unknown'
313
+ @full_address=nil
314
+ super(h[:lat],h[:lng])
315
+ end
316
+
317
+ # Returns true if geocoded to the United States.
318
+ def is_us?
319
+ country_code == 'US'
320
+ end
321
+
322
+ # full_address is provided by google but not by yahoo. It is intended that the google
323
+ # geocoding method will provide the full address, whereas for yahoo it will be derived
324
+ # from the parts of the address we do have.
325
+ def full_address
326
+ @full_address ? @full_address : to_geocodeable_s
327
+ end
328
+
329
+ # Extracts the street number from the street address if the street address
330
+ # has a value.
331
+ def street_number
332
+ street_address[/(\d*)/] if street_address
333
+ end
334
+
335
+ # Returns the street name portion of the street address.
336
+ def street_name
337
+ street_address[street_number.length, street_address.length].strip if street_address
338
+ end
339
+
340
+ # gives you all the important fields as key-value pairs
341
+ def hash
342
+ res={}
343
+ [: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) }
344
+ res
345
+ end
346
+ alias to_hash hash
347
+
348
+ # Sets the city after capitalizing each word within the city name.
349
+ def city=(city)
350
+ @city = Geokit::Inflector::titleize(city) if city
351
+ end
352
+
353
+ # Sets the street address after capitalizing each word within the street address.
354
+ def street_address=(address)
355
+ @street_address = Geokit::Inflector::titleize(address) if address
356
+ end
357
+
358
+ def bounds=(hash)
359
+ @bounds= Bounds.normalize([hash[:south], hash[:west]], [hash[:north], hash[:east]] )
360
+ end
361
+
362
+ # Returns a comma-delimited string consisting of the street address, city, state,
363
+ # zip, and country code. Only includes those attributes that are non-blank.
364
+ def to_geocodeable_s
365
+ a=[street_address, city, state, zip, country_code].compact
366
+ a.delete_if { |e| !e || e == '' }
367
+ a.join(', ')
368
+ end
369
+
370
+ # Returns a string representation of the instance.
371
+ def to_s
372
+ "Provider: #{provider}\n Street: #{street_address}\nCity: #{city}\nState: #{state}\nZip: #{zip}\nLatitude: #{lat}\nLongitude: #{lng}\nCountry: #{country_code}\nBounds: #{bounds}\nSuccess: #{success}"
373
+ end
374
+ end
375
+
376
+ # Bounds represents a rectangular bounds, defined by the SW and NE corners
377
+ class Bounds
378
+ # sw and ne are LatLng objects
379
+ attr_accessor :sw, :ne
380
+
381
+ # provide sw and ne to instantiate a new Bounds instance
382
+ def initialize(sw,ne)
383
+ raise ArgumentError if !(sw.is_a?(Geokit::LatLng) && ne.is_a?(Geokit::LatLng))
384
+ @sw,@ne=sw,ne
385
+ end
386
+
387
+ #returns the a single point which is the center of the rectangular bounds
388
+ def center
389
+ @sw.midpoint_to(@ne)
390
+ end
391
+
392
+ # a simple string representation:sw,ne
393
+ def to_s
394
+ "#{@sw.to_s},#{@ne.to_s}"
395
+ end
396
+
397
+ # a two-element array of two-element arrays: sw,ne
398
+ def to_a
399
+ [@sw.to_a, @ne.to_a]
400
+ end
401
+
402
+ # Returns true if the bounds contain the passed point.
403
+ # allows for bounds which cross the meridian
404
+ def contains?(point)
405
+ point=Geokit::LatLng.normalize(point)
406
+ res = point.lat > @sw.lat && point.lat < @ne.lat
407
+ if crosses_meridian?
408
+ res &= point.lng < @ne.lng || point.lng > @sw.lng
409
+ else
410
+ res &= point.lng < @ne.lng && point.lng > @sw.lng
411
+ end
412
+ res
413
+ end
414
+
415
+ # returns true if the bounds crosses the international dateline
416
+ def crosses_meridian?
417
+ @sw.lng > @ne.lng
418
+ end
419
+
420
+ # Returns true if the candidate object is logically equal. Logical equivalence
421
+ # is true if the lat and lng attributes are the same for both objects.
422
+ def ==(other)
423
+ other.is_a?(Bounds) ? self.sw == other.sw && self.ne == other.ne : false
424
+ end
425
+
426
+ class <<self
427
+
428
+ # returns an instance of bounds which completely encompases the given circle
429
+ def from_point_and_radius(point,radius,options={})
430
+ point=LatLng.normalize(point)
431
+ p0=point.endpoint(0,radius,options)
432
+ p90=point.endpoint(90,radius,options)
433
+ p180=point.endpoint(180,radius,options)
434
+ p270=point.endpoint(270,radius,options)
435
+ sw=Geokit::LatLng.new(p180.lat,p270.lng)
436
+ ne=Geokit::LatLng.new(p0.lat,p90.lng)
437
+ Geokit::Bounds.new(sw,ne)
438
+ end
439
+
440
+ # Takes two main combinations of arguments to create a bounds:
441
+ # point,point (this is the only one which takes two arguments
442
+ # [point,point]
443
+ # . . . where a point is anything LatLng#normalize can handle (which is quite a lot)
444
+ #
445
+ # NOTE: everything combination is assumed to pass points in the order sw, ne
446
+ def normalize (thing,other=nil)
447
+ # maybe this will be simple -- an actual bounds object is passed, and we can all go home
448
+ return thing if thing.is_a? Bounds
449
+
450
+ # no? OK, if there's no "other," the thing better be a two-element array
451
+ thing,other=thing if !other && thing.is_a?(Array) && thing.size==2
452
+
453
+ # Now that we're set with a thing and another thing, let LatLng do the heavy lifting.
454
+ # Exceptions may be thrown
455
+ Bounds.new(Geokit::LatLng.normalize(thing),Geokit::LatLng.normalize(other))
456
+ end
457
+ end
458
+ end
459
+ end
data/lib/geokit.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Geokit
2
+ VERSION = '1.2.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
+ require 'geokit/geocoders'
25
+ require 'geokit/mappable'
26
+
27
+ # make old-style module name "GeoKit" equivilent to new-style "Geokit"
28
+ GeoKit=Geokit
@@ -0,0 +1,56 @@
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
+ # Defines common test fixtures.
21
+ def setup
22
+ @address = 'San Francisco, CA'
23
+ @full_address = '100 Spear St, San Francisco, CA, 94105-1522, US'
24
+ @full_address_short_zip = '100 Spear St, San Francisco, CA, 94105, US'
25
+
26
+ @success = Geokit::GeoLoc.new({:city=>"SAN FRANCISCO", :state=>"CA", :country_code=>"US", :lat=>37.7742, :lng=>-122.417068})
27
+ @success.success = true
28
+ end
29
+
30
+ def test_timeout_call_web_service
31
+ Geokit::Geocoders::Geocoder.class_eval do
32
+ def self.do_get(url)
33
+ sleep(2)
34
+ end
35
+ end
36
+ url = "http://www.anything.com"
37
+ Geokit::Geocoders::timeout = 1
38
+ assert_nil Geokit::Geocoders::Geocoder.call_geocoder_service(url)
39
+ end
40
+
41
+ def test_successful_call_web_service
42
+ url = "http://www.anything.com"
43
+ Geokit::Geocoders::Geocoder.expects(:do_get).with(url).returns("SUCCESS")
44
+ assert_equal "SUCCESS", Geokit::Geocoders::Geocoder.call_geocoder_service(url)
45
+ end
46
+
47
+ def test_find_geocoder_methods
48
+ public_methods = Geokit::Geocoders::Geocoder.public_methods.map { |m| m.to_s }
49
+ assert public_methods.include?("yahoo_geocoder")
50
+ assert public_methods.include?("google_geocoder")
51
+ assert public_methods.include?("ca_geocoder")
52
+ assert public_methods.include?("us_geocoder")
53
+ assert public_methods.include?("multi_geocoder")
54
+ assert public_methods.include?("ip_geocoder")
55
+ end
56
+ 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