geokit 1.8.5 → 1.9.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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +7 -0
  4. data/README.markdown +31 -14
  5. data/fixtures/vcr_cassettes/geonames_geocode.yml +1 -1
  6. data/fixtures/vcr_cassettes/geonames_geocode_premium.yml +42 -0
  7. data/fixtures/vcr_cassettes/google_country_code_biased_result_orly.yml +160 -0
  8. data/fixtures/vcr_cassettes/google_country_code_biased_result_toledo.yml +111 -0
  9. data/fixtures/vcr_cassettes/google_result_toledo_default_bias.yml +275 -0
  10. data/fixtures/vcr_cassettes/google_sublocality.yml +126 -0
  11. data/fixtures/vcr_cassettes/mapbox_forward_geocode.yml +59 -0
  12. data/fixtures/vcr_cassettes/mapbox_reverse_geocode.yml +63 -0
  13. data/fixtures/vcr_cassettes/opencage_city.yml +51 -0
  14. data/fixtures/vcr_cassettes/opencage_full.yml +65 -0
  15. data/fixtures/vcr_cassettes/opencage_language_response_es.yml +66 -0
  16. data/fixtures/vcr_cassettes/opencage_reverse_madrid.yml +51 -0
  17. data/fixtures/vcr_cassettes/opencage_reverse_prilep.yml +53 -0
  18. data/geokit.gemspec +5 -3
  19. data/lib/geokit/bounds.rb +40 -31
  20. data/lib/geokit/core_ext.rb +1 -1
  21. data/lib/geokit/geo_loc.rb +63 -41
  22. data/lib/geokit/geocoders.rb +13 -13
  23. data/lib/geokit/geocoders/bing.rb +9 -9
  24. data/lib/geokit/geocoders/ca_geocoder.rb +29 -29
  25. data/lib/geokit/geocoders/fcc.rb +4 -4
  26. data/lib/geokit/geocoders/free_geo_ip.rb +6 -7
  27. data/lib/geokit/geocoders/geo_plugin.rb +5 -6
  28. data/lib/geokit/geocoders/geocodio.rb +2 -2
  29. data/lib/geokit/geocoders/geonames.rb +18 -12
  30. data/lib/geokit/geocoders/google.rb +31 -30
  31. data/lib/geokit/geocoders/ip.rb +3 -4
  32. data/lib/geokit/geocoders/mapbox.rb +94 -0
  33. data/lib/geokit/geocoders/mapquest.rb +5 -5
  34. data/lib/geokit/geocoders/opencage.rb +93 -0
  35. data/lib/geokit/geocoders/openstreetmap.rb +9 -6
  36. data/lib/geokit/geocoders/ripe.rb +3 -3
  37. data/lib/geokit/geocoders/us_geocoder.rb +10 -9
  38. data/lib/geokit/geocoders/yahoo.rb +33 -34
  39. data/lib/geokit/geocoders/yandex.rb +17 -17
  40. data/lib/geokit/inflectors.rb +4 -10
  41. data/lib/geokit/lat_lng.rb +50 -26
  42. data/lib/geokit/mappable.rb +83 -83
  43. data/lib/geokit/multi_geocoder.rb +25 -20
  44. data/lib/geokit/net_adapter/net_http.rb +7 -4
  45. data/lib/geokit/polygon.rb +36 -4
  46. data/lib/geokit/version.rb +1 -1
  47. data/test/helper.rb +15 -13
  48. data/test/test_base_geocoder.rb +6 -7
  49. data/test/test_bing_geocoder.rb +20 -21
  50. data/test/test_bounds.rb +26 -28
  51. data/test/test_ca_geocoder.rb +9 -10
  52. data/test/test_fcc_geocoder.rb +1 -1
  53. data/test/test_free_geo_ip_geocoder.rb +1 -1
  54. data/test/test_geo_plugin_geocoder.rb +9 -9
  55. data/test/test_geoloc.rb +7 -6
  56. data/test/test_geonames_geocoder.rb +28 -6
  57. data/test/test_google_geocoder.rb +210 -176
  58. data/test/test_inflector.rb +0 -1
  59. data/test/test_ipgeocoder.rb +17 -18
  60. data/test/test_latlng.rb +105 -85
  61. data/test/test_map_quest.rb +18 -21
  62. data/test/test_mapbox_geocoder.rb +31 -0
  63. data/test/test_mappable.rb +46 -0
  64. data/test/test_maxmind_geocoder.rb +1 -3
  65. data/test/test_multi_geocoder.rb +8 -9
  66. data/test/test_multi_ip_geocoder.rb +3 -5
  67. data/test/test_net_adapter.rb +4 -4
  68. data/test/test_opencage_geocoder.rb +108 -0
  69. data/test/test_openstreetmap_geocoder.rb +62 -44
  70. data/test/{test_polygon_contains.rb → test_polygon.rb} +30 -20
  71. data/test/test_ripe_geocoder.rb +2 -0
  72. data/test/test_us_geocoder.rb +7 -8
  73. data/test/test_yahoo_geocoder.rb +20 -21
  74. data/test/test_yandex_geocoder.rb +34 -35
  75. metadata +79 -56
  76. data/fixtures/vcr_cassettes/google_country_code_biased_result.yml +0 -401
@@ -1,44 +1,43 @@
1
- #require 'forwardable'
1
+ # require 'forwardable'
2
2
 
3
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.
4
+ # Contains class and instance methods providing distance calcuation services.
5
+ # This module is meant to be mixed into classes containing lat and lng
6
+ # attributes where distance calculation is desired.
7
7
  #
8
8
  # At present, two forms of distance calculations are provided:
9
9
  #
10
- # * Pythagorean Theory (flat Earth) - which assumes the world is flat and loses accuracy over long distances.
10
+ # * Pythagorean Theory (flat Earth) - which assumes the world is flat and
11
+ # loses accuracy over long distances.
11
12
  # * Haversine (sphere) - which is fairly accurate, but at a performance cost.
12
13
  #
13
14
  # Distance units supported are :miles, :kms, and :nms.
14
15
  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
16
  # Mix below class methods into the includer.
27
17
  def self.included(receiver) # :nodoc:
28
18
  receiver.extend ClassMethods
29
19
  end
30
20
 
31
21
  module ClassMethods #:nodoc:
22
+ PI_DIV_RAD = Math::PI / 180
23
+ EARTH_RADIUS_IN_METERS = 6376772.71
24
+ METERS_PER_LATITUDE_DEGREE = 111181.9
25
+
26
+ EARTH_RADIUS = {}
27
+ PER_LATITUDE_DEGREE = {}
28
+
32
29
  # Returns the distance between two points. The from and to parameters are
33
30
  # 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)
31
+ # :units - valid values are :miles, :kms, :nms
32
+ # (Geokit::default_units is the default)
33
+ # :formula - valid values are :flat or :sphere
34
+ # (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)
39
38
  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
39
+ units = options[:units] || Geokit.default_units
40
+ formula = options[:formula] || Geokit.default_formula
42
41
  case formula
43
42
  when :sphere then distance_between_sphere(from, to, units)
44
43
  when :flat then distance_between_flat(from, to, units)
@@ -56,8 +55,9 @@ module Geokit
56
55
 
57
56
  def distance_between_flat(from, to, units)
58
57
  lat_length = units_per_latitude_degree(units) * (from.lat - to.lat)
59
- lng_length = units_per_longitude_degree(from.lat, units) * (from.lng - to.lng)
60
- Math.sqrt(lat_length ** 2 + lng_length ** 2)
58
+ lng_length = units_per_longitude_degree(from.lat, units) *
59
+ (from.lng - to.lng)
60
+ Math.sqrt(lat_length**2 + lng_length**2)
61
61
  end
62
62
 
63
63
  # Ruby 1.9 raises {Math::DomainError}, but it is not defined in Ruby 1.8
@@ -67,25 +67,26 @@ module Geokit
67
67
  end
68
68
 
69
69
  # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
70
- # from the first point to the second point. Typicaly, the instance methods will be used
71
- # instead of this method.
72
- def heading_between(from,to)
73
- from=Geokit::LatLng.normalize(from)
74
- to=Geokit::LatLng.normalize(to)
75
-
76
- d_lng=deg2rad(to.lng-from.lng)
77
- from_lat=deg2rad(from.lat)
78
- to_lat=deg2rad(to.lat)
79
- y=Math.sin(d_lng) * Math.cos(to_lat)
80
- x=Math.cos(from_lat)*Math.sin(to_lat)-Math.sin(from_lat)*Math.cos(to_lat)*Math.cos(d_lng)
81
- heading=to_heading(Math.atan2(y,x))
70
+ # from the first point to the second point. Typicaly, the instance methods
71
+ # will be used instead of this method.
72
+ def heading_between(from, to)
73
+ from = Geokit::LatLng.normalize(from)
74
+ to = Geokit::LatLng.normalize(to)
75
+
76
+ d_lng = deg2rad(to.lng - from.lng)
77
+ from_lat = deg2rad(from.lat)
78
+ to_lat = deg2rad(to.lat)
79
+ y = Math.sin(d_lng) * Math.cos(to_lat)
80
+ x = Math.cos(from_lat) * Math.sin(to_lat) -
81
+ Math.sin(from_lat) * Math.cos(to_lat) * Math.cos(d_lng)
82
+ to_heading(Math.atan2(y, x))
82
83
  end
83
84
 
84
85
  # Given a start point, distance, and heading (in degrees), provides
85
86
  # an endpoint. Returns a LatLng instance. Typically, the instance method
86
87
  # will be used instead of this method.
87
- def endpoint(start,heading, distance, options={})
88
- units = options[:units] || Geokit::default_units
88
+ def endpoint(start, heading, distance, options = {})
89
+ units = options[:units] || Geokit.default_units
89
90
  ratio = distance.to_f / units_sphere_multiplier(units)
90
91
  start = Geokit::LatLng.normalize(start)
91
92
  lat = deg2rad(start.lat)
@@ -103,21 +104,20 @@ module Geokit
103
104
  end_lng = lng + Math.atan2(Math.sin(heading) * sin_ratio * cos_lat,
104
105
  cos_ratio - sin_lat * Math.sin(end_lat))
105
106
 
106
- LatLng.new(rad2deg(end_lat),rad2deg(end_lng))
107
+ LatLng.new(rad2deg(end_lat), rad2deg(end_lng))
107
108
  end
108
109
 
109
110
  # Returns the midpoint, given two points. Returns a LatLng.
110
111
  # Typically, the instance method will be used instead of this method.
111
112
  # Valid option:
112
- # :units - valid values are :miles, :kms, or :nms (:miles is the default)
113
- def midpoint_between(from,to,options={})
114
- from=Geokit::LatLng.normalize(from)
115
-
116
- units = options[:units] || Geokit::default_units
117
-
118
- heading=from.heading_to(to)
119
- distance=from.distance_to(to,options)
120
- midpoint=from.endpoint(heading,distance/2,options)
113
+ # :units - valid values are :miles, :kms, or :nms
114
+ # (:miles is the default)
115
+ def midpoint_between(from, to, options = {})
116
+ from = Geokit::LatLng.normalize(from)
117
+
118
+ heading = from.heading_to(to)
119
+ distance = from.distance_to(to, options)
120
+ from.endpoint(heading, distance / 2, options)
121
121
  end
122
122
 
123
123
  # Geocodes a location using the multi geocoder.
@@ -139,8 +139,6 @@ module Geokit
139
139
  (seconds % 60) # seconds as positive float
140
140
  ]
141
141
  end
142
-
143
- protected
144
142
 
145
143
  def deg2rad(degrees)
146
144
  degrees.to_f / 180.0 * Math::PI
@@ -151,35 +149,34 @@ module Geokit
151
149
  end
152
150
 
153
151
  def to_heading(rad)
154
- (rad2deg(rad)+360)%360
152
+ (rad2deg(rad) + 360) % 360
153
+ end
154
+
155
+ def self.register_unit(key, in_meters)
156
+ EARTH_RADIUS[key] = EARTH_RADIUS_IN_METERS * in_meters
157
+ PER_LATITUDE_DEGREE[key] = METERS_PER_LATITUDE_DEGREE * in_meters
155
158
  end
156
159
 
160
+ register_unit :meters, 1
161
+ register_unit :kms, 1 / 1000.0
162
+ register_unit :miles, 1 / 1609.0
163
+ register_unit :nms, 0.0005400722448725917
164
+
157
165
  # Returns the multiplier used to obtain the correct distance units.
166
+ # TODO make more accurate by coping
167
+ # http://msi.nga.mil/MSISiteContent/StaticFiles/Calculators/degree.html
158
168
  def units_sphere_multiplier(units)
159
- case units
160
- when :kms; EARTH_RADIUS_IN_KMS
161
- when :nms; EARTH_RADIUS_IN_NMS
162
- else EARTH_RADIUS_IN_MILES
163
- end
169
+ EARTH_RADIUS[units]
164
170
  end
165
171
 
166
172
  # Returns the number of units per latitude degree.
167
173
  def units_per_latitude_degree(units)
168
- case units
169
- when :kms; KMS_PER_LATITUDE_DEGREE
170
- when :nms; NMS_PER_LATITUDE_DEGREE
171
- else MILES_PER_LATITUDE_DEGREE
172
- end
174
+ PER_LATITUDE_DEGREE[units]
173
175
  end
174
176
 
175
177
  # Returns the number units per longitude degree.
176
178
  def units_per_longitude_degree(lat, units)
177
- miles_per_longitude_degree = (LATITUDE_DEGREES * Math.cos(lat * PI_DIV_RAD)).abs
178
- case units
179
- when :kms; miles_per_longitude_degree * KMS_PER_MILE
180
- when :nms; miles_per_longitude_degree * NMS_PER_MILE
181
- else miles_per_longitude_degree
182
- end
179
+ units_sphere_multiplier(units) * Math.cos(lat * PI_DIV_RAD) * PI_DIV_RAD
183
180
  end
184
181
  end
185
182
 
@@ -189,46 +186,49 @@ module Geokit
189
186
 
190
187
  # Extracts a LatLng instance. Use with models that are acts_as_mappable
191
188
  def to_lat_lng
192
- return self if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
193
- return LatLng.new(send(self.class.lat_column_name), send(self.class.lng_column_name))
194
- nil
189
+ if instance_of?(Geokit::LatLng) || instance_of?(Geokit::GeoLoc)
190
+ return self
191
+ end
192
+ lat = send(self.class.lat_column_name)
193
+ lng = send(self.class.lng_column_name)
194
+ LatLng.new(lat, lng)
195
195
  end
196
196
 
197
197
  # Returns the distance from another point. The other point parameter is
198
198
  # required to have lat and lng attributes. Valid options are:
199
199
  # :units - valid values are :miles, :kms, :or :nms (:miles is the default)
200
200
  # :formula - valid values are :flat or :sphere (:sphere is the default)
201
- def distance_to(other, options={})
201
+ def distance_to(other, options = {})
202
202
  self.class.distance_between(self, other, options)
203
203
  end
204
204
  alias distance_from distance_to
205
205
 
206
- # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
207
- # to the given point. The given point can be a LatLng or a string to be Geocoded
206
+ # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc) to
207
+ # the given point. The given point can be a LatLng or a string to be
208
+ # Geocoded
208
209
  def heading_to(other)
209
- self.class.heading_between(self,other)
210
+ self.class.heading_between(self, other)
210
211
  end
211
212
 
212
213
  # Returns heading in degrees (0 is north, 90 is east, 180 is south, etc)
213
- # FROM the given point. The given point can be a LatLng or a string to be Geocoded
214
+ # FROM the given point. The given point can be a LatLng or a string to be
215
+ # Geocoded
214
216
  def heading_from(other)
215
- self.class.heading_between(other,self)
217
+ self.class.heading_between(other, self)
216
218
  end
217
219
 
218
220
  # Returns the endpoint, given a heading (in degrees) and distance.
219
221
  # Valid option:
220
222
  # :units - valid values are :miles, :kms, or :nms (:miles is the default)
221
- def endpoint(heading,distance,options={})
222
- self.class.endpoint(self,heading,distance,options)
223
+ def endpoint(heading, distance, options = {})
224
+ self.class.endpoint(self, heading, distance, options)
223
225
  end
224
226
 
225
227
  # Returns the midpoint, given another point on the map.
226
228
  # Valid option:
227
229
  # :units - valid values are :miles, :kms, or :nms (:miles is the default)
228
- def midpoint_to(other, options={})
229
- self.class.midpoint_between(self,other,options)
230
+ def midpoint_to(other, options = {})
231
+ self.class.midpoint_between(self, other, options)
230
232
  end
231
-
232
233
  end
233
-
234
234
  end
@@ -1,26 +1,29 @@
1
1
  module Geokit
2
2
  module Geocoders
3
- # -------------------------------------------------------------------------------------------
3
+ # --------------------------------------------------------------------------
4
4
  # The Multi Geocoder
5
- # -------------------------------------------------------------------------------------------
5
+ # --------------------------------------------------------------------------
6
6
 
7
- # Provides methods to geocode with a variety of geocoding service providers, plus failover
8
- # among providers in the order you configure. When 2nd parameter is set 'true', perform
9
- # ip location lookup with 'address' as the ip address.
7
+ # Provides methods to geocode with a variety of geocoding service providers,
8
+ # plus failover among providers in the order you configure. When 2nd
9
+ # parameter is set 'true', perform ip location lookup with 'address' as the
10
+ # ip address.
10
11
  #
11
12
  # Goal:
12
13
  # - homogenize the results of multiple geocoders
13
14
  #
14
15
  # Limitations:
15
- # - currently only provides the first result. Sometimes geocoders will return multiple results.
16
+ # - currently only provides the first result. Sometimes geocoders will
17
+ # return multiple results.
16
18
  # - currently discards the "accuracy" component of the geocoding calls
17
19
  class MultiGeocoder < Geocoder
18
-
19
20
  private
20
- # This method will call one or more geocoders in the order specified in the
21
- # configuration until one of the geocoders work.
21
+
22
+ # This method will call one or more geocoders in the order specified in
23
+ # the configuration until one of the geocoders work.
22
24
  #
23
- # The failover approach is crucial for production-grade apps, but is rarely used.
25
+ # The failover approach is crucial for production-grade apps, but is
26
+ # rarely used.
24
27
  # 98% of your geocoding calls will be successful with the first call
25
28
  def self.do_geocode(address, *args)
26
29
  provider_order = provider_order_for(address, args)
@@ -31,24 +34,26 @@ module Geokit
31
34
  res = klass.send :geocode, address, *args
32
35
  return res if res.success?
33
36
  rescue => e
34
- logger.error("An error has occurred during geocoding: #{e}\nAddress: #{address}. Provider: #{provider}")
37
+ logger.error("An error has occurred during geocoding: #{e}\n" +
38
+ "Address: #{address}. Provider: #{provider}")
35
39
  end
36
40
  end
37
41
  # If we get here, we failed completely.
38
42
  GeoLoc.new
39
43
  end
40
44
 
41
- # This method will call one or more geocoders in the order specified in the
42
- # configuration until one of the geocoders work, only this time it's going
43
- # to try to reverse geocode a geographical point.
45
+ # This method will call one or more geocoders in the order specified in
46
+ # the configuration until one of the geocoders work, only this time it's
47
+ # going to try to reverse geocode a geographical point.
44
48
  def self.do_reverse_geocode(latlng)
45
- Geokit::Geocoders::provider_order.each do |provider|
49
+ Geokit::Geocoders.provider_order.each do |provider|
46
50
  klass = geocoder(provider)
47
51
  begin
48
52
  res = klass.send :reverse_geocode, latlng
49
53
  return res if res.success?
50
54
  rescue => e
51
- logger.error("An error has occurred during geocoding: #{e}\nLatlng: #{latlng}. Provider: #{provider}")
55
+ logger.error("An error has occurred during geocoding: #{e}\n" +
56
+ "Latlng: #{latlng}. Provider: #{provider}")
52
57
  end
53
58
  end
54
59
  # If we get here, we failed completely.
@@ -56,7 +61,8 @@ module Geokit
56
61
  end
57
62
 
58
63
  def self.geocoder(provider)
59
- Geokit::Geocoders.const_get "#{Geokit::Inflector::camelize(provider.to_s)}Geocoder"
64
+ class_name = "#{Geokit::Inflector.camelize(provider.to_s)}Geocoder"
65
+ Geokit::Geocoders.const_get class_name
60
66
  end
61
67
 
62
68
  def self.provider_order_for(address, args)
@@ -64,13 +70,12 @@ module Geokit
64
70
  args.last.delete(:provider_order)
65
71
  else
66
72
  if /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.match(address)
67
- Geokit::Geocoders::ip_provider_order
73
+ Geokit::Geocoders.ip_provider_order
68
74
  else
69
- Geokit::Geocoders::provider_order
75
+ Geokit::Geocoders.provider_order
70
76
  end
71
77
  end
72
78
  end
73
79
  end
74
80
  end
75
81
  end
76
-
@@ -6,16 +6,19 @@ module Geokit
6
6
  req = Net::HTTP::Get.new(url)
7
7
  req.basic_auth(uri.user, uri.password) if uri.userinfo
8
8
  net_http_args = [uri.host, uri.port]
9
- if (proxy_uri_string = Geokit::Geocoders::proxy)
9
+ if (proxy_uri_string = Geokit::Geocoders.proxy)
10
10
  proxy_uri = URI.parse(proxy_uri_string)
11
- net_http_args += [proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password]
11
+ net_http_args += [proxy_uri.host,
12
+ proxy_uri.port,
13
+ proxy_uri.user,
14
+ proxy_uri.password]
12
15
  end
13
- http = Net::HTTP::new(*net_http_args)
16
+ http = Net::HTTP.new(*net_http_args)
14
17
  if uri.scheme == 'https'
15
18
  http.use_ssl = true
16
19
  http.verify_mode = Geokit::Geocoders.ssl_verify_mode
17
20
  end
18
- http.start { |http| http.request(req) }
21
+ http.start { |h| h.request(req) }
19
22
  end
20
23
 
21
24
  def self.success?(response)
@@ -1,13 +1,12 @@
1
1
  module Geokit
2
2
  # A complex polygon made of multiple points. End point must equal start point to close the poly.
3
3
  class Polygon
4
-
5
4
  attr_accessor :points
6
-
5
+
7
6
  # Pass in an array of Geokit::LatLng
8
7
  def initialize(points)
9
8
  @points = points
10
-
9
+
11
10
  # A Polygon must be 'closed', the last point equal to the first point
12
11
  # Append the first point to the array to close the polygon
13
12
  @points << points[0] if points[0] != points[-1]
@@ -30,11 +29,44 @@ module Geokit
30
29
  oddNodes = !oddNodes
31
30
  end
32
31
  end
33
-
32
+
34
33
  last_point = p
35
34
  end
36
35
 
37
36
  oddNodes
38
37
  end # contains?
38
+
39
+ # A polygon is static and can not be updated with new points, as a result
40
+ # calculate the centroid once and store it when requested.
41
+ def centroid
42
+ @centroid ||= calculate_centroid
43
+ end # end centroid
44
+
45
+ private
46
+
47
+ def calculate_centroid
48
+ centroid_lat = 0.0
49
+ centroid_lng = 0.0
50
+ signed_area = 0.0
51
+
52
+ # Iterate over each element in the list but the last item as it's
53
+ # calculated by the i+1 logic
54
+ @points[0...-1].each_index do |i|
55
+ x0 = @points[i].lat
56
+ y0 = @points[i].lng
57
+ x1 = @points[i + 1].lat
58
+ y1 = @points[i + 1].lng
59
+ a = (x0 * y1) - (x1 * y0)
60
+ signed_area += a
61
+ centroid_lat += (x0 + x1) * a
62
+ centroid_lng += (y0 + y1) * a
63
+ end
64
+
65
+ signed_area *= 0.5
66
+ centroid_lat /= (6.0 * signed_area)
67
+ centroid_lng /= (6.0 * signed_area)
68
+
69
+ Geokit::LatLng.new(centroid_lat, centroid_lng)
70
+ end # end calculate_centroid
39
71
  end # class Polygon
40
72
  end