geonames_api 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +28 -0
  5. data/README.md +13 -2
  6. data/Rakefile +13 -0
  7. data/geonames_api.gemspec +7 -5
  8. data/lib/geonames_api.rb +30 -19
  9. data/lib/geonames_api/alternate_names.rb +54 -0
  10. data/lib/geonames_api/base.rb +92 -0
  11. data/lib/geonames_api/children.rb +85 -0
  12. data/lib/geonames_api/city.rb +8 -0
  13. data/lib/geonames_api/country.rb +6 -6
  14. data/lib/geonames_api/country_code.rb +8 -0
  15. data/lib/geonames_api/country_subdivision.rb +8 -0
  16. data/lib/geonames_api/earthquake.rb +8 -0
  17. data/lib/geonames_api/elevation.rb +16 -0
  18. data/lib/geonames_api/entity.rb +64 -0
  19. data/lib/geonames_api/error.rb +25 -2
  20. data/lib/geonames_api/geoname.rb +4 -0
  21. data/lib/geonames_api/hierarchy.rb +10 -0
  22. data/lib/geonames_api/list_endpoint.rb +21 -0
  23. data/lib/geonames_api/nearby_postal_code.rb +17 -0
  24. data/lib/geonames_api/place.rb +8 -0
  25. data/lib/geonames_api/place_name.rb +8 -0
  26. data/lib/geonames_api/place_search.rb +16 -0
  27. data/lib/geonames_api/postal_code.rb +23 -0
  28. data/lib/geonames_api/singleton_endpoint.rb +7 -0
  29. data/lib/geonames_api/street.rb +8 -0
  30. data/lib/geonames_api/time_zone.rb +17 -14
  31. data/lib/geonames_api/version.rb +2 -2
  32. data/lib/geonames_api/weather.rb +16 -16
  33. data/lib/geonames_api/weather_i_c_a_o.rb +8 -0
  34. data/lib/geonames_api/wikipedia.rb +5 -5
  35. data/spec/geonames_api/country_subdivision_spec.rb +27 -0
  36. data/spec/geonames_api/hierarchy_spec.rb +38 -0
  37. data/spec/geonames_api/place_name_spec.rb +22 -0
  38. data/spec/geonames_api/place_search_spec.rb +50 -0
  39. data/spec/geonames_api/place_spec.rb +17 -0
  40. data/spec/geonames_api/retry_spec.rb +37 -0
  41. data/spec/geonames_api/street_spec.rb +22 -0
  42. data/spec/geonames_api/weather_icao_spec.rb +10 -0
  43. data/spec/spec_helper.rb +15 -0
  44. metadata +97 -21
  45. data/lib/geonames_api/hash.rb +0 -5
  46. data/lib/geonames_api/object.rb +0 -72
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e01b04a032c053533c4b76638fc31b4777f5b8e
4
+ data.tar.gz: bd3e6b7a88b1376282220737a1d2870046db9af4
5
+ SHA512:
6
+ metadata.gz: f175f17720e7a8e6b3b404320593f1f7a2a6841d0c63bae16b95d6f5dc4c40a779b8e0b95f4902ea93da69f280b98718ce25ed2722b2586e0084057fbffc4e27
7
+ data.tar.gz: 088b25cd53a9c1533aa67df6b11d709020c45d67514911384859ef1b7fa28aeef94b10c490197e559b8ab9e43bbe942bdc3f4a5bb35702bd67d7d099165ecc63
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .rvmrc
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
@@ -15,3 +16,4 @@ spec/reports
15
16
  test/tmp
16
17
  test/version_tmp
17
18
  tmp
19
+ test.log
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ # - 1.8.7 is not supported due to a 1.9-style hash in geonames_api.rb :\
6
+
7
+ script: GEONAMES_USER=geonames_api_ci bundle exec rake --trace
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## 0.1.0
2
+
3
+ ### New features:
4
+
5
+ * When the GeoNames API returns a database or server timeout, your request
6
+ will be retried at most ```GeoNamesAPI.retries``` times. Default is 2, and
7
+ the delay between requests is at most ```GeoNamesAPI.max_sleep_time_between_retries```
8
+ (which defaults to 5 seconds).
9
+
10
+ * Several endpoints accept multiple param sets, and the order of the parameters
11
+ is not always in the order or priority, so ```find``` and ```all``` and ```where```
12
+ now also accept a parameter hash.
13
+
14
+ * ```GeoNamesAPI::Hierarchy``` is now an Enumerable of ```GeoName``` instances, as
15
+ all responses will have an ordered set of those entity types.
16
+
17
+ * URL parameters are properly encoded now.
18
+
19
+ * For paid users, set ```GeoNamesAPI.token``` and set the ```GeoNamesAPI.url```
20
+ to the ```https``` endpoint.
21
+
22
+ * Callers can rescue on ```GeoNamesAPI::InvalidParameter``` and ```GeoNamesAPI::InvalidInput``` now.
23
+
24
+ * GeoNamesAPI.formatted was deleted. The consumer shouldn't care if the JSON response
25
+ was pretty-printed.
26
+
27
+ * Timezones, AlternateNames, and GeoName entities are encoded as class instances now
28
+
data/README.md CHANGED
@@ -1,12 +1,23 @@
1
1
  # GeoNames API
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/mceachen/geonames_api.png?branch=master)](http://travis-ci.org/mceachen/geonames_api)
4
+ [![Code Climate](https://codeclimate.com/github/mceachen/geonames_api.png)](https://codeclimate.com/github/mceachen/geonames_api)
5
+
3
6
  This is a lightweight client for the [GeoNames](http://www.geonames.org) API. Huge thanks to them for such a great service!
4
7
 
5
8
  There are many GeoNames API clients. BUT, most are rewritten versions of a Java API whose interface is a little funny =|
6
9
 
7
10
  This is a simplified ruby implementation that does not implement the entire API. But, its lightweight and has a nice interface and will be easy to extend :)
8
11
 
9
- The gem was written by [@barelyknown](http://twitter.com/barelyknown).
12
+ The gem was originally written by [@barelyknown](http://twitter.com/barelyknown).
13
+
14
+ This fork adds
15
+ * automatic retries on timeout,
16
+ * properly encoded url parameters,
17
+ * support for paid users (with https and API tokens)
18
+ * more consistent ```find``` and ```where``` methods across endpoints
19
+ * Timezone, AlternateName, and GeoName entries properly encoded in results
20
+ * better test coverage, Travis CI, and CodeClimate integration
10
21
 
11
22
  ## Getting Started
12
23
 
@@ -184,4 +195,4 @@ The other services will be implemented as needed. In the mean time, feel free to
184
195
  3. Commit your changes (`git commit -am 'Add some feature'`)
185
196
  4. Push to the branch (`git push origin my-new-feature`)
186
197
  5. Create new Pull Request
187
- 6. Thanks <3
198
+ 6. Thanks <3
data/Rakefile CHANGED
@@ -1 +1,14 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ end
8
+
9
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
10
+ spec.pattern = 'spec/**/*_spec.rb'
11
+ spec.rcov = true
12
+ end
13
+
14
+ task :default => :spec
data/geonames_api.gemspec CHANGED
@@ -6,17 +6,19 @@ require 'geonames_api/version'
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "geonames_api"
8
8
  gem.version = GeoNamesAPI::VERSION
9
- gem.authors = ["Sean Devine"]
10
- gem.email = ["barelyknown@icloud.com"]
9
+ gem.authors = ["Sean Devine", "Matthew McEachen"]
10
+ gem.email = ["barelyknown@icloud.com", "matthew-github@mceachen.org"]
11
11
  gem.description = %q{Simple ruby client for the GeoNames API to get free and easy geographic info.}
12
- gem.summary = %q{This is a lightweight client for the GeoNames API. Huge thanks to them for such a great service! There are many GeoNames API clients. BUT, most are rewritten versions of a Java API whose interface is a little funny =| This is a simplified ruby implementation that does not implement the entire API. But, its lightweight and has a nice interface and will be easy to extend :)}
12
+ gem.summary = %q{This is a lightweight client for the GeoNames API. Huge thanks to them for such a great service! There are many GeoNames API clients. BUT, most are rewritten versions of a Java API whose interface is a little funny =| This is a simplified ruby implementation that does not implement the entire API. But, it's lightweight and has a nice interface and will be easy to extend :)}
13
13
  gem.homepage = "https://github.com/buytruckload/geonames_api"
14
14
 
15
15
  gem.add_runtime_dependency "activesupport"
16
+ gem.add_runtime_dependency "tzinfo"
16
17
  gem.add_runtime_dependency "zipruby"
17
-
18
+ gem.add_development_dependency 'rake'
19
+ gem.add_development_dependency 'rspec'
20
+
18
21
  gem.files = `git ls-files`.split($/)
19
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
22
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
23
  gem.require_paths = ["lib"]
22
24
  end
data/lib/geonames_api.rb CHANGED
@@ -3,34 +3,45 @@ require 'json'
3
3
  require 'csv'
4
4
  require 'active_support/all'
5
5
  require 'zipruby'
6
- require "geonames_api/version"
7
- require "geonames_api/hash"
8
- require "geonames_api/error"
9
- require "geonames_api/object"
10
- require "geonames_api/country"
11
- require "geonames_api/weather"
12
- require "geonames_api/time_zone"
13
- require "geonames_api/wikipedia"
14
6
 
15
7
  module GeoNamesAPI
16
-
8
+
17
9
  mattr_accessor :url
18
- @@url = "http://api.geonames.org/"
10
+ self.url = 'http://api.geonames.org/'
19
11
 
20
- mattr_accessor :formatted
21
- @@formatted = true
22
-
23
12
  mattr_accessor :lang
24
- @@lang = :en
13
+ self.lang = :en
25
14
 
26
15
  mattr_accessor :username
27
- @@username = "demo"
28
-
16
+ self.username = 'demo'
17
+
18
+ mattr_accessor :token
19
+ self.token = nil
20
+
29
21
  mattr_accessor :style
30
- @@style = :full
31
-
22
+ self.style = :full
23
+
24
+ mattr_accessor :logger
25
+ self.logger = nil
26
+
27
+ mattr_accessor :retries
28
+ self.retries = 3
29
+
30
+ mattr_accessor :max_sleep_time_between_retries
31
+ self.max_sleep_time_between_retries = 5
32
+
32
33
  def self.params
33
- { formatted: formatted, lang: lang, username: username, style: style }
34
+ {
35
+ lang: lang,
36
+ username: username,
37
+ token: token,
38
+ style: style
39
+ }.delete_if{ |k, v| v.blank? }
34
40
  end
35
41
 
36
42
  end
43
+
44
+ Dir[File.dirname(__FILE__) + '/geonames_api/*.rb'].each do |file|
45
+ tgt = File.basename(file, File.extname(file))
46
+ GeoNamesAPI.autoload tgt.camelize, "geonames_api/#{tgt}"
47
+ end
@@ -0,0 +1,54 @@
1
+ module GeoNamesAPI
2
+ # Arrays of Geoname instances are returned by `Hierarchy.find`
3
+ class AlternateNames
4
+
5
+ def initialize(alternate_names_array)
6
+ @name_by_lang = {}
7
+ alternate_names_array.each do |hash|
8
+ @name_by_lang[hash['lang']] = hash['name']
9
+ end
10
+ end
11
+
12
+ def [](lang)
13
+ @name_by_lang[lang]
14
+ end
15
+ end
16
+ end
17
+
18
+ =begin
19
+ Expected input:
20
+ [
21
+ {
22
+ "name": "els Abruços",
23
+ "lang": "ca"
24
+ },
25
+ {
26
+ "name": "Abruzzen",
27
+ "lang": "de"
28
+ },
29
+ {
30
+ "name": "Abruzzo",
31
+ "lang": "en"
32
+ },
33
+ {
34
+ "name": "Los Abruzos",
35
+ "lang": "es"
36
+ },
37
+ {
38
+ "name": "Abruzzes",
39
+ "lang": "fr"
40
+ },
41
+ {
42
+ "name": "Abruzzo",
43
+ "lang": "it"
44
+ },
45
+ {
46
+ "name": "http://en.wikipedia.org/wiki/Abruzzo",
47
+ "lang": "link"
48
+ },
49
+ {
50
+ "name": "Abruzzen",
51
+ "lang": "nl"
52
+ }
53
+ ]
54
+ =end
@@ -0,0 +1,92 @@
1
+ require 'cgi'
2
+
3
+ module GeoNamesAPI
4
+ class Base < Entity
5
+
6
+ def self.find(*names_or_params)
7
+ result = where(name_params(names_or_params))
8
+ if result
9
+ endpoint_returns_list? ? result.first : result
10
+ end
11
+ end
12
+
13
+ def self.all(*names_or_params)
14
+ result = where(name_params(names_or_params))
15
+ if result
16
+ endpoint_returns_list? ? result : [result]
17
+ end
18
+ end
19
+
20
+ def self.where(params={})
21
+ retries_remaining = GeoNamesAPI.retries
22
+ url = url(params)
23
+ begin
24
+ response = make_request(url)
25
+ unless response.empty?
26
+ parse_response(response, params)
27
+ end
28
+ rescue Timeout => e
29
+ if retries_remaining > 0
30
+ retries_remaining -= 1
31
+ puts "retrying!"
32
+ sleep rand * GeoNamesAPI.max_sleep_time_between_retries
33
+ retry
34
+ else
35
+ raise e
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.make_request(url)
41
+ JSON.load(open(url).read)
42
+ end
43
+
44
+ private_class_method :make_request
45
+
46
+ KEYS = %w(streetSegment geonames)
47
+
48
+ def self.parse_response(response, request_params)
49
+ GeoNamesAPI.logger.info "GEONAMES RESPONSE (#{Time.now}): #{response}" if GeoNamesAPI.logger
50
+ if (status = response['status'])
51
+ raise Error.from_status(status)
52
+ end
53
+ new(response, request_params)
54
+ end
55
+
56
+ private_class_method :parse_response
57
+
58
+ def self.url(params={})
59
+ endpoint = GeoNamesAPI.url + self::METHOD + params_to_url(GeoNamesAPI.params.merge(params))
60
+ GeoNamesAPI.logger.info "GEONAMES REQUEST (#{Time.now}): #{endpoint}" if GeoNamesAPI.logger
61
+ endpoint
62
+ end
63
+
64
+ private_class_method :url
65
+
66
+ def self.name_params(names)
67
+ return names.first if names.first.is_a? Hash
68
+ params, n = {}, 0
69
+ if names.any?
70
+ [self::FIND_PARAMS].flatten.each { |i| params[i] = names[n]; n+= 1 }
71
+ end
72
+ params.delete_if { |k, v| v.blank? }
73
+ end
74
+
75
+ private_class_method :name_params
76
+
77
+ def self.params_to_url(params={})
78
+ esc_params = params.map do |key, value|
79
+ "#{esc(key)}=#{esc(value)}"
80
+ end
81
+ "?#{esc_params.join('&')}"
82
+ end
83
+
84
+ private_class_method :params_to_url
85
+
86
+ def self.esc(str)
87
+ CGI::escape(str.to_s)
88
+ end
89
+
90
+ private_class_method :esc
91
+ end
92
+ end
@@ -0,0 +1,85 @@
1
+ module GeoNamesAPI
2
+ class Children < ListEndpoint
3
+
4
+ METHOD = "childrenJSON"
5
+ FIND_PARAMS = %w(geonameId)
6
+
7
+ end
8
+ end
9
+
10
+ =begin
11
+ {
12
+ "totalResultsCount": 20,
13
+ "geonames": [
14
+ {
15
+ "alternateNames": [
16
+ {
17
+ "name": "els Abruços",
18
+ "lang": "ca"
19
+ },
20
+ {
21
+ "name": "Abruzzen",
22
+ "lang": "de"
23
+ },
24
+ {
25
+ "name": "Abruzzo",
26
+ "lang": "en"
27
+ },
28
+ {
29
+ "name": "Los Abruzos",
30
+ "lang": "es"
31
+ },
32
+ {
33
+ "name": "Abruzzes",
34
+ "lang": "fr"
35
+ },
36
+ {
37
+ "name": "Abruzzo",
38
+ "lang": "it"
39
+ },
40
+ {
41
+ "name": "http://en.wikipedia.org/wiki/Abruzzo",
42
+ "lang": "link"
43
+ },
44
+ {
45
+ "name": "Abruzzen",
46
+ "lang": "nl"
47
+ }
48
+ ],
49
+ "countryName": "Italy",
50
+ "adminCode1": "01",
51
+ "lng": "13.75",
52
+ "adminName2": "",
53
+ "fcodeName": "first-order administrative division",
54
+ "adminName3": "",
55
+ "timezone": {
56
+ "dstOffset": 2,
57
+ "gmtOffset": 1,
58
+ "timeZoneId": "Europe/Rome"
59
+ },
60
+ "adminName4": "",
61
+ "adminName5": "",
62
+ "bbox": {
63
+ "south": 41.68307876586914,
64
+ "east": 14.783888816833496,
65
+ "north": 42.8957405090332,
66
+ "west": 13.019405364990234
67
+ },
68
+ "name": "Abruzzo",
69
+ "fcode": "ADM1",
70
+ "geonameId": 3183560,
71
+ "lat": "42.25",
72
+ "population": 1338898,
73
+ "adminName1": "Abruzzo",
74
+ "countryId": "3175395",
75
+ "adminId1": "3183560",
76
+ "fclName": "country, state, region,...",
77
+ "countryCode": "IT",
78
+ "wikipediaURL": "",
79
+ "toponymName": "Regione Abruzzo",
80
+ "fcl": "A",
81
+ "numberOfChildren": 4,
82
+ "continentCode": "EU"
83
+ },
84
+
85
+ =end
@@ -0,0 +1,8 @@
1
+ module GeoNamesAPI
2
+ class City < ListEndpoint
3
+
4
+ METHOD = "citiesJSON"
5
+ FIND_PARAMS = %w(north south east west maxRows)
6
+
7
+ end
8
+ end
@@ -1,8 +1,8 @@
1
1
  module GeoNamesAPI
2
- class Country < GeoNamesAPI::Object
2
+ class Country < ListEndpoint
3
3
 
4
4
  METHOD = "countryInfoJSON"
5
- ID = "country"
5
+ FIND_PARAMS = %w(country)
6
6
 
7
7
  EXPORT_BASE_URL = "http://download.geonames.org/export/zip/"
8
8
  EXPORT_HEADERS = %W(country_code postal_code place_name admin_name1 admin_code1 admin_name2 admin_code2 admin_name3 admin_code3 latitude longitude accuracy)
@@ -18,14 +18,14 @@ module GeoNamesAPI
18
18
  end
19
19
  csv
20
20
  end
21
-
21
+
22
22
  def postal_code_csv
23
23
  CSV.parse(postal_code_export, headers: true, col_sep: "\t", header_converters: :symbol, encoding: "ISO8859-1")
24
24
  end
25
-
25
+
26
26
  def postal_code_export_url
27
27
  EXPORT_BASE_URL + country_code + ".zip"
28
28
  end
29
-
29
+
30
30
  end
31
- end
31
+ end