geocoder 1.2.5 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +83 -8
  4. data/lib/geocoder/ip_address.rb +11 -2
  5. data/lib/geocoder/lookup.rb +4 -0
  6. data/lib/geocoder/lookups/geoip2.rb +40 -0
  7. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  8. data/lib/geocoder/lookups/maxmind_local.rb +1 -1
  9. data/lib/geocoder/lookups/okf.rb +43 -0
  10. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  11. data/lib/geocoder/lookups/smarty_streets.rb +1 -1
  12. data/lib/geocoder/results/geoip2.rb +64 -0
  13. data/lib/geocoder/results/google_places_details.rb +35 -0
  14. data/lib/geocoder/results/okf.rb +106 -0
  15. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  16. data/lib/geocoder/results/test.rb +1 -1
  17. data/lib/geocoder/stores/active_record.rb +22 -11
  18. data/lib/geocoder/version.rb +1 -1
  19. data/test/fixtures/google_places_details_invalid_request +4 -0
  20. data/test/fixtures/google_places_details_madison_square_garden +120 -0
  21. data/test/fixtures/google_places_details_no_results +4 -0
  22. data/test/fixtures/google_places_details_no_reviews +60 -0
  23. data/test/fixtures/google_places_details_no_types +66 -0
  24. data/test/fixtures/okf_kirstinmaki +67 -0
  25. data/test/fixtures/okf_no_results +4 -0
  26. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_WR26NJ +1 -0
  27. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_generic_error +1 -0
  28. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_hampshire +1 -0
  29. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_key_limit_exceeded +1 -0
  30. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_no_results +1 -0
  31. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_romsey +1 -0
  32. data/test/fixtures/postcode_anywhere_uk_geocode_v2_00_unknown_key +1 -0
  33. data/test/test_helper.rb +44 -0
  34. data/test/unit/cache_test.rb +1 -1
  35. data/test/unit/error_handling_test.rb +3 -3
  36. data/test/unit/ip_address_test.rb +3 -0
  37. data/test/unit/lookup_test.rb +1 -1
  38. data/test/unit/lookups/geoip2_test.rb +27 -0
  39. data/test/unit/lookups/google_places_details_test.rb +122 -0
  40. data/test/unit/lookups/okf_test.rb +38 -0
  41. data/test/unit/lookups/postcode_anywhere_uk_test.rb +70 -0
  42. data/test/unit/query_test.rb +1 -0
  43. data/test/unit/test_mode_test.rb +1 -1
  44. metadata +28 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 47da8a4b65f982fc2063b17aa979e48bd0747ff8
4
- data.tar.gz: e248d9065a8228d98597639be3fb03cf012dc93b
3
+ metadata.gz: 27a73d81025d3d5faa23e10ae7db3c1d25e9825e
4
+ data.tar.gz: 740a6672d7cbfd97dc9c1e22a84473007674d4c5
5
5
  SHA512:
6
- metadata.gz: 5db5819f29c54c1923c31e80717ede54948f987fe086600f166a748b2b5b8c8d3cb021638f261792c95dfbeb13d7e243047e13351dc7edc205535b871b383361
7
- data.tar.gz: 3bbf41ec2823259eeffe13e6d90a0aac72710a4dd4e728caca64ed9d7dc550a9306cae0bcf9cd19d9ba6b6797a7b51f1e2c9720ef0bb9f88e9878b547c951c0c
6
+ metadata.gz: 9ae067878ed711962248b7a63bd8e322f3f0529cef013eb667f96b2c7b51b1f2a59c0ec9437fc79fb6a7338dc569c834abae6c903cf28339562ce6a4ff8db7f6
7
+ data.tar.gz: 44af9419d97670067cfde24878a7b893d2f0fa6b784102277089f53c9b43d796cf90a755c261851d23b268b902f48a2217147fdff935719d3574cf4b5915ea38
data/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@ Changelog
3
3
 
4
4
  Major changes to Geocoder for each release. Please see the Git log for complete list of changes.
5
5
 
6
+ 1.2.6 (2014 Nov 8)
7
+ -------------------
8
+ * Add :geoip2 lookup (thanks github.com/ChristianHoj).
9
+ * Add :okf lookup (thanks github.com/kakoni).
10
+ * Add :postcode_anywhere_uk lookup (thanks github.com/rob-murray).
11
+ * Properly detect IPv6 addresses (thanks github.com/sethherr and github.com/ignatiusreza).
12
+
6
13
  1.2.5 (2014 Sep 12)
7
14
  -------------------
8
15
  * Fix bugs in :opencagedata lookup (thanks github.com/duboff and kayakyakr).
data/README.md CHANGED
@@ -384,7 +384,7 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
384
384
 
385
385
  #### Google (`:google`, `:google_premier`)
386
386
 
387
- * **API key**: required for Premier (do NOT use a key for the free version)
387
+ * **API key**: required for Premier, optional for the free service (if using the free service with API key, https is required. Add `:use_https => true` to `Geocoder.configure`)
388
388
  * **Key signup**: https://developers.google.com/maps/documentation/business/
389
389
  * **Quota**: 2,500 requests/day, 100,000 with Google Maps API Premier
390
390
  * **Region**: world
@@ -396,6 +396,20 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
396
396
  * **Limitations**: "You must not use or display the Content without a corresponding Google map, unless you are explicitly permitted to do so in the Maps APIs Documentation, or through written permission from Google." "You must not pre-fetch, cache, or store any Content, except that you may store: (i) limited amounts of Content for the purpose of improving the performance of your Maps API Implementation..."
397
397
  * **Notes**: To use Google Premier set `Geocoder.configure(:lookup => :google_premier, :api_key => [key, client, channel])`.
398
398
 
399
+ #### Google Places Details (`:google_places_details`)
400
+
401
+ The [Google Places Details API](https://developers.google.com/places/documentation/details) is not, strictly speaking, a geocoding service. It accepts a Google `place_id` and returns address information, ratings and reviews. A `place_id` can be obtained from the Google Places Autocomplete API and should be passed to Geocoder as the first search argument: `Geocoder.search("ChIJhRwB-yFawokR5Phil-QQ3zM", :lookup => :google_places_details)`.
402
+
403
+ * **API key**: required
404
+ * **Key signup**: https://code.google.com/apis/console/
405
+ * **Quota**: 1,000 request/day, 100,000 after credit card authentication
406
+ * **Region**: world
407
+ * **SSL support**: yes
408
+ * **Languages**: ar, eu, bg, bn, ca, cs, da, de, el, en, en-AU, en-GB, es, eu, fa, fi, fil, fr, gl, gu, hi, hr, hu, id, it, iw, ja, kn, ko, lt, lv, ml, mr, nl, no, pl, pt, pt-BR, pt-PT, ro, ru, sk, sl, sr, sv, tl, ta, te, th, tr, uk, vi, zh-CN, zh-TW (see http://spreadsheets.google.com/pub?key=p9pdwsai2hDMsLkXsoM05KQ&gid=1)
409
+ * **Documentation**: https://developers.google.com/places/documentation/details
410
+ * **Terms of Service**: https://developers.google.com/places/policies
411
+ * **Limitations**: "If your application displays Places API data on a page or view that does not also display a Google Map, you must show a "Powered by Google" logo with that data."
412
+
399
413
  #### Yahoo BOSS (`:yahoo`)
400
414
 
401
415
  * **API key**: requires OAuth consumer key and secret (set `Geocoder.configure(:api_key => [key, secret])`)
@@ -410,7 +424,7 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
410
424
 
411
425
  #### Bing (`:bing`)
412
426
 
413
- * **API key**: required
427
+ * **API key**: required (set `Geocoder.configure(:lookup => :bing, :api_key => key)`)
414
428
  * **Key signup**: http://www.bingmapsportal.com
415
429
  * **Quota**: 50,000 requests/24 hrs
416
430
  * **Region**: world
@@ -565,7 +579,7 @@ Data Science Toolkit provides an API whose reponse format is like Google's but w
565
579
 
566
580
  #### SmartyStreets (`:smarty_streets`)
567
581
 
568
- * **API key**: required
582
+ * **API key**: requires auth_id and auth_token (set `Geocoder.configure(:api_key => [id, token])`)
569
583
  * **Quota**: 10,000 free, 250/month then purchase at sliding scale.
570
584
  * **Region**: US
571
585
  * **SSL support**: yes
@@ -574,6 +588,34 @@ Data Science Toolkit provides an API whose reponse format is like Google's but w
574
588
  * **Terms of Service**: http://smartystreets.com/legal/terms-of-service
575
589
  * **Limitations**: No reverse geocoding.
576
590
 
591
+
592
+ #### OKF Geocoder (`:okf`)
593
+
594
+ * **API key**: none
595
+ * **Quota**: none
596
+ * **Region**: FI
597
+ * **SSL support**: no
598
+ * **Languages**: fi
599
+ * **Documentation**: http://books.okf.fi/geocoder/_full/
600
+ * **Terms of Service**: http://www.itella.fi/liitteet/palvelutjatuotteet/yhteystietopalvelut/Postinumeropalvelut-Palvelukuvausjakayttoehdot.pdf
601
+ * **Limitations**: ?
602
+
603
+
604
+ #### PostcodeAnywhere Uk (`:postcode_anywhere_uk`)
605
+
606
+ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string from UK postcode, placename, point of interest or location.
607
+
608
+ * **API key**: required
609
+ * **Quota**: Dependant on service plan?
610
+ * **Region**: UK
611
+ * **SSL support**: yes
612
+ * **Languages**: English
613
+ * **Documentation**: [http://www.postcodeanywhere.co.uk/Support/WebService/Geocoding/UK/Geocode/2/](http://www.postcodeanywhere.co.uk/Support/WebService/Geocoding/UK/Geocode/2/)
614
+ * **Terms of Service**: ?
615
+ * **Limitations**: ?
616
+ * **Notes**: To use PostcodeAnywhere you must include an API key: `Geocoder.configure(:lookup => :postcode_anywhere_uk, :api_key => 'your_api_key')`.
617
+
618
+
577
619
  ### IP Address Services
578
620
 
579
621
  #### FreeGeoIP (`:freegeoip`)
@@ -647,12 +689,11 @@ This lookup provides methods for geocoding IP addresses without making a call to
647
689
 
648
690
  You can generate ActiveRecord migrations and download and import data via provided rake tasks:
649
691
 
692
+ # generate migration to create tables
650
693
  rails generate geocoder:maxmind:geolite PACKAGE=city
651
694
 
652
- rake geocoder:maxmind:geolite:download PACKAGE=city
653
- rake geocoder:maxmind:geolite:extract PACKAGE=city
654
- rake geocoder:maxmind:geolite:insert PACKAGE=city
655
- rake geocoder:maxmind:geolite:load PACKAGE=city # runs the above three in sequence
695
+ # download, unpack, and import data
696
+ rake geocoder:maxmind:geolite:load PACKAGE=city
656
697
 
657
698
  You can replace `city` with `country` in any of the above tasks, generators, and configurations.
658
699
 
@@ -668,6 +709,36 @@ You can replace `city` with `country` in any of the above tasks, generators, and
668
709
  * **Limitations**: Only good for non-commercial use. For commercial usage please check http://developer.baidu.com/map/question.htm#qa0013
669
710
  * **Notes**: To use Baidu set `Geocoder.configure(:lookup => :baidu_ip, :api_key => "your_api_key")`.
670
711
 
712
+ #### GeoLite2 (`:geoip2`)
713
+
714
+ This lookup provides methods for geocoding IP addresses without making a call to a remote API (improves speed and availability). It works, but support is new and should not be considered production-ready. Please [report any bugs](https://github.com/alexreisner/geocoder/issues) you encounter.
715
+
716
+ * **API key**: none (requires a GeoIP2 or free GeoLite2 City or Country binary database which can be downloaded from [MaxMind](http://dev.maxmind.com/geoip/geoip2/geoip2/))
717
+ * **Quota**: none
718
+ * **Region**: world
719
+ * **SSL support**: N/A
720
+ * **Languages**: English
721
+ * **Documentation**: http://www.maxmind.com/en/city
722
+ * **Terms of Service**: ?
723
+ * **Limitations**: ?
724
+ * **Notes**: **You must download a binary database file from MaxMind and set the `:file` configuration option.** The CSV format databases are not yet supported since they are still in alpha stage. Set the path to the database file in your configuration:
725
+
726
+ Geocoder.configure(
727
+ ip_lookup: :geoip2,
728
+ geoip2: {
729
+ file: File.join('folder', 'GeoLite2-City.mmdb')
730
+ }
731
+ )
732
+
733
+ You must add either the *[hive_geoip2](https://rubygems.org/gems/hive_geoip2)* gem (native extension that relies on libmaxminddb) or the *[maxminddb](http://rubygems.org/gems/maxminddb)* gem (pure Ruby implementation) to your Gemfile or have it installed in your system. The pure Ruby gem (maxminddb) will be used by default. To use `hive_geoip2`:
734
+
735
+ Geocoder.configure(
736
+ ip_lookup: :geoip2,
737
+ geoip2: {
738
+ lib: 'hive_geoip2',
739
+ file: File.join('folder', 'GeoLite2-City.mmdb')
740
+ }
741
+ )
671
742
 
672
743
  Caching
673
744
  -------
@@ -947,12 +1018,16 @@ A lot of debugging time can be saved by understanding how Geocoder works with Ac
947
1018
 
948
1019
  ### Unexpected Responses from Geocoding Services
949
1020
 
950
- Take a look at the server's raw JSON response. You can do this by getting the request URL in an app console:
1021
+ Take a look at the server's raw response. You can do this by getting the request URL in an app console:
951
1022
 
952
1023
  Geocoder::Lookup.get(:google).query_url(Geocoder::Query.new("..."))
953
1024
 
954
1025
  Replace `:google` with the lookup you are using and replace `...` with the address you are trying to geocode. Then visit the returned URL in your web browser. Often the API will return an error message that helps you resolve the problem. If, after reading the raw response, you believe there is a problem with Geocoder, please post an issue and include both the URL and raw response body.
955
1026
 
1027
+ You can also fetch the response in the console:
1028
+
1029
+ Geocoder::Lookup.get(:google).send(:fetch_raw_data, Geocoder::Query.new("..."))
1030
+
956
1031
 
957
1032
  Reporting Issues
958
1033
  ----------------
@@ -2,11 +2,20 @@ module Geocoder
2
2
  class IpAddress < String
3
3
 
4
4
  def loopback?
5
- valid? and (self == "0.0.0.0" or self.match(/\A127\./))
5
+ valid? and (self == "0.0.0.0" or self.match(/\A127\./) or self == "::1")
6
6
  end
7
7
 
8
8
  def valid?
9
- !!self.match(/\A(::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\z/)
9
+ ipregex = %r{
10
+ \A( # String Starts
11
+ ((::ffff:)?((\d{1,3})\.){3}\d{1,3}) # Check for IPv4
12
+ | # .... Or
13
+ (\S+?(:\S+?){6}\S+) # Check for IPv6
14
+ | # .... Or
15
+ (::1) # IPv6 loopback
16
+ )\z
17
+ }x
18
+ !!self.match(ipregex)
10
19
  end
11
20
  end
12
21
  end
@@ -25,6 +25,7 @@ module Geocoder
25
25
  :esri,
26
26
  :google,
27
27
  :google_premier,
28
+ :google_places_details,
28
29
  :yahoo,
29
30
  :bing,
30
31
  :geocoder_ca,
@@ -38,6 +39,8 @@ module Geocoder
38
39
  :baidu,
39
40
  :geocodio,
40
41
  :smarty_streets,
42
+ :okf,
43
+ :postcode_anywhere_uk,
41
44
  :test
42
45
  ]
43
46
  end
@@ -49,6 +52,7 @@ module Geocoder
49
52
  [
50
53
  :baidu_ip,
51
54
  :freegeoip,
55
+ :geoip2,
52
56
  :maxmind,
53
57
  :maxmind_local,
54
58
  :telize,
@@ -0,0 +1,40 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/geoip2'
3
+
4
+ module Geocoder
5
+ module Lookup
6
+ class Geoip2 < Base
7
+ def initialize
8
+ unless configuration[:file].nil?
9
+ begin
10
+ @gem_name = configuration[:lib] || 'maxminddb'
11
+ require @gem_name
12
+ rescue LoadError
13
+ raise "Could not load Maxmind DB dependency. To use the GeoIP2 lookup you must add the #{@gem_name} gem to your Gemfile or have it installed in your system."
14
+ end
15
+ end
16
+ super
17
+ end
18
+
19
+ def name
20
+ 'GeoIP2'
21
+ end
22
+
23
+ def required_api_key_parts
24
+ []
25
+ end
26
+
27
+ private
28
+
29
+ def results(query)
30
+ return [] unless configuration[:file]
31
+ if @gem_name == 'hive_geoip2'
32
+ result = Hive::GeoIP2.lookup(query.to_s, configuration[:file].to_s)
33
+ else
34
+ result = MaxMindDB.new(configuration[:file].to_s).lookup(query.to_s)
35
+ end
36
+ result.nil? ? [] : [result]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ require "geocoder/lookups/google"
2
+ require "geocoder/results/google_places_details"
3
+
4
+ module Geocoder
5
+ module Lookup
6
+ class GooglePlacesDetails < Google
7
+ def name
8
+ "Google Places Details"
9
+ end
10
+
11
+ def required_api_key_parts
12
+ ["key"]
13
+ end
14
+
15
+ def use_ssl?
16
+ true
17
+ end
18
+
19
+ def query_url(query)
20
+ "#{protocol}://maps.googleapis.com/maps/api/place/details/json?#{url_query_string(query)}"
21
+ end
22
+
23
+ private
24
+
25
+ def results(query)
26
+ return [] unless doc = fetch_data(query)
27
+
28
+ case doc["status"]
29
+ when "OK"
30
+ return [doc["result"]]
31
+ when "OVER_QUERY_LIMIT"
32
+ raise_error(Geocoder::OverQueryLimitError) || warn("Google Places Details API error: over query limit.")
33
+ when "REQUEST_DENIED"
34
+ raise_error(Geocoder::RequestDenied) || warn("Google Places Details API error: request denied.")
35
+ when "INVALID_REQUEST"
36
+ raise_error(Geocoder::InvalidRequest) || warn("Google Places Details API error: invalid request.")
37
+ end
38
+
39
+ []
40
+ end
41
+
42
+ def query_url_google_params(query)
43
+ {
44
+ placeid: query.text,
45
+ language: query.language || configuration.language
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -36,7 +36,7 @@ module Geocoder::Lookup
36
36
  addr = IPAddr.new(query.text).to_i
37
37
  q = "SELECT l.country, l.region, l.city, l.latitude, l.longitude
38
38
  FROM maxmind_geolite_city_location l WHERE l.loc_id = (SELECT b.loc_id FROM maxmind_geolite_city_blocks b
39
- WHERE b.start_ip_num <= #{addr} AND #{addr} <= b.end_ip_num)"
39
+ WHERE b.start_ip_num <= #{addr} AND #{addr} <= b.end_ip_num LIMIT 1)"
40
40
  format_result(q, [:country_name, :region_name, :city_name, :latitude, :longitude])
41
41
  elsif configuration[:package] == :country
42
42
  addr = IPAddr.new(query.text).to_i
@@ -0,0 +1,43 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/okf"
3
+
4
+ module Geocoder::Lookup
5
+ class Okf < Base
6
+
7
+ def name
8
+ "Okf"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://data.okf.fi/gis/1/geocode/json?" + url_query_string(query)
13
+ end
14
+
15
+ private # ---------------------------------------------------------------
16
+
17
+ def valid_response?(response)
18
+ status = parse_json(response.body)["status"]
19
+ super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
20
+ end
21
+
22
+ def results(query)
23
+ return [] unless doc = fetch_data(query)
24
+ case doc['status']; when "OK" # OK status implies >0 results
25
+ return doc['results']
26
+ end
27
+ return []
28
+ end
29
+
30
+ def query_url_okf_params(query)
31
+ params = {
32
+ (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
33
+ :sensor => "false",
34
+ :language => (query.language || configuration.language)
35
+ }
36
+ params
37
+ end
38
+
39
+ def query_url_params(query)
40
+ query_url_okf_params(query).merge(super)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/postcode_anywhere_uk'
3
+
4
+ module Geocoder::Lookup
5
+ class PostcodeAnywhereUk < Base
6
+ # API documentation: http://www.postcodeanywhere.co.uk/Support/WebService/Geocoding/UK/Geocode/2/
7
+ BASE_URL_GEOCODE_V2_00 = 'services.postcodeanywhere.co.uk/Geocoding/UK/Geocode/v2.00/json.ws'
8
+ DAILY_LIMIT_EXEEDED_ERROR_CODES = ['8', '17'] # api docs say these two codes are the same error
9
+ INVALID_API_KEY_ERROR_CODE = '2'
10
+
11
+ def name
12
+ 'PostcodeAnywhereUk'
13
+ end
14
+
15
+ def required_api_key_parts
16
+ %w(key)
17
+ end
18
+
19
+ def query_url(query)
20
+ format('%s://%s?%s', protocol, BASE_URL_GEOCODE_V2_00, url_query_string(query))
21
+ end
22
+
23
+ private
24
+
25
+ def results(query)
26
+ response = fetch_data(query)
27
+ return [] if response.nil? || !response.is_a?(Array) || response.empty?
28
+
29
+ raise_exception_for_response(response[0]) if response[0]['Error']
30
+ response
31
+ end
32
+
33
+ def raise_exception_for_response(response)
34
+ case response['Error']
35
+ when *DAILY_LIMIT_EXEEDED_ERROR_CODES
36
+ raise_error(Geocoder::OverQueryLimitError, response['Cause']) || warn(response['Cause'])
37
+ when INVALID_API_KEY_ERROR_CODE
38
+ raise_error(Geocoder::InvalidApiKey, response['Cause']) || warn(response['Cause'])
39
+ else # anything else just raise general error with the api cause
40
+ raise_error(Geocoder::Error, response['Cause']) || warn(response['Cause'])
41
+ end
42
+ end
43
+
44
+ def query_url_params(query)
45
+ {
46
+ :location => query.sanitized_text,
47
+ :key => configuration.api_key
48
+ }.merge(super)
49
+ end
50
+ end
51
+ end
@@ -8,7 +8,7 @@ module Geocoder::Lookup
8
8
  end
9
9
 
10
10
  def required_api_key_parts
11
- %w(auth-token)
11
+ %w(auti-id auth-token)
12
12
  end
13
13
 
14
14
  def query_url(query)
@@ -0,0 +1,64 @@
1
+ require 'geocoder/results/base'
2
+
3
+ module Geocoder
4
+ module Result
5
+ class Geoip2 < Base
6
+ def address(format = :full)
7
+ s = state.to_s == '' ? '' : ", #{state_code}"
8
+ "#{city}#{s} #{postal_code}, #{country}".sub(/^[ ,]*/, '')
9
+ end
10
+
11
+ def coordinates
12
+ [latitude, longitude]
13
+ end
14
+
15
+ def latitude
16
+ return 0.0 unless @data['location']
17
+ @data['location']['latitude'].to_f
18
+ end
19
+
20
+ def longitude
21
+ return 0.0 unless @data['location']
22
+ @data['location']['longitude'].to_f
23
+ end
24
+
25
+ def city
26
+ return '' unless @data['city']
27
+ @data['city']['names']['en']
28
+ end
29
+
30
+ def state
31
+ return '' unless @data['subdivisions']
32
+ @data['subdivisions'][0]['names']['en']
33
+ end
34
+
35
+ def state_code
36
+ return '' unless @data['subdivisions']
37
+ @data['subdivisions'][0]['iso_code']
38
+ end
39
+
40
+ def country
41
+ @data['country']['names']['en']
42
+ end
43
+
44
+ def country_code
45
+ @data['country']['iso_code']
46
+ end
47
+
48
+ def postal_code
49
+ return '' unless @data['postal']
50
+ @data['postal']['code']
51
+ end
52
+
53
+ def self.response_attributes
54
+ %w[ip]
55
+ end
56
+
57
+ response_attributes.each do |a|
58
+ define_method a do
59
+ @data[a]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end