feat: implement hotspot visualization on map

- Add HotspotSeverity type and HotspotWithSeverity interface
- Implement getHotspots() method in analyticsService with severity calculation
- Add hotspot layer to MapView component with color-coded circles
- Create hotspot markers with severity icons and report count badges
- Add detailed hotspot popups showing report count, severity score, radius, and dates
- Implement hotspot toggle control in MapFilters with loading state
- Add hotspot severity legend to MapFilters
- Integrate hotspot data fetching in MapPage with sample fallback data
- Color-code hotspot boundaries: yellow (low), orange (medium/high), red (critical)
- Display hotspot statistics in interactive popups

Implements task 12: Create hotspot visualization
Requirements: 3.2, 3.3, 3.5
This commit is contained in:
2025-11-16 19:01:26 +00:00
parent 82635559db
commit 40a96142ba
6 changed files with 371 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
// Analytics service for API interactions
import { apiClient } from './api';
import type { AnalyticsSummary, TimeSeriesDataPoint, LitterTypeDistribution, QuantityAggregation } from '@/types';
import type { AnalyticsSummary, TimeSeriesDataPoint, LitterTypeDistribution, QuantityAggregation, Hotspot, HotspotWithSeverity, HotspotSeverity } from '@/types';
class AnalyticsService {
/**
@@ -91,6 +91,39 @@ class AnalyticsService {
return response.blob();
}
/**
* Get hotspot data for map visualization
*/
async getHotspots(): Promise<HotspotWithSeverity[]> {
const hotspots = await apiClient.get<Hotspot[]>('/analytics/hotspots');
// Calculate severity based on score and add to each hotspot
return hotspots.map(hotspot => ({
...hotspot,
severity: this.calculateHotspotSeverity(hotspot.severityScore, hotspot.reportCount),
}));
}
/**
* Calculate hotspot severity based on score and report count
*/
private calculateHotspotSeverity(score: number, reportCount: number): HotspotSeverity {
// Critical: score >= 0.8 or 20+ reports
if (score >= 0.8 || reportCount >= 20) {
return 'critical';
}
// High: score >= 0.6 or 15+ reports
if (score >= 0.6 || reportCount >= 15) {
return 'high';
}
// Medium: score >= 0.4 or 10+ reports
if (score >= 0.4 || reportCount >= 10) {
return 'medium';
}
// Low: everything else
return 'low';
}
}
export const analyticsService = new AnalyticsService();