mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Fix missing hexes
This commit is contained in:
parent
2fe36f02d6
commit
a20a3c5b36
6 changed files with 71 additions and 57 deletions
File diff suppressed because one or more lines are too long
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue