mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-13 02:31:39 -05:00
Refactor H3 hexagon calculation logic to improve clarity and maintainability
This commit is contained in:
parent
3450ca35b0
commit
c756346569
2 changed files with 57 additions and 78 deletions
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Stats::CalculateMonth
|
||||
include ActiveModel::Validations
|
||||
|
||||
def initialize(user_id, year, month)
|
||||
@user = User.find(user_id)
|
||||
@year = year.to_i
|
||||
|
|
@ -87,6 +85,6 @@ class Stats::CalculateMonth
|
|||
end
|
||||
|
||||
def calculate_h3_hex_ids
|
||||
Stats::HexagonCalculator.new(user.id, year, month).calculate_h3_hex_ids
|
||||
Stats::HexagonCalculator.new(user.id, year, month).call
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,70 +14,63 @@ class Stats::HexagonCalculator
|
|||
end
|
||||
|
||||
def call(h3_resolution: DEFAULT_H3_RESOLUTION)
|
||||
calculate_h3_hexagon_centers_with_resolution(h3_resolution)
|
||||
calculate_h3_hexagon_centers(h3_resolution)
|
||||
end
|
||||
|
||||
def calculate_h3_hex_ids
|
||||
return {} if points.empty?
|
||||
result = calculate_hexagons(DEFAULT_H3_RESOLUTION)
|
||||
return {} if result.nil?
|
||||
|
||||
begin
|
||||
result = call
|
||||
|
||||
if result.empty?
|
||||
Rails.logger.info "No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)"
|
||||
return {}
|
||||
end
|
||||
|
||||
# Convert array format to hash format: { h3_index => [count, earliest, latest] }
|
||||
hex_hash = result.each_with_object({}) do |hex_data, hash|
|
||||
h3_index, count, earliest, latest = hex_data
|
||||
hash[h3_index] = [count, earliest, latest]
|
||||
end
|
||||
|
||||
Rails.logger.info "Pre-calculated #{hex_hash.size} H3 hex IDs for user #{user.id}, #{year}-#{month}"
|
||||
hex_hash
|
||||
rescue PostGISError => e
|
||||
Rails.logger.warn "H3 hex IDs calculation failed for user #{user.id}, #{year}-#{month}: #{e.message}"
|
||||
{}
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user, :year, :month
|
||||
|
||||
def calculate_h3_hexagon_centers_with_resolution(h3_resolution)
|
||||
points = fetch_user_points_for_period
|
||||
def calculate_h3_hexagon_centers(h3_resolution)
|
||||
result = calculate_hexagons(h3_resolution)
|
||||
return [] if result.nil?
|
||||
|
||||
return [] if points.empty?
|
||||
|
||||
h3_indexes_with_counts = calculate_h3_indexes(points, h3_resolution)
|
||||
|
||||
if h3_indexes_with_counts.size > MAX_HEXAGONS
|
||||
Rails.logger.warn "Too many hexagons (#{h3_indexes_with_counts.size}), using lower resolution"
|
||||
# Try with lower resolution (larger hexagons)
|
||||
lower_resolution = [h3_resolution - 2, 0].max
|
||||
Rails.logger.info "Recalculating with lower H3 resolution: #{lower_resolution}"
|
||||
# Create a new instance with lower resolution for recursion
|
||||
return self.class.new(user.id, year, month)
|
||||
.calculate_h3_hexagon_centers_with_resolution(lower_resolution)
|
||||
end
|
||||
|
||||
Rails.logger.info "Generated #{h3_indexes_with_counts.size} H3 hexagons at resolution #{h3_resolution} for user #{user.id}"
|
||||
|
||||
# Convert to format: [h3_index_string, point_count, earliest_timestamp, latest_timestamp]
|
||||
h3_indexes_with_counts.map do |h3_index, data|
|
||||
# Convert to array format: [h3_index_string, point_count, earliest_timestamp, latest_timestamp]
|
||||
result.map do |h3_index_string, data|
|
||||
[
|
||||
h3_index.to_s(16), # Store as hex string
|
||||
data[:count],
|
||||
data[:earliest],
|
||||
data[:latest]
|
||||
h3_index_string,
|
||||
data[0], # count
|
||||
data[1], # earliest
|
||||
data[2] # latest
|
||||
]
|
||||
end
|
||||
rescue StandardError => e
|
||||
message = "Failed to calculate H3 hexagon centers: #{e.message}"
|
||||
ExceptionReporter.call(e, message) if defined?(ExceptionReporter)
|
||||
raise PostGISError, message
|
||||
end
|
||||
|
||||
# Unified hexagon calculation method
|
||||
def calculate_hexagons(h3_resolution)
|
||||
return nil if points.empty?
|
||||
|
||||
begin
|
||||
h3_hash = calculate_h3_indexes(points, h3_resolution)
|
||||
|
||||
if h3_hash.empty?
|
||||
Rails.logger.info "No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)"
|
||||
return nil
|
||||
end
|
||||
|
||||
if h3_hash.size > MAX_HEXAGONS
|
||||
Rails.logger.warn "Too many hexagons (#{h3_hash.size}), using lower resolution"
|
||||
# Try with lower resolution (larger hexagons)
|
||||
lower_resolution = [h3_resolution - 2, 0].max
|
||||
Rails.logger.info "Recalculating with lower H3 resolution: #{lower_resolution}"
|
||||
# Create a new instance with lower resolution for recursion
|
||||
return self.class.new(user.id, year, month).calculate_hexagons(lower_resolution)
|
||||
end
|
||||
|
||||
Rails.logger.info "Generated #{h3_hash.size} H3 hexagons at resolution #{h3_resolution} for user #{user.id}"
|
||||
h3_hash
|
||||
rescue StandardError => e
|
||||
message = "Failed to calculate H3 hexagon centers: #{e.message}"
|
||||
ExceptionReporter.call(e, message) if defined?(ExceptionReporter)
|
||||
raise PostGISError, message
|
||||
end
|
||||
end
|
||||
|
||||
def start_timestamp
|
||||
|
|
@ -95,30 +88,13 @@ class Stats::HexagonCalculator
|
|||
.points
|
||||
.without_raw_data
|
||||
.where(timestamp: start_timestamp..end_timestamp)
|
||||
.where.not(lonlat: nil)
|
||||
.select(:lonlat, :timestamp)
|
||||
.order(timestamp: :asc)
|
||||
end
|
||||
|
||||
def start_date_iso8601
|
||||
@start_date_iso8601 ||= DateTime.new(year, month, 1).beginning_of_day.iso8601
|
||||
end
|
||||
|
||||
def end_date_iso8601
|
||||
@end_date_iso8601 ||= DateTime.new(year, month, -1).end_of_day.iso8601
|
||||
end
|
||||
|
||||
def fetch_user_points_for_period
|
||||
start_timestamp = DateTime.parse(start_date_iso8601).to_i
|
||||
end_timestamp = DateTime.parse(end_date_iso8601).to_i
|
||||
|
||||
Point.where(user_id: user.id)
|
||||
.where(timestamp: start_timestamp..end_timestamp)
|
||||
.where.not(lonlat: nil)
|
||||
.select(:id, :lonlat, :timestamp)
|
||||
end
|
||||
|
||||
def calculate_h3_indexes(points, h3_resolution)
|
||||
h3_data = Hash.new { |h, k| h[k] = { count: 0, earliest: nil, latest: nil } }
|
||||
h3_data = {}
|
||||
|
||||
points.find_each do |point|
|
||||
# Extract lat/lng from PostGIS point
|
||||
|
|
@ -126,14 +102,19 @@ class Stats::HexagonCalculator
|
|||
|
||||
# Get H3 index for this point
|
||||
h3_index = H3.from_geo_coordinates(coordinates, h3_resolution.clamp(0, 15))
|
||||
h3_index_string = h3_index.to_s(16) # Convert to hex string immediately
|
||||
|
||||
# Aggregate data for this hexagon
|
||||
data = h3_data[h3_index]
|
||||
data[:count] += 1
|
||||
data[:earliest] = [data[:earliest], point.timestamp].compact.min
|
||||
data[:latest] = [data[:latest], point.timestamp].compact.max
|
||||
# Initialize or update data for this hexagon
|
||||
if h3_data[h3_index_string]
|
||||
data = h3_data[h3_index_string]
|
||||
data[0] += 1 # increment count
|
||||
data[1] = [data[1], point.timestamp].min # update earliest
|
||||
data[2] = [data[2], point.timestamp].max # update latest
|
||||
else
|
||||
h3_data[h3_index_string] = [1, point.timestamp, point.timestamp] # [count, earliest, latest]
|
||||
end
|
||||
end
|
||||
|
||||
h3_data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue