geonames_api 0.0.6 → 0.1.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 (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