Fix missing hexes

This commit is contained in:
Eugene Burmakin 2025-09-19 22:52:08 +02:00
parent 2fe36f02d6
commit a20a3c5b36
6 changed files with 71 additions and 57 deletions

File diff suppressed because one or more lines are too long

View file

@ -268,7 +268,7 @@ export default class extends BaseController {
const endTime = props.latest_point ? new Date(props.latest_point).toLocaleTimeString() : '';
return `
<div style="font-size: 12px; line-height: 1.6; max-width: 200px;">
<div style="font-size: 12px; line-height: 1.6; max-width: 300px;">
<strong style="color: #3388ff;">📍 Location Data</strong><br>
<div style="margin: 4px 0;">
<strong>Points:</strong> ${props.point_count || 0}

View file

@ -17,8 +17,12 @@ module Maps
start_timestamp = parse_date_parameter(@start_date)
end_timestamp = parse_date_parameter(@end_date)
points_relation = @user.points.where(timestamp: start_timestamp..end_timestamp)
point_count = points_relation.count
point_count =
@user
.points
.where(timestamp: start_timestamp..end_timestamp)
.select(:id)
.count
return build_no_data_response if point_count.zero?
@ -73,9 +77,8 @@ module Maps
param.to_i
else
parsed_time = Time.zone.parse(param)
if parsed_time.nil?
raise ArgumentError, "Invalid date format: #{param}"
end
raise ArgumentError, "Invalid date format: #{param}" if parsed_time.nil?
parsed_time.to_i
end
when Integer

View file

@ -28,11 +28,9 @@ class Stats::CalculateMonth
end
# Public method for calculating H3 hexagon centers with custom parameters
def calculate_h3_hexagon_centers(user_id: nil, start_date: nil, end_date: nil, h3_resolution: DEFAULT_H3_RESOLUTION)
target_start_date = start_date || start_date_iso8601
target_end_date = end_date || end_date_iso8601
def calculate_h3_hexagon_centers
points = fetch_user_points_for_period
points = fetch_user_points_for_period(user_id, target_start_date, target_end_date)
return [] if points.empty?
h3_indexes_with_counts = calculate_h3_indexes(points, h3_resolution)
@ -43,14 +41,14 @@ class Stats::CalculateMonth
lower_resolution = [h3_resolution - 2, 0].max
Rails.logger.info "Recalculating with lower H3 resolution: #{lower_resolution}"
return calculate_h3_hexagon_centers(
user_id: user_id,
start_date: target_start_date,
end_date: target_end_date,
user_id: user.id,
start_date: start_date_iso8601,
end_date: end_date_iso8601,
h3_resolution: lower_resolution
)
end
Rails.logger.info "Generated #{h3_indexes_with_counts.size} H3 hexagons at resolution #{h3_resolution} for user #{user_id}"
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|
@ -136,7 +134,10 @@ class Stats::CalculateMonth
return {} if points.empty?
begin
result = calculate_h3_hexagon_centers
result = calculate_h3_hexagon_centers(
user_id: user.id, h3_resolution: DEFAULT_H3_RESOLUTION,
start_date: start_date_iso8601, end_date: end_date_iso8601
)
if result.empty?
Rails.logger.info "No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)"
@ -158,18 +159,18 @@ class Stats::CalculateMonth
end
def start_date_iso8601
DateTime.new(year, month, 1).beginning_of_day.iso8601
@start_date_iso8601 ||= DateTime.new(year, month, 1).beginning_of_day.iso8601
end
def end_date_iso8601
DateTime.new(year, month, -1).end_of_day.iso8601
@end_date_iso8601 ||= DateTime.new(year, month, -1).end_of_day.iso8601
end
def fetch_user_points_for_period(user_id, start_date, end_date)
start_timestamp = parse_date_parameter(start_date)
end_timestamp = parse_date_parameter(end_date)
def fetch_user_points_for_period
start_timestamp = start_date_iso8601.to_i
end_timestamp = end_date_iso8601.to_i
Point.where(user_id: user_id)
Point.where(user_id: user.id)
.where(timestamp: start_timestamp..end_timestamp)
.where.not(lonlat: nil)
.select(:id, :lonlat, :timestamp)

View file

@ -155,11 +155,13 @@ Converts H3 indexes back to polygon geometry:
**Features:**
- Uses H3 library for accurate hexagon boundaries
- Converts coordinates to GeoJSON Polygon format
- Supports both center-based and H3-index-based generation
- Direct H3 index to polygon conversion for efficiency
- H3-index-only generation for maximum efficiency
- Direct H3 index to polygon conversion with coordinate transformation
**Usage Modes:**
- **Center-based**: `new(center_lng: lng, center_lat: lat)`
**Usage:**
- **H3-index only**: `new(h3_index: h3_index_string_or_integer)`
- Supports both hex string (`"8a1fb46622dffff"`) and integer formats
- Converts H3 boundary coordinates to [lng, lat] GeoJSON format
## H3 Hexagon System
@ -193,8 +195,8 @@ Dawarich uses H3 resolution 8 by default:
```mermaid
graph TD
A[User Data Import] --> B[Stats::CalculateMonth Service]
B --> C[Calculate H3 Hexagon Centers]
C --> D[Store in hexagon_centers Column]
B --> C[Calculate H3 Hex IDs]
C --> D[Store in h3_hex_ids Column]
D --> E[Stats Available for Sharing]
```
@ -203,7 +205,7 @@ graph TD
2. Background job triggers `Stats::CalculateMonth`
3. Service calculates monthly statistics including H3 hex IDs
4. H3 indexes are calculated for all points in the month
5. Results stored in `stats.h3_hex_ids` as JSON hash
5. Results stored in `stats.h3_hex_ids` as JSON hash with format `{"h3_index": [count, earliest, latest]}`
### 2. Sharing Activation
@ -251,16 +253,17 @@ graph TD
D --> E[HexagonRequestHandler]
E --> F[Find Stat by UUID]
F --> G[HexagonCenterManager]
G --> H[Load Pre-calculated Centers]
G --> H[Load Pre-calculated H3 Hex IDs]
H --> I[Convert to GeoJSON Polygons]
I --> J[Return FeatureCollection]
```
**Data Transformation:**
1. Retrieve stored H3 hex IDs hash from database
2. Convert each H3 index to hexagon boundary coordinates
3. Build GeoJSON Feature with properties (point count, timestamps)
4. Return complete FeatureCollection for map rendering
2. For each H3 index, use H3 library to get hexagon boundary coordinates
3. Convert coordinates to GeoJSON Polygon format ([lng, lat] ordering)
4. Build GeoJSON Feature with properties (point count, earliest/latest timestamps)
5. Return complete FeatureCollection for map rendering
## API Endpoints
@ -354,9 +357,9 @@ PATCH /stats/:year/:month/sharing
## Performance Considerations
### Pre-calculation Strategy
- **Background processing**: Hexagons calculated during stats job
- **Storage efficiency**: H3 indexes are compact
- **Query optimization**: GIN index on hexagon_centers column
- **Background processing**: H3 hex IDs calculated during stats job
- **Storage efficiency**: H3 indexes are compact and stored as hash keys
- **Query optimization**: GIN index on h3_hex_ids column
- **Caching**: Pre-calculated data serves multiple requests
### Memory Management
@ -367,14 +370,17 @@ PATCH /stats/:year/:month/sharing
### Database Optimization
```sql
-- Optimized queries
-- Optimized queries for H3 hex data
SELECT h3_hex_ids FROM stats
WHERE sharing_uuid = ? AND sharing_settings->>'enabled' = 'true';
-- Index for performance
-- GIN index for efficient JSONB queries
CREATE INDEX index_stats_on_h3_hex_ids
ON stats USING gin (h3_hex_ids)
WHERE (h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb);
-- Example H3 hex data structure in database
-- h3_hex_ids: {"8a1fb46622dffff": [15, 1640995200, 1640998800], ...}
```
## Error Handling
@ -430,10 +436,11 @@ ENABLE_PUBLIC_SHARING=true
### Common Issues
#### No Hexagons Displayed
1. Check if `hexagons_available?` returns true
2. Verify `h3_hex_ids` column has data
3. Confirm H3 library is properly installed
4. Check API endpoint returns valid GeoJSON
1. Check if `hexagons_available?` returns true for the stat
2. Verify `h3_hex_ids` column contains non-empty hash data
3. Confirm H3 gem is properly installed and accessible
4. Check API endpoint returns valid GeoJSON FeatureCollection
5. Verify H3 indexes are valid and can be converted to boundaries
#### Sharing Link Not Working
1. Verify UUID exists in database
@ -457,11 +464,18 @@ puts stat.public_accessible?
puts stat.hexagons_available?
"
# Verify H3 hex data format
# Verify H3 hex data format and structure
rails runner "
stat = Stat.first
puts stat.h3_hex_ids.class
puts stat.h3_hex_ids.first
stat = Stat.where.not(h3_hex_ids: {}).first
puts \"Data type: #{stat.h3_hex_ids.class}\"
puts \"Sample entry: #{stat.h3_hex_ids.first}\"
puts \"Total hexagons: #{stat.h3_hex_ids.size}\"
puts \"Available: #{stat.hexagons_available?}\"
# Test H3 polygon generation
h3_index, data = stat.h3_hex_ids.first
polygon = Maps::HexagonPolygonGenerator.new(h3_index: h3_index).call
puts \"Generated polygon type: #{polygon['type']}\"
"
```
@ -475,13 +489,14 @@ puts stat.h3_hex_ids.first
### Technical Improvements
- **CDN integration**: Faster global access to shared stats
- **Compression**: Further optimize H3 hex data storage
- **Compression**: Further optimize H3 hex data storage format
- **Real-time updates**: Live sharing for ongoing activities
- **API versioning**: Stable API contracts for external integration
- **H3 resolution optimization**: Dynamic resolution based on geographic area
- **Adaptive H3 resolution**: Dynamic resolution based on geographic area and zoom level
- **Polygon caching**: Cache generated polygons for frequently accessed stats
## Conclusion
The Shareable Stats feature provides a robust, secure, and performant way for Dawarich users to share their location insights. The H3 hexagon system offers excellent visualization while maintaining privacy through aggregated data. The UUID-based security model ensures that only intended recipients can access shared statistics, while the configurable expiration system gives users complete control over data visibility.
The architecture is designed for scalability and performance, with pre-calculated data reducing server load and providing fast response times for public viewers. The comprehensive error handling and monitoring ensure reliable operation in production environments.
The architecture is designed for scalability and performance, with pre-calculated H3 hex data reducing server load and providing fast response times for public viewers. The streamlined H3-only implementation ensures consistent polygon generation and efficient storage. The comprehensive error handling and monitoring ensure reliable operation in production environments.

View file

@ -5,15 +5,10 @@ require 'rails_helper'
RSpec.describe Maps::BoundsCalculator do
describe '.call' do
subject(:calculate_bounds) do
described_class.new(
user: target_user,
start_date: start_date,
end_date: end_date
).call
described_class.new(user:, start_date:, end_date:).call
end
let(:user) { create(:user) }
let(:target_user) { user }
let(:start_date) { '2024-06-01T00:00:00Z' }
let(:end_date) { '2024-06-30T23:59:59Z' }
@ -63,7 +58,7 @@ RSpec.describe Maps::BoundsCalculator do
end
context 'with no user' do
let(:target_user) { nil }
let(:user) { nil }
it 'raises NoUserFoundError' do
expect { calculate_bounds }.to raise_error(