dawarich/app/services/maps/hexagon_polygon_generator.rb
2025-09-17 01:55:42 +02:00

104 lines
3.2 KiB
Ruby

# frozen_string_literal: true
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)
@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
end
private
attr_reader :center_lng, :center_lat, :size_meters, :use_h3, :h3_resolution
def generate_h3_hexagon_polygon
# Convert coordinates to H3 format [lat, lng]
coordinates = [center_lat, center_lng]
# Get H3 index for these coordinates at specified resolution
h3_index = H3.from_geo_coordinates(coordinates, h3_resolution)
# Get the boundary coordinates for this H3 hexagon
boundary_coordinates = H3.to_boundary(h3_index)
# Convert to GeoJSON polygon format (lng, lat)
polygon_coordinates = boundary_coordinates.map { |lat, lng| [lng, lat] }
# Close the polygon by adding the first point at the end
polygon_coordinates << polygon_coordinates.first
{
'type' => 'Polygon',
'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