mirror of
https://github.com/System-End/theseus.git
synced 2026-04-19 19:55:10 +00:00
139 lines
5 KiB
Ruby
139 lines
5 KiB
Ruby
module GeocodingService
|
|
FIFTEEN_FALLS = { place_id: 339396112,
|
|
licence: "Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
|
|
osm_type: "node",
|
|
osm_id: 12202788601,
|
|
lat: "44.3805465",
|
|
lon: "-73.2270890",
|
|
category: "office",
|
|
type: "ngo",
|
|
place_rank: 30,
|
|
importance: 5.165255207136671e-05,
|
|
addresstype: "office",
|
|
name: "Hack Club HQ",
|
|
display_name: "Hack Club HQ, 15, Falls Road, Shelburne Village Historic District, Shelburne, Chittenden County, Vermont, 05482, United States",
|
|
boundingbox: ["44.3804965", "44.3805965", "-73.2271390", "-73.2270390"] }
|
|
|
|
class << self
|
|
def geocode_address_model(address, exact: false)
|
|
Rails.cache.fetch("geocode_address_#{address.id}", expires_in: 5.months) do
|
|
params = {
|
|
street: address.line_1,
|
|
city: address.city,
|
|
state: address.state,
|
|
postalcode: address.postal_code,
|
|
country: address.country,
|
|
}
|
|
|
|
result = first_hit(params) || FIFTEEN_FALLS
|
|
result[:lat] = result[:lat].to_f
|
|
result[:lon] = result[:lon].to_f
|
|
result = fuzz_coordinates(result[:lat], result[:lon]) unless exact
|
|
result
|
|
end
|
|
end
|
|
|
|
def geocode_return_address(return_address, exact: false)
|
|
Rails.cache.fetch("geocode_return_address_#{return_address.id}", expires_in: 5.months) do
|
|
params = {
|
|
city: return_address.city,
|
|
state: return_address.state,
|
|
postalcode: return_address.postal_code,
|
|
country: return_address.country,
|
|
}
|
|
params[:street] = return_address.line_1 if exact
|
|
|
|
first_hit(params) || FIFTEEN_FALLS
|
|
end
|
|
end
|
|
|
|
def first_hit(params)
|
|
hackclub_geocode(params)
|
|
end
|
|
|
|
def fuzz_coordinates(lat, lon)
|
|
# Add random offset within 5 mile radius, with minimum of 3 miles for privacy
|
|
# 1 degree latitude ≈ 69 miles, so 15 miles ≈ 0.2175 degrees
|
|
# 1 degree longitude varies by latitude, but at ~45°N ≈ 49 miles, so 15 miles ≈ 0.306 degrees
|
|
max_lat_offset = 0.2175
|
|
max_lon_offset = 0.306 * Math.cos(lat * Math::PI / 180)
|
|
|
|
# Generate random angle and distance within the annular ring (3-5 miles)
|
|
angle = rand * 2 * Math::PI
|
|
|
|
# For uniform distribution in annular ring, we need to account for quadratic area growth
|
|
# Area of annular ring = π(r_max² - r_min²)
|
|
# For uniform distribution: rand = (r² - r_min²) / (r_max² - r_min²)
|
|
# Solving for r: r = sqrt(r_min² + rand * (r_max² - r_min²))
|
|
min_distance_factor = 0.6 # 3 miles / 5 miles
|
|
max_distance_factor = 1.0 # 5 miles / 5 miles
|
|
|
|
# Calculate distance factor using proper area-based distribution
|
|
min_squared = min_distance_factor ** 2
|
|
max_squared = max_distance_factor ** 2
|
|
distance_factor = Math.sqrt(min_squared + rand * (max_squared - min_squared))
|
|
|
|
lat_offset = distance_factor * max_lat_offset * Math.sin(angle)
|
|
lon_offset = distance_factor * max_lon_offset * Math.cos(angle)
|
|
|
|
fuzzed_lat = lat + lat_offset
|
|
fuzzed_lon = lon + lon_offset
|
|
|
|
# Reduce precision to ~100m resolution (3 decimal places)
|
|
{
|
|
lat: fuzzed_lat.round(3),
|
|
lon: fuzzed_lon.round(3),
|
|
}
|
|
end
|
|
|
|
def hackclub_geocode(params)
|
|
return nil if params.nil?
|
|
|
|
Rails.logger.info "Hack Club Geocoding: #{params}"
|
|
|
|
address_components = []
|
|
address_components << params[:street] if params[:street]
|
|
address_components << params[:city] if params[:city]
|
|
address_components << params[:state] if params[:state]
|
|
address_components << params[:postalcode] if params[:postalcode]
|
|
address_components << params[:country] if params[:country]
|
|
|
|
address = address_components.join(", ")
|
|
|
|
response = conn.get("v1/geocode", {
|
|
address: address,
|
|
key: ENV["HACKCLUB_GEOCODER_API_KEY"],
|
|
})
|
|
|
|
if response.body.key?("error")
|
|
Rails.logger.error "Hack Club Geocoder error: #{response.body["error"]}"
|
|
Sentry.capture_message("Geocoding error: #{response.body["error"]}")
|
|
return nil
|
|
end
|
|
|
|
# Parse successful response
|
|
result = response.body
|
|
|
|
{
|
|
lat: result["lat"].to_s,
|
|
lon: result["lng"].to_s, # hc api returns "lng", we convert to "lon" for consistency with OSM
|
|
display_name: result["formatted_address"],
|
|
place_id: result.dig("raw_backend_response", "results", 0, "place_id") || "dunno",
|
|
}
|
|
rescue => e
|
|
Rails.logger.error "Hack Club Geocoder request failed: #{e.message}"
|
|
Sentry.capture_exception(e)
|
|
nil
|
|
end
|
|
|
|
private
|
|
|
|
def conn
|
|
@conn ||= Faraday.new(url: "https://geocoder.hackclub.com/") do |faraday|
|
|
faraday.request :url_encoded
|
|
faraday.adapter Faraday.default_adapter
|
|
faraday.response :json
|
|
end
|
|
end
|
|
end
|
|
end
|