cartodb-4.42/services/geocoder/lib/hires_geocoder.rb
2024-04-06 05:25:13 +00:00

170 lines
5.5 KiB
Ruby

require 'csv'
require 'json'
require 'open3'
require_relative '../../../lib/carto/http/client'
require_relative 'hires_geocoder_interface'
require_relative 'geocoder_config'
module CartoDB
class HiresGeocoder < HiresGeocoderInterface
# Generous timeouts for this
HTTP_CONNECTION_TIMEOUT = 60
HTTP_REQUEST_TIMEOUT = 600
# Default options for the regular HERE Geocoding API
# Refer to developer.here.com for further reading
GEOCODER_OPTIONS = {
gen: 4, # enables or disables backward incompatible behavior in the API
jsonattributes: 1, # lowercase the first character of each JSON response attribute name
language: 'en-US', # preferred language of address elements in the result
maxresults: 1
}
attr_reader :app_id, :token, :mailto,
:status, :processed_rows, :total_rows, :successful_processed_rows, :failed_processed_rows,
:empty_processed_rows, :dir, :non_batch_base_url
attr_accessor :input_file
def initialize(input_csv_file, working_dir, log, geocoding_model)
@input_file = input_csv_file
@dir = working_dir
@log = log
@geocoding_model = geocoding_model
@non_batch_base_url = config.fetch('non_batch_base_url')
@app_id = config.fetch('app_id')
@token = config.fetch('token')
@mailto = config.fetch('mailto')
init_rows_count
end
def run
init_rows_count
@log.append_and_store "Initialized non batch Here geocoding job"
@result = File.join(dir, 'generated_csv_out.txt')
change_status('running')
@total_rows = input_rows
@log.append_and_store "Total rows to be processed: #{@total_rows}"
::CSV.open(@result, "wb") do |output_csv_file|
::CSV.foreach(input_file, headers: true) do |input_row|
process_row(input_row, output_csv_file)
end
end
change_status('completed')
update_log_stats
@log.append_and_store "Non-batch Here geocoding job finished"
end
def used_batch_request?
false
end
def cancel; end
def update_status; end
def result
@result
end
def request_id
# INFO: there's no request_id for non-batch geocodings
nil
end
private
def config
GeocoderConfig.instance.get
end
def http_client
@http_client ||= Carto::Http::Client.get('hires_geocoder',
log_requests: true)
end
def input_rows
stdout, _stderr, _status = Open3.capture3('wc', '-l', input_file)
stdout.to_i
rescue StandardError
0
end
def process_row(input_row, output_csv_file)
@processed_rows += 1
latitude, longitude = geocode_text(input_row["searchtext"])
if !(latitude.nil? || latitude == "") && !(longitude.nil? || longitude == "")
@successful_processed_rows += 1
output_csv_file.add_row [input_row["searchtext"], 1, 1, latitude, longitude]
else
@empty_processed_rows += 1
end
rescue StandardError => e
@log.append_and_store "Error processing row with search text #{input_row['searchtext']}: #{e.message}"
CartoDB.notify_debug("Hires geocoding process row error",
error: e.backtrace.join("\n"),
searchtext: input_row["searchtext"],
backtrace: e.backtrace)
@failed_processed_rows += 1
end
def geocode_text(text)
options = GEOCODER_OPTIONS.merge(searchtext: text, app_id: app_id, app_code: token)
url = "#{non_batch_base_url}?#{URI.encode_www_form(options)}"
http_response = http_client.get(url,
connecttimeout: HTTP_CONNECTION_TIMEOUT,
timeout: HTTP_REQUEST_TIMEOUT)
if http_response.success?
response = ::JSON.parse(http_response.body)["response"]
if response['view'].empty?
# no location info for the text input, stop here
return [nil, nil]
end
position = response["view"][0]["result"][0]["location"]["displayPosition"]
return position["latitude"], position["longitude"]
else
CartoDB.notify_debug('Non-batched geocoder failed request', http_response)
return [nil, nil]
end
rescue NoMethodError => e
if e.message == %Q(undefined method `[]' for nil:NilClass)
CartoDB.notify_debug("Non-batched geocoder couldn't parse response",
error: e.backtrace.join("\n"), backtrace: e.backtrace, text: text, response_body: http_response.body)
[nil, nil]
else
raise e
end
end
def api_url(arguments, extra_components = nil)
arguments.merge!(app_id: app_id, token: token, mailto: mailto)
components = [base_url]
components << extra_components unless extra_components.nil?
components << '?' + URI.encode_www_form(arguments)
components.join('/')
end
def init_rows_count
@processed_rows = 0
@successful_processed_rows = 0
@failed_processed_rows = 0
@empty_processed_rows = 0
end
def update_log_stats
@log.append_and_store "Geocoding non-batch Here job status update. "\
"Status: #{@status} --- Processed rows: #{@processed_rows} "\
"--- Success: #{@successful_processed_rows} --- Empty: #{@empty_processed_rows} "\
"--- Failed: #{@failed_processed_rows}"
end
def change_status(status)
@status = status
@geocoding_model.state = status
@geocoding_model.save
end
end
end