sora_geocoding 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.
@@ -0,0 +1,59 @@
1
+ require 'logger'
2
+ require 'singleton'
3
+
4
+ module SoraGeocoding
5
+ def self.log(level, message)
6
+ Logger.instance.log(level, message)
7
+ end
8
+
9
+ #
10
+ # Logger
11
+ #
12
+ class Logger
13
+ include Singleton
14
+
15
+ LOG_LEVEL = {
16
+ debug: ::Logger::DEBUG,
17
+ info: ::Logger::INFO,
18
+ warn: ::Logger::WARN,
19
+ error: ::Logger::ERROR,
20
+ fatal: ::Logger::FATAL
21
+ }.freeze
22
+
23
+ def log(level, message)
24
+ raise StandardError, 'SoraGeocoding tried to log a message with an invalid log level.' unless valid_level?(level)
25
+
26
+ if respond_to?(:add)
27
+ add(LOG_LEVEL[level], message)
28
+ else
29
+ raise SoraGeocoding::ConfigurationError, 'Please specify valid logger for SoraGeocoding. ' \
30
+ 'Logger specified must respond to `add(level, message)`.'
31
+ end
32
+ nil
33
+ end
34
+
35
+ def add(level, message)
36
+ return unless log_message_at_level?(level)
37
+
38
+ case level
39
+ when ::Logger::DEBUG, ::Logger::INFO
40
+ puts message
41
+ when ::Logger::WARN
42
+ warn message
43
+ when ::Logger::ERROR
44
+ raise message
45
+ when ::Logger::FATAL
46
+ raise message
47
+ end
48
+ end
49
+
50
+ private
51
+ def valid_level?(level)
52
+ LOG_LEVEL.keys.include?(level)
53
+ end
54
+
55
+ def log_message_at_level?(level)
56
+ level >= SoraGeocoding.config.logger_level
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ module SoraGeocoding
2
+ class Query < Base
3
+ attr_accessor :query, :opts, :url, :req
4
+
5
+ def initialize(query, options = {})
6
+ self.query = query
7
+ self.opts = configure(options)
8
+ self.url = SoraGeocoding::Url.new(query)
9
+ self.req = SoraGeocoding::Request.new
10
+ end
11
+
12
+ def to_s
13
+ query
14
+ end
15
+
16
+ def configure(func_opts)
17
+ SoraGeocoding.configure(func_opts)
18
+ end
19
+
20
+ def execute
21
+ data = req.fetch_data(url.get)
22
+ SoraGeocoding.log(:error, "The data could not be retrieved from #{url.site}") if data.nil?
23
+
24
+ {
25
+ site: url.site,
26
+ data: data.to_s
27
+ }
28
+ rescue StandardError
29
+ if url.site == 'yahoo'
30
+ initialize_geocoding
31
+ retry
32
+ end
33
+ SoraGeocoding.log(:warn, 'The information could not be retrieved. Please change your address.')
34
+ end
35
+
36
+ private
37
+ def initialize_geocoding
38
+ opts[:site] = 'geocoding'
39
+ initialize(query, opts)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,146 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'open-uri'
4
+ require 'rexml/document'
5
+
6
+ module SoraGeocoding
7
+ class Request < Base
8
+ def parse_xml(data)
9
+ REXML::Document.new(data)
10
+ rescue StandardError
11
+ unless raise_error(ResponseParseError.new(data))
12
+ SoraGeocoding.log(:warn, "API's response was not valid XML")
13
+ SoraGeocoding.log(:debug, "Raw response: #{data}")
14
+ end
15
+ end
16
+
17
+ def parse_raw_data(raw_data)
18
+ parse_xml(raw_data)
19
+ end
20
+
21
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
22
+ def check_response_for_errors!(response)
23
+ if response.code.to_i == 400
24
+ raise_error(SoraGeocoding::InvalidRequest) ||
25
+ SoraGeocoding.log(:warn, 'API error: 400 Bad Request')
26
+ elsif response.code.to_i == 401
27
+ raise_error(SoraGeocoding::RequestDenied) ||
28
+ SoraGeocoding.log(:warn, 'API error: 401 Unauthorized')
29
+ elsif response.code.to_i == 402
30
+ raise_error(SoraGeocoding::PaymentRequiredError) ||
31
+ SoraGeocoding.log(:warn, 'API error: 402 Payment Required')
32
+ elsif response.code.to_i == 403
33
+ raise_error(SoraGeocoding::ForbiddenError) ||
34
+ SoraGeocoding.log(:warn, 'API error: 403 Forbidden')
35
+ elsif response.code.to_i == 404
36
+ raise_error(SoraGeocoding::NotFoundError) ||
37
+ SoraGeocoding.log(:warn, 'API error: 404 Not Found')
38
+ elsif response.code.to_i == 407
39
+ raise_error(SoraGeocoding::ProxyAuthenticationRequiredError) ||
40
+ SoraGeocoding.log(:warn, 'API error: 407 Proxy Authentication Required')
41
+ elsif response.code.to_i == 429
42
+ raise_error(SoraGeocoding::OverQueryLimitError) ||
43
+ SoraGeocoding.log(:warn, 'API error: 429 Too Many Requests')
44
+ elsif response.code.to_i == 503
45
+ raise_error(SoraGeocoding::ServiceUnavailable) ||
46
+ SoraGeocoding.log(:warn, 'API error: 503 Service Unavailable')
47
+ end
48
+ end
49
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
50
+
51
+ def fetch_data(query)
52
+ parse_raw_data(fetch_raw_data(query))
53
+ rescue SocketError => e
54
+ raise_error(e) || SoraGeocoding.log(:warn, 'API connection cannot be established.')
55
+ rescue Errno::ECONNREFUSED => e
56
+ raise_error(e) || SoraGeocoding.log(:warn, 'API connection refused.')
57
+ rescue SoraGeocoding::NetworkError => e
58
+ raise_error(e) || SoraGeocoding.log(:warn, 'API connection is either unreacheable or reset by the peer')
59
+ rescue SoraGeocoding::Timeout => e
60
+ raise_error(e) || SoraGeocoding.log(:warn, 'API not responding fast enough ' \
61
+ '(use SoraGeocoding.configure(:timeout => ...) to set limit).')
62
+ end
63
+
64
+ #
65
+ # Make an HTTP(S) request to return the response object.
66
+ #
67
+ # rubocop:disable Metrics/AbcSize
68
+ def make_api_request(query_url)
69
+ uri = URI.parse(query_url)
70
+ SoraGeocoding.log(:debug, "HTTP request being made for #{uri}")
71
+ http_client.start(
72
+ uri.host,
73
+ uri.port,
74
+ use_ssl: use_ssl?(uri.port),
75
+ open_timeout: configuration.timeout,
76
+ read_timeout: configuration.timeout
77
+ ) do |client|
78
+ configure_ssl!(client) if use_ssl?
79
+ req = Net::HTTP::Get.new(uri.request_uri, configuration.http_headers)
80
+ if configuration.basic_auth[:user] && configuration.basic_auth[:password]
81
+ req.basic_auth(configuration.basic_auth[:user], configuration.basic_auth[:password])
82
+ end
83
+ client.request(req)
84
+ end
85
+ rescue Timeout::Error
86
+ raise SoraGeocoding::Timeout
87
+ rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::ECONNRESET
88
+ raise SoraGeocoding::NetworkError
89
+ end
90
+ # rubocop:enable Metrics/AbcSize
91
+
92
+ #
93
+ # Detect if you are using ssl
94
+ #
95
+ def use_ssl?(http_port = nil)
96
+ if (http_port == 443) || (supported_protocols == [:https])
97
+ true
98
+ elsif supported_protocols == [:http]
99
+ false
100
+ else
101
+ configuration.use_https
102
+ end
103
+ end
104
+
105
+ def configure_ssl!(client); end
106
+
107
+ #
108
+ # Generate a string of http, https or protocol.
109
+ #
110
+ def protocol
111
+ 'http' + (use_ssl? ? 's' : '')
112
+ end
113
+
114
+ #
115
+ # Make HTTP requests.
116
+ #
117
+ def http_client
118
+ proxy = configuration.send("#{protocol}_proxy")
119
+ if proxy
120
+ proxy_url = proxy =~ /^#{protocol}/ ? proxy : protocol + '://' + proxy
121
+ begin
122
+ uri = URI.parse(proxy_url)
123
+ rescue URI::InvalidURIError
124
+ raise ConfigurationError, "Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'"
125
+ end
126
+ Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password)
127
+ else
128
+ Net::HTTP
129
+ end
130
+ end
131
+
132
+ #
133
+ # Supported Protocols
134
+ #
135
+ def supported_protocols
136
+ %i[http https]
137
+ end
138
+
139
+ private
140
+ def fetch_raw_data(query)
141
+ response = make_api_request(query)
142
+ check_response_for_errors!(response)
143
+ response.body
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,28 @@
1
+ require 'rexml/document'
2
+
3
+ module SoraGeocoding
4
+ module Results
5
+ #
6
+ # The Foundation for Returning Results
7
+ #
8
+ class Base
9
+ attr_accessor :data
10
+
11
+ def initialize(data)
12
+ @data = REXML::Document.new(data)
13
+ end
14
+
15
+ def coordinates
16
+ { lat: @data['lat'], lon: @data['lon'] }
17
+ end
18
+
19
+ def latitude
20
+ coordinates[:lat]
21
+ end
22
+
23
+ def longitude
24
+ coordinates[:lon]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require 'sora_geocoding/results/base'
2
+
3
+ module SoraGeocoding
4
+ module Results
5
+ #
6
+ # get the latitude and longitude of the Geocoding API
7
+ #
8
+ class Geocoding < Base
9
+ def coordinates
10
+ check_data_for_errors!
11
+
12
+ common = '/result/coordinate'
13
+ lat = @data.elements["#{common}/lat"].get_text
14
+ lon = @data.elements["#{common}/lng"].get_text
15
+ { lat: lat, lon: lon }
16
+ end
17
+
18
+ def check_data_for_errors!
19
+ error = @data.elements['/result/error']
20
+ return unless error
21
+
22
+ code = error.get_text.to_s
23
+ SoraGeocoding.log(:error, "Geocoding API error: #{code} Zero Results")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ require 'sora_geocoding/results/base'
2
+
3
+ module SoraGeocoding
4
+ module Results
5
+ #
6
+ # get the latitude and longitude of the Yahoo! Geocoder API
7
+ #
8
+ class YahooGeocoder < Base
9
+ def coordinates
10
+ check_data_for_errors!
11
+ lonlat = @data.elements['/YDF/Feature/Geometry/Coordinates'].get_text.to_s.split(',')
12
+ { lat: lonlat[1], lon: lonlat[0] }
13
+ end
14
+
15
+ def check_data_for_errors!
16
+ if @data.elements['/Error']
17
+ message = @data.elements['/Error/Message'].get_text.to_s
18
+ code = @data.elements['/Error/Code'].get_text.to_s
19
+ SoraGeocoding.log(:error, "Yahoo Geocoder API error: #{code} #{message}")
20
+ elsif @data.elements['/YDF'].attributes['totalResultsReturned'].to_i < 1
21
+ SoraGeocoding.log(:error, 'Yahoo Geocoder API error: 001 Zero Results')
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ require 'open-uri'
2
+ require 'sora_geocoding/base'
3
+
4
+ module SoraGeocoding
5
+ #
6
+ # generate url
7
+ #
8
+ class Url < Base
9
+ attr_accessor :site
10
+
11
+ BASE_YAHOO_URL = 'https://map.yahooapis.jp/geocode/V1/geoCoder'.freeze
12
+ BASE_GEOCODING_URL = 'https://www.geocoding.jp/api/'.freeze
13
+
14
+ def initialize(query)
15
+ @yahoo_appid = configuration.yahoo_appid
16
+ @site = configuration.site
17
+ @query = query
18
+ end
19
+
20
+ def get
21
+ switch_site
22
+ select
23
+ end
24
+
25
+ private
26
+ def select
27
+ if @yahoo_appid.nil? || @site == 'geocoding'
28
+ geocoding
29
+ elsif @site.nil? || @site == 'yahoo'
30
+ yahoo
31
+ end
32
+ end
33
+
34
+ def switch_site
35
+ if @yahoo_appid.nil? || @site == 'geocoding'
36
+ @site = 'geocoding'
37
+ elsif @site.nil? || @site == 'yahoo'
38
+ @site = 'yahoo'
39
+ else
40
+ raise 'Please specify the correct site.'
41
+ end
42
+ end
43
+
44
+ def yahoo
45
+ params = {
46
+ appid: @yahoo_appid,
47
+ query: @query,
48
+ results: '1',
49
+ detail: 'full',
50
+ output: 'xml'
51
+ }
52
+ "#{BASE_YAHOO_URL}?#{encode_uri(params)}"
53
+ end
54
+
55
+ def geocoding
56
+ params = { q: @query }
57
+ "#{BASE_GEOCODING_URL}?#{encode_uri(params)}"
58
+ end
59
+
60
+ def encode_uri(params)
61
+ URI.encode_www_form(params)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module SoraGeocoding
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,32 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'sora_geocoding/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'sora_geocoding'
7
+ spec.version = SoraGeocoding::VERSION
8
+ spec.authors = ['hirontan']
9
+ spec.email = ['hirontan@sora.flights']
10
+
11
+ spec.summary = 'Get the latitude and longitude from the Geocoding API and the Yahoo! Geocoder API.'
12
+ spec.description = 'This library takes into account the number of API calls and address
13
+ lookup to get the latitude and longitude.
14
+ The API uses the Geocoding API and the Yahoo! Geocoder API.'
15
+ spec.homepage = 'https://rubygems.org/gems/sora_geocoding'
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/hirontan/sora_geocoding'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/hirontan/sora_geocoding/blob/master/CHANGELOG.md'
21
+
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_development_dependency 'bundler', '~> 2.0'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sora_geocoding
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - hirontan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: |-
56
+ This library takes into account the number of API calls and address
57
+ lookup to get the latitude and longitude.
58
+ The API uses the Geocoding API and the Yahoo! Geocoder API.
59
+ email:
60
+ - hirontan@sora.flights
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".gitignore"
66
+ - ".rspec"
67
+ - ".rubocop.yml"
68
+ - ".travis.yml"
69
+ - ".yardopts"
70
+ - CHANGELOG.md
71
+ - CODE_OF_CONDUCT.md
72
+ - Gemfile
73
+ - Gemfile.lock
74
+ - Guardfile
75
+ - LICENSE
76
+ - LICENSE.txt
77
+ - README.md
78
+ - Rakefile
79
+ - bin/console
80
+ - bin/setup
81
+ - lib/hash_recursive_merge.rb
82
+ - lib/sora_geocoding.rb
83
+ - lib/sora_geocoding/base.rb
84
+ - lib/sora_geocoding/configuration.rb
85
+ - lib/sora_geocoding/exceptions.rb
86
+ - lib/sora_geocoding/logger.rb
87
+ - lib/sora_geocoding/query.rb
88
+ - lib/sora_geocoding/request.rb
89
+ - lib/sora_geocoding/results/base.rb
90
+ - lib/sora_geocoding/results/geocoding.rb
91
+ - lib/sora_geocoding/results/yahoo_geocoder.rb
92
+ - lib/sora_geocoding/url.rb
93
+ - lib/sora_geocoding/version.rb
94
+ - sora_geocoding.gemspec
95
+ homepage: https://rubygems.org/gems/sora_geocoding
96
+ licenses:
97
+ - MIT
98
+ metadata:
99
+ homepage_uri: https://rubygems.org/gems/sora_geocoding
100
+ source_code_uri: https://github.com/hirontan/sora_geocoding
101
+ changelog_uri: https://github.com/hirontan/sora_geocoding/blob/master/CHANGELOG.md
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.7.6.2
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Get the latitude and longitude from the Geocoding API and the Yahoo! Geocoder
122
+ API.
123
+ test_files: []