dawarich/app/services/maps/h3_hexagon_renderer.rb

137 lines
4.3 KiB
Ruby
Raw Normal View History

2025-09-16 19:55:42 -04:00
# frozen_string_literal: true
module Maps
class H3HexagonRenderer
def initialize(params:, user: nil, context: nil)
2025-09-16 19:55:42 -04:00
@params = params
2025-09-18 14:10:00 -04:00
@user = user
@context = context
2025-09-16 19:55:42 -04:00
end
def call
context = @context || resolve_context
2025-09-16 19:55:42 -04:00
h3_data = get_h3_hexagon_data(context)
return empty_feature_collection if h3_data.empty?
convert_h3_to_geojson(h3_data)
end
private
attr_reader :params, :user, :context
2025-09-16 19:55:42 -04:00
def get_h3_hexagon_data(context)
# For public sharing, get pre-calculated data from stat
if context[:stat]&.hexagon_centers.present?
hexagon_data = context[:stat].hexagon_centers
# Check if this is old format (coordinates) or new format (H3 indexes)
if hexagon_data.first.is_a?(Array) && hexagon_data.first[0].is_a?(Float)
Rails.logger.debug "Found old coordinate format for stat #{context[:stat].id}, generating H3 on-the-fly"
return generate_h3_data_on_the_fly(context)
else
Rails.logger.debug "Using pre-calculated H3 data for stat #{context[:stat].id}"
return hexagon_data
end
end
# For authenticated users, calculate on-the-fly if no pre-calculated data
2025-09-18 12:29:46 -04:00
Rails.logger.debug 'No pre-calculated H3 data, calculating on-the-fly'
2025-09-16 19:55:42 -04:00
generate_h3_data_on_the_fly(context)
end
def generate_h3_data_on_the_fly(context)
start_date = parse_date_for_h3(context[:start_date])
end_date = parse_date_for_h3(context[:end_date])
h3_resolution = params[:h3_resolution]&.to_i&.clamp(0, 15) || 6
# Use dummy year/month since we're only using the H3 calculation method
stats_service = Stats::CalculateMonth.new(context[:user]&.id, 2024, 1)
stats_service.calculate_h3_hexagon_centers(
user_id: context[:user]&.id,
2025-09-16 19:55:42 -04:00
start_date: start_date,
end_date: end_date,
h3_resolution: h3_resolution
)
2025-09-16 19:55:42 -04:00
end
def convert_h3_to_geojson(h3_data)
features = h3_data.map do |h3_record|
h3_index_string, point_count, earliest_timestamp, latest_timestamp = h3_record
# Convert hex string back to H3 index
h3_index = h3_index_string.to_i(16)
# Get hexagon boundary coordinates
boundary_coordinates = H3.to_boundary(h3_index)
# Convert to GeoJSON polygon format (lng, lat)
polygon_coordinates = boundary_coordinates.map { |lat, lng| [lng, lat] }
polygon_coordinates << polygon_coordinates.first # Close the polygon
{
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [polygon_coordinates]
},
properties: {
h3_index: h3_index_string,
point_count: point_count,
earliest_point: earliest_timestamp ? Time.at(earliest_timestamp).iso8601 : nil,
latest_point: latest_timestamp ? Time.at(latest_timestamp).iso8601 : nil,
center: H3.to_geo_coordinates(h3_index) # [lat, lng]
}
}
end
{
type: 'FeatureCollection',
features: features,
metadata: {
hexagon_count: features.size,
total_points: features.sum { |f| f[:properties][:point_count] },
source: 'h3'
}
}
end
def empty_feature_collection
{
type: 'FeatureCollection',
features: [],
metadata: {
hexagon_count: 0,
total_points: 0,
source: 'h3'
}
}
end
def parse_date_for_h3(date_param)
# If already a Time object (from public sharing context), return as-is
return date_param if date_param.is_a?(Time)
# If it's a string ISO date, parse it directly to Time
2025-09-18 12:29:46 -04:00
return Time.zone.parse(date_param) if date_param.is_a?(String)
2025-09-16 19:55:42 -04:00
# If it's an integer timestamp, convert to Time
2025-09-18 12:29:46 -04:00
return Time.zone.at(date_param) if date_param.is_a?(Integer)
2025-09-16 19:55:42 -04:00
# For other cases, try coercing and converting
case date_param
when String
date_param.match?(/^\d+$/) ? Time.zone.at(date_param.to_i) : Time.zone.parse(date_param)
when Integer
Time.zone.at(date_param)
else
Time.zone.at(date_param.to_i)
end
rescue ArgumentError => e
Rails.logger.error "Invalid date format: #{date_param} - #{e.message}"
raise ArgumentError, "Invalid date format: #{date_param}"
2025-09-16 19:55:42 -04:00
end
end
2025-09-18 12:29:46 -04:00
end