# Piano di Implementazione: Geolocalizzazione e Calcolo Bounds ## Panoramica Questo documento descrive il piano per implementare le seguenti funzionalità nell'Ingress Dashboard: 1. **Geolocalizzazione dell'utente** al primo caricamento dell'applicazione 2. **Calcolo del center** della viewport della mappa 3. **Calcolo del radius** in metri in base allo zoom della mappa per coprire l'intera viewport 4. **Integrazione dei parametri center e radius** nell'API ## Architettura Proposta ### Componenti Coinvolti ``` ┌─────────────────────────────────────────────────────────────┐ │ App.tsx │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Stato: center, radius, plexts, loading, filters │ │ │ │ │ │ │ │ loadData() → fetchPlexts(center, radius, filters) │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ props: onCenterChange, onRadiusChange ▼ ┌─────────────────────────────────────────────────────────────┐ │ PlayerMap.tsx │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ useMapGeolocation() │ │ │ │ - map.locate() al mount │ │ │ │ - onLocationFound → setView(lat, lng, zoom) │ │ │ │ │ │ │ │ useMapBounds() │ │ │ │ - onMoveEnd → calcola center e radius │ │ │ │ - onZoomEnd → calcola center e radius │ │ │ │ - invoca callbacks onCenterChange, onRadiusChange │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ params: center, radius, filters ▼ ┌─────────────────────────────────────────────────────────────┐ │ api.ts │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ fetchPlexts(base_url, endpoint, creds, { │ │ │ │ center: [lat, lng], │ │ │ │ radius: meters, │ │ │ │ timestamp_from, timestamp_to, │ │ │ │ player_name, limit │ │ │ │ }) │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Dettagli di Implementazione ### 1. Aggiornamento dei Tipi (`src/types.ts`) Aggiungere le interfacce per i nuovi parametri: ```typescript export interface MapCenter { lat: number; lng: number; } export interface MapBoundsParams { center: [number, number]; // [lat, lng] radius: number; // metri } ``` ### 2. Aggiornamento dell'API (`src/api.ts`) Modificare la funzione `fetchPlexts` per accettare i nuovi parametri: ```typescript export async function fetchPlexts( base_url: string, endpoint: string, creds: string, params?: { center?: [number, number]; // [lat, lng] radius?: number; // metri timestamp_from?: number; timestamp_to?: number; player_name?: string; limit?: number; } ): Promise ``` I parametri `center` e `radius` verranno aggiunti alla query string. ### 3. Hook Personalizzato: `useMapGeolocation` Creare un nuovo file `src/hooks/useMapGeolocation.ts`: ```typescript import { useEffect } from 'react'; import { useMap } from 'react-leaflet'; interface UseMapGeolocationOptions { enableHighAccuracy?: boolean; timeout?: number; maximumAge?: number; zoom?: number; } export function useMapGeolocation(options: UseMapGeolocationOptions = {}) { const map = useMap(); useEffect(() => { map.locate({ setView: true, maxZoom: options.zoom || 13, enableHighAccuracy: options.enableHighAccuracy ?? true, timeout: options.timeout || 10000, maximumAge: options.maximumAge || 0, }); const onLocationFound = (e: any) => { console.log('Location found:', e.latlng); }; const onLocationError = (e: any) => { console.error('Location error:', e.message); // Fallback to default location map.setView([45.57, 12.36], 12); }; map.on('locationfound', onLocationFound); map.on('locationerror', onLocationError); return () => { map.off('locationfound', onLocationFound); map.off('locationerror', onLocationError); }; }, [map, options]); } ``` ### 4. Hook Personalizzato: `useMapBounds` Creare un nuovo file `src/hooks/useMapBounds.ts`: ```typescript import { useEffect } from 'react'; import { useMap } from 'react-leaflet'; import L from 'leaflet'; interface UseMapBoundsOptions { onCenterChange?: (center: [number, number]) => void; onRadiusChange?: (radius: number) => void; debounceMs?: number; } /** * Calcola il raggio in metri dai bounds della mappa * Il raggio è la distanza dal centro al bordo della viewport */ function calculateRadiusFromBounds(map: L.Map): number { const bounds = map.getBounds(); const center = bounds.getCenter(); const northEast = bounds.getNorthEast(); // Calcola la distanza dal centro al bordo nord-est const radius = center.distanceTo(northEast); return Math.round(radius); } export function useMapBounds(options: UseMapBoundsOptions = {}) { const map = useMap(); const { onCenterChange, onRadiusChange, debounceMs = 500 } = options; useEffect(() => { let timeoutId: NodeJS.Timeout | null = null; const updateBounds = () => { const center = map.getCenter(); const radius = calculateRadiusFromBounds(map); if (onCenterChange) { onCenterChange([center.lat, center.lng]); } if (onRadiusChange) { onRadiusChange(radius); } }; const onMoveEnd = () => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(updateBounds, debounceMs); }; const onZoomEnd = () => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(updateBounds, debounceMs); }; // Calcola bounds iniziali updateBounds(); map.on('moveend', onMoveEnd); map.on('zoomend', onZoomEnd); return () => { if (timeoutId) { clearTimeout(timeoutId); } map.off('moveend', onMoveEnd); map.off('zoomend', onZoomEnd); }; }, [map, onCenterChange, onRadiusChange, debounceMs]); } ``` ### 5. Aggiornamento di `PlayerMap.tsx` Aggiungere le nuove funzionalità: ```typescript import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet"; import MarkerClusterGroup from "react-leaflet-cluster"; import L from "leaflet"; import { useMapGeolocation } from "../hooks/useMapGeolocation"; import { useMapBounds } from "../hooks/useMapBounds"; interface PlayerMapProps { plexts: Plext[]; onCenterChange?: (center: [number, number]) => void; onRadiusChange?: (radius: number) => void; } // Componente interno per gli hooks function MapContent({ onCenterChange, onRadiusChange }: { onCenterChange?: (center: [number, number]) => void; onRadiusChange?: (radius: number) => void; }) { useMapGeolocation({ zoom: 13 }); useMapBounds({ onCenterChange, onRadiusChange, debounceMs: 500 }); return null; } export function PlayerMap({ plexts, onCenterChange, onRadiusChange }: PlayerMapProps) { // ... codice esistente per i marker ... return (
{/* markers */}
); } ``` ### 6. Aggiornamento di `App.tsx` Aggiungere lo stato per center e radius e passarlo all'API: ```typescript export function App() { const [data, setData] = useState({ count: 0, plexts: [] }); const [loading, setLoading] = useState(true); const [timestampFrom, setTimestampFrom] = useState(null); const [timestampTo, setTimestampTo] = useState(null); const [playerName, setPlayerName] = useState("all"); const [limit, setLimit] = useState(100); const [mapCenter, setMapCenter] = useState<[number, number]>([45.57, 12.36]); const [mapRadius, setMapRadius] = useState(10000); // Default 10km const loadData = async () => { try { const params = { center: mapCenter, radius: mapRadius, timestamp_from: timestampFrom ? timestampFrom.getTime() : undefined, timestamp_to: timestampTo ? timestampTo.getTime() : undefined, player_name: playerName !== "all" ? playerName : undefined, limit, }; const result = await fetchPlexts( process.env.PUBLIC_API_BASE_URL || "", process.env.PUBLIC_API_ENDPOINT || "", process.env.PUBLIC_API_AUTH_CREDENTIALS || "", params ); setData(result); } catch (error) { console.error("Error loading data:", error); } finally { setLoading(false); } }; useEffect(() => { loadData(); const interval = setInterval(loadData, 60000); return () => clearInterval(interval); }, [timestampFrom, timestampTo, playerName, limit, mapCenter, mapRadius]); // ... resto del codice ... return ( // ... // ... ); } ``` ## Diagramma di Flusso ```mermaid flowchart TD A[App Mount] --> B[PlayerMap Render] B --> C[MapContainer Initialize] C --> D[useMapGeolocation Hook] D --> E{Geolocation Available?} E -->|Yes| F[map.locate] E -->|No| G[Use Default Center] F --> H[locationfound Event] H --> I[map.setView to User Location] I --> J[useMapBounds Hook] J --> K[Calculate Initial Center & Radius] K --> L[onCenterChange Callback] L --> M[App State Updated] M --> N[loadData with center & radius] N --> O[fetchPlexts API Call] O --> P[Update Data State] P --> Q[Re-render Map with New Data] J --> R[User Moves/Zooms Map] R --> S[moveend/zoomend Events] S --> T[Debounced Update] T --> U[Recalculate Center & Radius] U --> L ``` ## Considerazioni Tecniche ### Geolocalizzazione 1. **Permessi**: L'utente deve concedere il permesso di geolocalizzazione. Se rifiuta, usare la posizione di default. 2. **Timeout**: Impostare un timeout ragionevole (es. 10 secondi) per evitare attese infinite. 3. **Fallback**: Se la geolocalizzazione fallisce, mantenere la posizione di default (Veneto region: [45.57, 12.36]). 4. **Privacy**: Non memorizzare la posizione dell'utente in modo persistente. ### Calcolo del Radius 1. **Formula**: Il raggio viene calcolato come la distanza dal centro al bordo nord-est della viewport usando `center.distanceTo(northEast)`. 2. **Unità**: Il raggio è espresso in metri. 3. **Debouncing**: Aggiungere un debouncing di 500ms per evitare chiamate API eccessive durante lo spostamento della mappa. ### Performance 1. **Debouncing**: Usare debouncing per gli eventi `moveend` e `zoomend` per ridurre le chiamate API. 2. **Refresh Interval**: Mantenere l'intervallo di refresh di 60 secondi per i dati. 3. **Lazy Loading**: I dati vengono caricati solo quando necessario (al cambio di filtri o posizione). ### API Integration 1. **Parametri**: I parametri `center` e `radius` vengono aggiunti alla query string dell'API. 2. **Formato**: `center` come `[lat, lng]` e `radius` come numero intero in metri. 3. **Compatibilità**: Mantenere la compatibilità con i parametri esistenti. ## Dipendenze ### Nuove Dipendenze (se necessarie) Non sono richieste nuove dipendenze. React-Leaflet e Leaflet forniscono già tutte le funzionalità necessarie. ### Dipendenze Esistenti - `react-leaflet`: Per la mappa e gli hooks - `leaflet`: Per le funzioni di calcolo della distanza - `react`: Per gli hooks e la gestione dello stato ## Rischi e Mitigazioni | Rischio | Mitigazione | |---------|-------------| | Geolocalizzazione non disponibile | Fallback a posizione di default | | Troppo chiamate API durante pan/zoom | Debouncing di 500ms | | Calcolo radius impreciso | Usare `distanceTo` di Leaflet per precisione | | Performance con molti marker | Clustering già implementato | | Permessi geolocalizzazione negati | Gestire errore e usare default | ## Criteri di Successo 1. ✅ L'applicazione geolocalizza l'utente al primo caricamento 2. ✅ Il center della mappa viene calcolato e passato all'API 3. ✅ Il radius viene calcolato in metri e copre l'intera viewport 4. ✅ I dati vengono aggiornati quando la mappa viene spostata/zoomata 5. ✅ L'API riceve correttamente i parametri center e radius 6. ✅ L'esperienza utente rimane fluida (nessun lag eccessivo) 7. ✅ Il fallback funziona correttamente se la geolocalizzazione fallisce ## Ordine di Esecuzione 1. Aggiornare i tipi in `src/types.ts` 2. Aggiornare l'API in `src/api.ts` 3. Creare l'hook `useMapGeolocation.ts` 4. Creare l'hook `useMapBounds.ts` 5. Aggiornare `PlayerMap.tsx` 6. Aggiornare `App.tsx` 7. Testare l'integrazione completa