sora_geocoding 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []