Rename hexagon_centers to h3_hex_ids and update related logic

This commit is contained in:
Eugene Burmakin 2025-09-19 00:23:12 +02:00
parent 440b031a0c
commit 2bd0390d1a
10 changed files with 541 additions and 81 deletions

View file

@ -57,9 +57,9 @@ class Stat < ApplicationRecord
end
def hexagons_available?
hexagon_centers.present? &&
hexagon_centers.is_a?(Array) &&
hexagon_centers.any?
h3_hex_ids.present? &&
h3_hex_ids.is_a?(Hash) &&
h3_hex_ids.any?
end
def generate_new_sharing_uuid!

View file

@ -9,7 +9,6 @@ module Maps
def call
return build_response_from_centers if pre_calculated_centers_available?
return handle_legacy_area_too_large if legacy_area_too_large?
nil # No pre-calculated data available
end
@ -19,78 +18,59 @@ module Maps
attr_reader :stat, :user
def pre_calculated_centers_available?
return false if stat&.hexagon_centers.blank?
return false if stat&.h3_hex_ids.blank?
# Handle legacy hash format
if stat.hexagon_centers.is_a?(Hash)
!stat.hexagon_centers['area_too_large']
else
# Handle array format (actual hexagon centers)
stat.hexagon_centers.is_a?(Array) && stat.hexagon_centers.any?
end
end
def legacy_area_too_large?
stat&.hexagon_centers.is_a?(Hash) && stat.hexagon_centers['area_too_large']
stat.h3_hex_ids.is_a?(Hash) && stat.h3_hex_ids.any?
end
def build_response_from_centers
centers = stat.hexagon_centers
Rails.logger.debug "Using pre-calculated hexagon centers: #{centers.size} centers"
hex_ids = stat.h3_hex_ids
Rails.logger.debug "Using pre-calculated H3 hex IDs: #{hex_ids.size} hexagons"
result = build_hexagons_from_centers(centers)
result = build_hexagons_from_h3_ids(hex_ids)
{ success: true, data: result, pre_calculated: true }
end
def handle_legacy_area_too_large
Rails.logger.info "Recalculating previously skipped large area hexagons for stat #{stat.id}"
new_centers = recalculate_hexagon_centers
return nil unless new_centers.is_a?(Array)
update_stat_with_new_centers(new_centers)
end
def recalculate_hexagon_centers
def recalculate_h3_hex_ids
service = Stats::CalculateMonth.new(user.id, stat.year, stat.month)
service.send(:calculate_hexagon_centers)
service.send(:calculate_h3_hex_ids)
end
def update_stat_with_new_centers(new_centers)
stat.update(hexagon_centers: new_centers)
result = build_hexagons_from_centers(new_centers)
Rails.logger.debug "Successfully recalculated hexagon centers: #{new_centers.size} centers"
def update_stat_with_new_hex_ids(new_hex_ids)
stat.update(h3_hex_ids: new_hex_ids)
result = build_hexagons_from_h3_ids(new_hex_ids)
Rails.logger.debug "Successfully recalculated H3 hex IDs: #{new_hex_ids.size} hexagons"
{ success: true, data: result, pre_calculated: true }
end
def build_hexagons_from_centers(centers)
# Convert stored centers back to hexagon polygons
hexagon_features = centers.map.with_index { |center, index| build_hexagon_feature(center, index) }
def build_hexagons_from_h3_ids(hex_ids)
# Convert stored H3 IDs back to hexagon polygons
hexagon_features = hex_ids.map.with_index do |(h3_index, data), index|
build_hexagon_feature_from_h3(h3_index, data, index)
end
build_feature_collection(hexagon_features)
end
def build_hexagon_feature(center, index)
lng, lat, earliest, latest = center
def build_hexagon_feature_from_h3(h3_index, data, index)
count, earliest, latest = data
{
'type' => 'Feature',
'id' => index + 1,
'geometry' => generate_hexagon_geometry(lng, lat),
'properties' => build_hexagon_properties(index, earliest, latest)
'geometry' => generate_hexagon_geometry_from_h3(h3_index),
'properties' => build_hexagon_properties(index, count, earliest, latest)
}
end
def generate_hexagon_geometry(lng, lat)
Maps::HexagonPolygonGenerator.new(
center_lng: lng,
center_lat: lat
).call
def generate_hexagon_geometry_from_h3(h3_index)
Maps::HexagonPolygonGenerator.new(h3_index: h3_index).call
end
def build_hexagon_properties(index, earliest, latest)
def build_hexagon_properties(index, count, earliest, latest)
{
'hex_id' => index + 1,
'point_count' => count,
'earliest_point' => earliest ? Time.zone.at(earliest).iso8601 : nil,
'latest_point' => latest ? Time.zone.at(latest).iso8601 : nil
}

View file

@ -2,10 +2,11 @@
module Maps
class HexagonPolygonGenerator
def initialize(center_lng:, center_lat:, h3_resolution: 5)
def initialize(center_lng: nil, center_lat: nil, h3_resolution: 5, h3_index: nil)
@center_lng = center_lng
@center_lat = center_lat
@h3_resolution = h3_resolution
@h3_index = h3_index
end
def call
@ -14,7 +15,7 @@ module Maps
private
attr_reader :center_lng, :center_lat, :h3_resolution
attr_reader :center_lng, :center_lat, :h3_resolution, :h3_index
def generate_h3_hexagon_polygon
# Convert coordinates to H3 format [lat, lng]

View file

@ -86,7 +86,7 @@ class Stats::CalculateMonth
daily_distance: distance_by_day,
distance: distance(distance_by_day),
toponyms: toponyms,
hexagon_centers: calculate_hexagon_centers
h3_hex_ids: calculate_h3_hex_ids
)
stat.save
end
@ -132,22 +132,28 @@ class Stats::CalculateMonth
Stat.where(year:, month:, user:).destroy_all
end
def calculate_hexagon_centers
return nil if points.empty?
def calculate_h3_hex_ids
return {} if points.empty?
begin
result = calculate_h3_hexagon_centers
if result.empty?
Rails.logger.info "No H3 hexagon centers calculated for user #{user.id}, #{year}-#{month} (no data)"
return nil
Rails.logger.info "No H3 hex IDs calculated for user #{user.id}, #{year}-#{month} (no data)"
return {}
end
Rails.logger.info "Pre-calculated #{result.size} H3 hexagon centers for user #{user.id}, #{year}-#{month}"
result
# Convert array format to hash format: { h3_index => [count, earliest, latest] }
hex_hash = result.each_with_object({}) do |hex_data, hash|
h3_index, count, earliest, latest = hex_data
hash[h3_index] = [count, earliest, latest]
end
Rails.logger.info "Pre-calculated #{hex_hash.size} H3 hex IDs for user #{user.id}, #{year}-#{month}"
hex_hash
rescue PostGISError => e
Rails.logger.warn "H3 hexagon centers calculation failed for user #{user.id}, #{year}-#{month}: #{e.message}"
nil
Rails.logger.warn "H3 hex IDs calculation failed for user #{user.id}, #{year}-#{month}: #{e.message}"
{}
end
end

View file

@ -1,7 +0,0 @@
# frozen_string_literal: true
class AddHexagonDataToStats < ActiveRecord::Migration[8.0]
def change
add_column :stats, :hexagon_data, :jsonb
end
end

View file

@ -1,5 +0,0 @@
class AddHexagonCentersToStats < ActiveRecord::Migration[8.0]
def change
add_column :stats, :hexagon_centers, :jsonb
end
end

View file

@ -1,7 +0,0 @@
class AddIndexToHexagonCenters < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def change
add_index :stats, :hexagon_centers, using: :gin, where: "hexagon_centers IS NOT NULL", algorithm: :concurrently
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
class AddH3HexIdsToStats < ActiveRecord::Migration[8.0]
def change
add_column :stats, :h3_hex_ids, :jsonb, default: {}
add_index :stats, :h3_hex_ids, using: :gin, where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)"
end
end

5
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_14_095157) do
ActiveRecord::Schema[8.0].define(version: 2025_09_10_224714) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
enable_extension "postgis"
@ -222,10 +222,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_14_095157) do
t.jsonb "daily_distance", default: {}
t.jsonb "sharing_settings", default: {}
t.uuid "sharing_uuid"
t.jsonb "hexagon_data"
t.jsonb "hexagon_centers"
t.index ["distance"], name: "index_stats_on_distance"
t.index ["hexagon_centers"], name: "index_stats_on_hexagon_centers", where: "(hexagon_centers IS NOT NULL)", using: :gin
t.index ["month"], name: "index_stats_on_month"
t.index ["sharing_uuid"], name: "index_stats_on_sharing_uuid", unique: true
t.index ["user_id"], name: "index_stats_on_user_id"

View file

@ -0,0 +1,487 @@
# Shareable Stats Feature Documentation
## Overview
The Shareable Stats feature allows Dawarich users to publicly share their monthly location statistics without requiring authentication. This system provides a secure, time-limited way to share location insights while maintaining user privacy through configurable expiration settings and unguessable UUID-based access.
## Key Features
- **Time-based expiration**: Share links can expire after 1 hour, 12 hours, 24 hours, or be permanent
- **UUID-based security**: Each shared stat has a unique, unguessable UUID for secure access
- **Public API access**: Hexagon map data can be accessed via API without authentication when sharing is enabled
- **H3 Hexagon visualization**: Enhanced geographic data visualization using Uber's H3 hexagonal hierarchical spatial index
- **Automatic expiration**: Expired shares are automatically inaccessible
- **Privacy controls**: Users can enable/disable sharing and regenerate sharing URLs at any time
## Database Schema
### Stats Table Extensions
The sharing functionality extends the `stats` table with the following columns:
```sql
-- Public sharing configuration
sharing_settings JSONB DEFAULT {}
sharing_uuid UUID
-- Pre-calculated H3 hexagon data for performance
h3_hex_ids JSONB DEFAULT {}
-- Indexes for performance
INDEX ON h3_hex_ids USING GIN WHERE (h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)
```
### Sharing Settings Structure
```json
{
"enabled": true,
"expiration": "24h", // "1h", "12h", "24h", or "permanent"
"expires_at": "2024-01-15T12:00:00Z"
}
```
### H3 Hex IDs Data Format
The `h3_hex_ids` column stores pre-calculated H3 hexagon data as a hash:
```json
{
"8a1fb46622dffff": [15, 1640995200, 1640998800],
"8a1fb46622e7fff": [8, 1640996400, 1640999200],
// ... more H3 index entries
// Format: { "h3_index_string": [point_count, earliest_timestamp, latest_timestamp] }
}
```
## Architecture Components
### Models
#### Stat Model (`app/models/stat.rb`)
**Key Methods:**
- `sharing_enabled?`: Checks if sharing is enabled
- `sharing_expired?`: Validates expiration status
- `public_accessible?`: Combined check for sharing availability
- `hexagons_available?`: Verifies pre-calculated H3 hex data exists
- `enable_sharing!(expiration:)`: Enables sharing with expiration
- `disable_sharing!`: Disables sharing
- `generate_new_sharing_uuid!`: Regenerates sharing UUID
- `calculate_data_bounds`: Calculates geographic bounds for the month
### Controllers
#### Shared::StatsController (`app/controllers/shared/stats_controller.rb`)
Handles public sharing functionality:
**Routes:**
- `GET /shared/stats/:uuid` - Public view of shared stats
- `PATCH /stats/:year/:month/sharing` - Sharing management (authenticated)
**Key Methods:**
- `show`: Renders public stats view without authentication
- `update`: Manages sharing settings (enable/disable, expiration)
#### Api::V1::Maps::HexagonsController (`app/controllers/api/v1/maps/hexagons_controller.rb`)
Provides hexagon data for both authenticated and public access:
**Features:**
- Skip authentication for public sharing requests (`uuid` parameter)
- Context resolution for public vs. authenticated access
- Error handling for missing or expired shares
```ruby
# Public access via UUID
GET /api/v1/maps/hexagons?uuid=SHARING_UUID
# Authenticated access
GET /api/v1/maps/hexagons?start_date=2024-01-01&end_date=2024-01-31
```
### Services
#### Maps::HexagonRequestHandler (`app/services/maps/hexagon_request_handler.rb`)
Central service for processing hexagon requests:
**Workflow:**
1. Attempts to find matching stat for the request
2. Delegates to `HexagonCenterManager` for pre-calculated data
3. Returns empty feature collection if no data available
#### Maps::HexagonCenterManager (`app/services/maps/hexagon_center_manager.rb`)
Manages pre-calculated H3 hexagon data:
**Responsibilities:**
- Retrieves pre-calculated H3 hex IDs from database
- Converts stored H3 indexes to GeoJSON polygons
- Builds hexagon features with point counts and timestamps
- Handles efficient polygon generation from H3 indexes
**Data Flow:**
1. Check if pre-calculated H3 hex IDs are available
2. Convert H3 indexes to hexagon polygons using `HexagonPolygonGenerator`
3. Build GeoJSON FeatureCollection with metadata and point counts
#### Stats::CalculateMonth (`app/services/stats/calculate_month.rb`)
Responsible for calculating and storing hexagon data during stats processing:
**H3 Configuration:**
- `DEFAULT_H3_RESOLUTION = 8`: Small hexagons for good detail
- `MAX_HEXAGONS = 10_000`: Maximum to prevent memory issues
**Key Methods:**
- `calculate_h3_hex_ids`: Main method for H3 calculation and storage
- `calculate_h3_hexagon_centers`: Internal H3 calculation logic
- `calculate_h3_indexes`: Groups points into H3 hexagons
- `fetch_user_points_for_period`: Retrieves points for date range
**Algorithm:**
1. Fetch user points for the specified month
2. Convert each point to H3 index at specified resolution
3. Aggregate points per hexagon with count and timestamp bounds
4. Apply resolution reduction if hexagon count exceeds maximum
5. Store as hash of { h3_index_string => [count, earliest, latest] }
#### Maps::HexagonPolygonGenerator (`app/services/maps/hexagon_polygon_generator.rb`)
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
**Usage Modes:**
- **Center-based**: `new(center_lng: lng, center_lat: lat)`
## H3 Hexagon System
### What is H3?
H3 is Uber's Hexagonal Hierarchical Spatial Index that provides:
- **Uniform coverage**: Earth divided into hexagonal cells
- **Hierarchical resolution**: 16 levels from global to local
- **Efficient indexing**: Fast spatial queries and aggregations
- **Consistent shape**: Hexagons have uniform neighbors
### Resolution Levels
Dawarich uses H3 resolution 8 by default:
- **Resolution 8**: ~737m average hexagon edge length
- **Fallback mechanism**: Reduces resolution if too many hexagons
- **Maximum limit**: 10,000 hexagons to prevent memory issues
### Performance Benefits
1. **Pre-calculation**: H3 hexagons calculated once during stats processing
2. **Efficient storage**: Hash-based storage with H3 index as key
3. **Fast retrieval**: Database lookup instead of real-time calculation
4. **Reduced bandwidth**: Compact JSON hash format for API responses
5. **Direct polygon generation**: H3 index directly converts to polygon boundaries
## Workflow
### 1. Stats Calculation Process
```mermaid
graph TD
A[User Data Import] --> B[Stats::CalculateMonth Service]
B --> C[Calculate H3 Hexagon Centers]
C --> D[Store in hexagon_centers Column]
D --> E[Stats Available for Sharing]
```
**Detailed Steps:**
1. User imports location data (GPX, JSON, etc.)
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
### 2. Sharing Activation
```mermaid
graph TD
A[User Visits Stats Page] --> B[Enable Sharing Toggle]
B --> C[Select Expiration Duration]
C --> D[PATCH /stats/:year/:month/sharing]
D --> E[Generate/Update sharing_uuid]
E --> F[Set sharing_settings]
F --> G[Return Public URL]
```
**Sharing Settings:**
- **Expiration options**: 1h, 12h, 24h, permanent
- **UUID generation**: Secure random UUID for each stat
- **Expiration timestamp**: Calculated and stored in sharing_settings
### 3. Public Access Flow
```mermaid
graph TD
A[Public User Visits Shared URL] --> B[Validate UUID & Expiration]
B --> C{Valid & Not Expired?}
C -->|Yes| D[Load Public Stats View]
C -->|No| E[Redirect with Error]
D --> F[Render Map with Hexagons]
F --> G[Load Hexagon Data via API]
G --> H[Display Interactive Map]
```
**Security Checks:**
1. Verify sharing UUID exists in database
2. Check `sharing_settings.enabled = true`
3. Validate expiration timestamp if not permanent
4. Return 404 if any check fails
### 4. Hexagon Data Retrieval
```mermaid
graph TD
A[Map Requests Hexagon Data] --> B[GET /api/v1/maps/hexagons?uuid=UUID]
B --> C[HexagonsController]
C --> D[Skip Authentication for UUID Request]
D --> E[HexagonRequestHandler]
E --> F[Find Stat by UUID]
F --> G[HexagonCenterManager]
G --> H[Load Pre-calculated Centers]
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
## API Endpoints
### Public Sharing
#### View Shared Stats
```http
GET /shared/stats/:uuid
```
- **Authentication**: None required
- **Response**: HTML page with public stats view
- **Error Handling**: Redirects to root with alert if invalid/expired
#### Get Hexagon Data
```http
GET /api/v1/maps/hexagons?uuid=:uuid
```
- **Authentication**: None required for UUID access
- **Response**: GeoJSON FeatureCollection
- **Features**: Each feature represents one hexagon with point count and timestamps
### Authenticated Management
#### Toggle Sharing
```http
PATCH /stats/:year/:month/sharing
```
**Parameters:**
- `enabled`: "1" to enable, "0" to disable
- `expiration`: "1h", "12h", "24h", or "permanent" (when enabling)
**Response:**
```json
{
"success": true,
"sharing_url": "https://domain.com/shared/stats/uuid",
"message": "Sharing enabled successfully"
}
```
## Security Features
### UUID-based Access
- **Unguessable URLs**: Uses secure random UUIDs
- **No enumeration**: Can't guess valid sharing links
- **Automatic generation**: New UUID created for each sharing activation
### Time-based Expiration
- **Configurable duration**: Multiple expiration options
- **Automatic enforcement**: Expired shares become inaccessible
- **Precise timestamping**: ISO8601 format with timezone awareness
### Limited Data Exposure
- **No user identification**: Public view doesn't expose user details
- **Aggregated data only**: Only statistical summaries are shared
- **No raw location points**: Individual coordinates not exposed
### Privacy Controls
- **User control**: Users can enable/disable sharing at any time
- **UUID regeneration**: Can generate new sharing URL to invalidate old ones
- **Granular permissions**: Per-month sharing control
## Frontend Integration
### Public View Template (`app/views/stats/public_month.html.erb`)
**Features:**
- **Responsive design**: Mobile-friendly layout with Tailwind CSS
- **Monthly statistics**: Distance, active days, countries visited
- **Interactive hexagon map**: Leaflet.js with H3 hexagon overlay
- **Activity charts**: Daily distance visualization
- **Location summary**: Countries and cities visited
**Map Integration:**
```erb
<div id="public-monthly-stats-map"
data-controller="public-stat-map"
data-public-stat-map-uuid-value="<%= @stat.sharing_uuid %>"
data-public-stat-map-hexagons-available-value="<%= @hexagons_available.to_s %>">
</div>
```
### JavaScript Controller
**Stimulus Controller**: `public-stat-map`
- **Leaflet initialization**: Sets up interactive map
- **Hexagon layer**: Loads and renders hexagon data from API
- **User interaction**: Click handlers, zoom controls
- **Loading states**: Shows loading spinner during data fetch
## 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
- **Caching**: Pre-calculated data serves multiple requests
### Memory Management
- **Hexagon limits**: Maximum 10,000 hexagons per month
- **Resolution fallback**: Automatically reduces detail for large areas
- **Lazy loading**: Only calculate when stats are processed
- **Efficient formats**: JSON storage optimized for size
### Database Optimization
```sql
-- Optimized queries
SELECT h3_hex_ids FROM stats
WHERE sharing_uuid = ? AND sharing_settings->>'enabled' = 'true';
-- Index for performance
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);
```
## Error Handling
### Validation Errors
- **Missing UUID**: 404 response with user-friendly message
- **Expired sharing**: Redirect with appropriate alert
- **Invalid parameters**: Bad request with error details
### Service Errors
- **H3 calculation failures**: Graceful degradation, logs warning
- **Database errors**: Transaction rollback, user notification
- **Memory issues**: Resolution reduction, retry mechanism
### Frontend Resilience
- **Loading states**: User feedback during data fetching
- **Fallback content**: Display stats even if hexagons fail
- **Error messages**: Clear communication of issues
## Configuration
### Environment Variables
```bash
# H3 hexagon settings (optional, defaults shown)
H3_DEFAULT_RESOLUTION=8
H3_MAX_HEXAGONS=10000
# Feature flags
ENABLE_PUBLIC_SHARING=true
```
### Runtime Configuration
- **Resolution adaptation**: Automatic based on data size
- **Expiration options**: Configurable in sharing settings
- **Security headers**: CORS configuration for API access
## Monitoring and Analytics
### Logging
- **Share creation**: Log when sharing is enabled
- **Public access**: Log UUID-based requests (without exposing UUID)
- **Performance metrics**: H3 calculation timing
- **Error tracking**: Failed calculations and API errors
### Metrics
- **Sharing adoption**: How many users enable sharing
- **Expiration preferences**: Popular expiration durations
- **Performance**: Hexagon calculation and rendering times
- **Error rates**: Failed sharing requests
## Troubleshooting
### 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
#### Sharing Link Not Working
1. Verify UUID exists in database
2. Check sharing_settings.enabled = true
3. Validate expiration timestamp
4. Confirm public routes are properly configured
#### Performance Issues
1. Monitor hexagon count (should be < 10,000)
2. Check if resolution is too high for large areas
3. Verify database indexes are present
4. Consider increasing H3_MAX_HEXAGONS if needed
### Debug Commands
```bash
# Check sharing status for a stat
rails runner "
stat = Stat.find_by(sharing_uuid: 'UUID_HERE')
puts stat.public_accessible?
puts stat.hexagons_available?
"
# Verify H3 hex data format
rails runner "
stat = Stat.first
puts stat.h3_hex_ids.class
puts stat.h3_hex_ids.first
"
```
## Future Enhancements
### Planned Features
- **Social sharing**: Integration with social media platforms
- **Embedding**: Iframe widgets for external sites
- **Analytics**: View count and engagement metrics
- **Custom styling**: User-configurable map themes
### Technical Improvements
- **CDN integration**: Faster global access to shared stats
- **Compression**: Further optimize H3 hex data storage
- **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
## 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.