Remove unused code

This commit is contained in:
Eugene Burmakin 2025-09-18 22:23:47 +02:00
parent 5db2ac7fac
commit 0cce4929f0
8 changed files with 39 additions and 407 deletions

View file

@ -72,8 +72,6 @@ module Maps
if param.match?(/^\d+$/)
param.to_i
else
# Use Time.parse for strict validation, then convert via Time.zone
parsed_time = Time.parse(param) # This will raise ArgumentError for invalid dates
Time.zone.parse(param).to_i
end
when Integer

View file

@ -1,136 +0,0 @@
# frozen_string_literal: true
module Maps
class H3HexagonRenderer
def initialize(params:, user: nil, context: nil)
@params = params
@user = user
@context = context
end
def call
context = @context || resolve_context
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
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
Rails.logger.debug 'No pre-calculated H3 data, calculating on-the-fly'
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,
start_date: start_date,
end_date: end_date,
h3_resolution: h3_resolution
)
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
return Time.zone.parse(date_param) if date_param.is_a?(String)
# If it's an integer timestamp, convert to Time
return Time.zone.at(date_param) if date_param.is_a?(Integer)
# 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}"
end
end
end

View file

@ -2,10 +2,6 @@
module Maps
class HexagonCenterManager
def self.call(stat:, user:)
new(stat: stat, user: user).call
end
def initialize(stat:, user:)
@stat = stat
@user = user
@ -86,11 +82,10 @@ module Maps
end
def generate_hexagon_geometry(lng, lat)
Maps::HexagonPolygonGenerator.call(
Maps::HexagonPolygonGenerator.new(
center_lng: lng,
center_lat: lat,
size_meters: 1000
)
center_lat: lat
).call
end
def build_hexagon_properties(index, earliest, latest)

View file

@ -2,37 +2,19 @@
module Maps
class HexagonPolygonGenerator
DEFAULT_SIZE_METERS = 1000
def self.call(center_lng:, center_lat:, size_meters: DEFAULT_SIZE_METERS, use_h3: false, h3_resolution: 5)
new(
center_lng: center_lng,
center_lat: center_lat,
size_meters: size_meters,
use_h3: use_h3,
h3_resolution: h3_resolution
).call
end
def initialize(center_lng:, center_lat:, size_meters: DEFAULT_SIZE_METERS, use_h3: false, h3_resolution: 5)
def initialize(center_lng:, center_lat:, h3_resolution: 5)
@center_lng = center_lng
@center_lat = center_lat
@size_meters = size_meters
@use_h3 = use_h3
@h3_resolution = h3_resolution
end
def call
if use_h3
generate_h3_hexagon_polygon
else
generate_hexagon_polygon
end
generate_h3_hexagon_polygon
end
private
attr_reader :center_lng, :center_lat, :size_meters, :use_h3, :h3_resolution
attr_reader :center_lng, :center_lat, :h3_resolution
def generate_h3_hexagon_polygon
# Convert coordinates to H3 format [lat, lng]
@ -45,7 +27,7 @@ module Maps
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 = boundary_coordinates.map { [_2, _1] }
# Close the polygon by adding the first point at the end
polygon_coordinates << polygon_coordinates.first
@ -55,50 +37,5 @@ module Maps
'coordinates' => [polygon_coordinates]
}
end
def generate_hexagon_polygon
# Generate hexagon vertices around center point
# For a regular hexagon:
# - Circumradius (center to vertex) = size_meters / 2
# - This creates hexagons that are approximately size_meters wide
radius_meters = size_meters / 2.0
# Convert meter radius to degrees
# 1 degree latitude ≈ 111,111 meters
# 1 degree longitude ≈ 111,111 * cos(latitude) meters at given latitude
lat_degree_in_meters = 111_111.0
lng_degree_in_meters = lat_degree_in_meters * Math.cos(center_lat * Math::PI / 180)
radius_lat_degrees = radius_meters / lat_degree_in_meters
radius_lng_degrees = radius_meters / lng_degree_in_meters
vertices = build_vertices(radius_lat_degrees, radius_lng_degrees)
{
'type' => 'Polygon',
'coordinates' => [vertices]
}
end
def build_vertices(radius_lat_degrees, radius_lng_degrees)
vertices = []
6.times do |i|
# Calculate angle for each vertex (60 degrees apart, starting from 0)
# Start at 30 degrees to orient hexagon with flat top
angle = ((i * 60) + 30) * Math::PI / 180
# Calculate vertex position using proper geographic coordinate system
# longitude (x-axis) uses cosine, latitude (y-axis) uses sine
lng_offset = radius_lng_degrees * Math.cos(angle)
lat_offset = radius_lat_degrees * Math.sin(angle)
vertices << [center_lng + lng_offset, center_lat + lat_offset]
end
# Close the polygon by adding the first vertex at the end
vertices << vertices.first
vertices
end
end
end

View file

@ -2,24 +2,20 @@
module Maps
class HexagonRequestHandler
def initialize(params:, user: nil, context: nil)
def initialize(params:, user: nil, stat: nil, start_date: nil, end_date: nil)
@params = params
@user = user
@context = context
@stat = stat
@start_date = start_date
@end_date = end_date
end
def call
context = @context || resolve_context
# For authenticated users, we need to find the matching stat
stat = context[:stat] || find_matching_stat(context)
stat ||= find_matching_stat
# Use pre-calculated hexagon centers
if stat
cached_result = Maps::HexagonCenterManager.call(
stat: stat,
user: context[:user]
)
cached_result = Maps::HexagonCenterManager.new(stat:, user:).call
return cached_result[:data] if cached_result&.dig(:success)
end
@ -31,22 +27,22 @@ module Maps
private
attr_reader :params, :user, :context
attr_reader :params, :user, :stat, :start_date, :end_date
def find_matching_stat(context)
return unless context[:user] && context[:start_date]
def find_matching_stat
return unless user && start_date
# Parse the date to extract year and month
if context[:start_date].is_a?(String)
date = Date.parse(context[:start_date])
elsif context[:start_date].is_a?(Time)
date = context[:start_date].to_date
if start_date.is_a?(String)
date = Date.parse(start_date)
elsif start_date.is_a?(Time)
date = start_date.to_date
else
return
end
# Find the stat for this user, year, and month
context[:user].stats.find_by(year: date.year, month: date.month)
user.stats.find_by(year: date.year, month: date.month)
rescue Date::Error
nil
end

View file

@ -4,12 +4,7 @@ require 'rails_helper'
RSpec.describe Maps::HexagonCenterManager do
describe '.call' do
subject(:manage_centers) do
described_class.call(
stat: stat,
target_user: target_user
)
end
subject(:manage_centers) { described_class.new(stat:, user:).call }
let(:user) { create(:user) }
let(:target_user) { user }

View file

@ -5,19 +5,17 @@ require 'rails_helper'
RSpec.describe Maps::HexagonPolygonGenerator do
describe '.call' do
subject(:generate_polygon) do
described_class.call(
described_class.new(
center_lng: center_lng,
center_lat: center_lat,
size_meters: size_meters
)
center_lat: center_lat
).call
end
let(:center_lng) { -74.0 }
let(:center_lat) { 40.7 }
let(:size_meters) { 1000 }
it 'returns a polygon geometry' do
result = generate_polygon
it 'returns a polygon geometry using H3' do
result = generate_h3_polygon
expect(result['type']).to eq('Polygon')
expect(result['coordinates']).to be_an(Array)
@ -25,7 +23,7 @@ RSpec.describe Maps::HexagonPolygonGenerator do
end
it 'generates a hexagon with 7 coordinate pairs (6 vertices + closing)' do
result = generate_polygon
result = generate_h3_polygon
coordinates = result['coordinates'].first
expect(coordinates.length).to eq(7) # 6 vertices + closing vertex
@ -33,7 +31,7 @@ RSpec.describe Maps::HexagonPolygonGenerator do
end
it 'generates unique vertices' do
result = generate_polygon
result = generate_h3_polygon
coordinates = result['coordinates'].first
# Remove the closing vertex for uniqueness check
@ -42,17 +40,9 @@ RSpec.describe Maps::HexagonPolygonGenerator do
end
it 'generates vertices around the center point' do
result = generate_polygon
result = generate_h3_polygon
coordinates = result['coordinates'].first
# Check that not all vertices are the same as center (vertices should be distributed)
vertices_equal_to_center = coordinates[0..5].count do |vertex|
lng, lat = vertex
lng == center_lng && lat == center_lat
end
expect(vertices_equal_to_center).to eq(0) # No vertex should be exactly at center
# Check that vertices have some variation in coordinates
longitudes = coordinates[0..5].map { |vertex| vertex[0] }
latitudes = coordinates[0..5].map { |vertex| vertex[1] }
@ -61,128 +51,13 @@ RSpec.describe Maps::HexagonPolygonGenerator do
expect(latitudes.uniq.size).to be > 1 # Should have different latitudes
end
context 'with different size' do
let(:size_meters) { 500 }
it 'generates a smaller hexagon' do
small_result = generate_polygon
large_result = described_class.call(
center_lng: center_lng,
center_lat: center_lat,
size_meters: 2000
)
# Small hexagon should have vertices closer to center than large hexagon
small_distance = calculate_distance_from_center(small_result['coordinates'].first.first)
large_distance = calculate_distance_from_center(large_result['coordinates'].first.first)
expect(small_distance).to be < large_distance
end
end
context 'with different center coordinates' do
let(:center_lng) { 13.4 } # Berlin
let(:center_lat) { 52.5 }
it 'generates hexagon around the new center' do
result = generate_polygon
coordinates = result['coordinates'].first
# Check that vertices are around the Berlin coordinates
avg_lng = coordinates[0..5].sum { |vertex| vertex[0] } / 6
avg_lat = coordinates[0..5].sum { |vertex| vertex[1] } / 6
expect(avg_lng).to be_within(0.01).of(center_lng)
expect(avg_lat).to be_within(0.01).of(center_lat)
end
end
context 'with H3 enabled' do
subject(:generate_h3_polygon) do
described_class.call(
center_lng: center_lng,
center_lat: center_lat,
size_meters: size_meters,
use_h3: true
)
context 'when H3 operations fail' do
before do
allow(H3).to receive(:from_geo_coordinates).and_raise(StandardError, 'H3 error')
end
it 'returns a polygon geometry using H3' do
result = generate_h3_polygon
expect(result['type']).to eq('Polygon')
expect(result['coordinates']).to be_an(Array)
expect(result['coordinates'].length).to eq(1) # One ring
end
it 'generates a hexagon with 7 coordinate pairs (6 vertices + closing)' do
result = generate_h3_polygon
coordinates = result['coordinates'].first
expect(coordinates.length).to eq(7) # 6 vertices + closing vertex
expect(coordinates.first).to eq(coordinates.last) # Closed polygon
end
it 'generates unique vertices' do
result = generate_h3_polygon
coordinates = result['coordinates'].first
# Remove the closing vertex for uniqueness check
unique_vertices = coordinates[0..5]
expect(unique_vertices.uniq.length).to eq(6) # All vertices should be unique
end
it 'generates vertices around the center point' do
result = generate_h3_polygon
coordinates = result['coordinates'].first
# Check that vertices have some variation in coordinates
longitudes = coordinates[0..5].map { |vertex| vertex[0] }
latitudes = coordinates[0..5].map { |vertex| vertex[1] }
expect(longitudes.uniq.size).to be > 1 # Should have different longitudes
expect(latitudes.uniq.size).to be > 1 # Should have different latitudes
end
context 'when H3 operations fail' do
before do
allow(H3).to receive(:from_geo_coordinates).and_raise(StandardError, 'H3 error')
end
it 'raises the H3 error' do
expect { generate_h3_polygon }.to raise_error(StandardError, 'H3 error')
end
end
it 'produces different results than mathematical hexagon' do
h3_result = generate_h3_polygon
math_result = described_class.call(
center_lng: center_lng,
center_lat: center_lat,
size_meters: size_meters,
use_h3: false
)
# H3 and mathematical hexagons should generally be different
# (unless we're very unlucky with alignment)
expect(h3_result['coordinates']).not_to eq(math_result['coordinates'])
end
end
context 'with use_h3 parameter variations' do
it 'defaults to mathematical hexagon when use_h3 is false' do
result_explicit_false = described_class.call(
center_lng: center_lng,
center_lat: center_lat,
use_h3: false
)
result_default = described_class.call(
center_lng: center_lng,
center_lat: center_lat
)
expect(result_explicit_false).to eq(result_default)
it 'raises the H3 error' do
expect { generate_h3_polygon }.to raise_error(StandardError, 'H3 error')
end
end

View file

@ -7,22 +7,14 @@ RSpec.describe Maps::HexagonRequestHandler do
subject(:handle_request) do
described_class.new(
params: params,
user: current_api_user
user: user,
stat: nil,
start_date: params[:start_date],
end_date: params[:end_date]
).call
end
let(:user) { create(:user) }
let(:current_api_user) { user }
before do
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
# Clean up database state to avoid conflicts - order matters due to foreign keys
Point.delete_all
Stat.delete_all
User.delete_all
end
context 'with authenticated user but no pre-calculated data' do
let(:params) do
@ -71,7 +63,6 @@ RSpec.describe Maps::HexagonRequestHandler do
}
)
end
let(:current_api_user) { nil }
it 'returns pre-calculated hexagon data' do
result = handle_request
@ -96,7 +87,6 @@ RSpec.describe Maps::HexagonRequestHandler do
}
)
end
let(:current_api_user) { nil }
it 'returns empty feature collection when no pre-calculated centers' do
result = handle_request
@ -124,7 +114,6 @@ RSpec.describe Maps::HexagonRequestHandler do
}
)
end
let(:current_api_user) { nil }
before do
# Mock successful recalculation
@ -143,22 +132,5 @@ RSpec.describe Maps::HexagonRequestHandler do
expect(stat.reload.hexagon_centers).to eq([[-74.0, 40.7, 1_717_200_000, 1_717_203_600]])
end
end
context 'error handling' do
let(:params) do
ActionController::Parameters.new(
{
uuid: 'invalid-uuid'
}
)
end
let(:current_api_user) { nil }
it 'raises ActiveRecord::RecordNotFound for invalid UUID' do
expect { handle_request }.to raise_error(
ActiveRecord::RecordNotFound
)
end
end
end
end