diff --git a/src/services/DataService.ts b/src/services/DataService.ts index d10e047..93a8187 100644 --- a/src/services/DataService.ts +++ b/src/services/DataService.ts @@ -47,14 +47,16 @@ async function fetchLiveTransactions(periodDays: number): Promise<{ let keyMap = new Map(); try { keyMap = await getIdentityKeyMap(); + console.log('[GéoFlux] keyMap:', keyMap.size, 'entrées'); } catch (err) { - console.warn('Identity key map indisponible :', err); + console.error('[GéoFlux] Identity key map ERREUR:', err); } // Clés Duniter uniques des émetteurs const duniterKeys = [...new Set( rawTransfers.map((t) => keyMap.get(t.fromId)).filter(Boolean) as string[] )]; + console.log('[GéoFlux] duniterKeys:', duniterKeys.length); // Résolution géo par clé Duniter (_id Cesium+) let geoMap = new Map(); @@ -63,8 +65,9 @@ async function fetchLiveTransactions(periodDays: number): Promise<{ for (const [key, p] of profiles) { geoMap.set(key, { lat: p.lat, lng: p.lng, city: p.city }); } + console.log('[GéoFlux] geoMap:', geoMap.size, 'entrées'); } catch (err) { - console.warn('Cesium+ indisponible :', err); + console.error('[GéoFlux] Cesium+ ERREUR:', err); } // Seules les transactions avec un profil géo entrent dans le heatmap diff --git a/src/services/adapters/CesiumAdapter.ts b/src/services/adapters/CesiumAdapter.ts index 8bf59cd..816d02e 100644 --- a/src/services/adapters/CesiumAdapter.ts +++ b/src/services/adapters/CesiumAdapter.ts @@ -22,15 +22,13 @@ export interface GeoProfile { lng: number; } +// geoPoint accepte n'importe quel type — Cesium+ utilise plusieurs formats ES geo_point const HitSchema = z.object({ _id: z.string(), _source: z.object({ title: z.string().optional(), city: z.string().optional(), - geoPoint: z.object({ - lat: z.coerce.number().min(-90).max(90), - lon: z.coerce.number().min(-180).max(180), - }).optional(), + geoPoint: z.unknown().optional(), }), }); @@ -40,6 +38,30 @@ const SearchResponseSchema = z.object({ }), }); +/** Normalise les différents formats Elasticsearch geo_point → {lat, lng} ou null */ +function parseGeoPoint(raw: unknown): { lat: number; lng: number } | null { + if (raw == null) return null; + // Format objet { lat, lon } + if (typeof raw === 'object' && !Array.isArray(raw)) { + const g = raw as Record; + const lat = Number(g.lat); + const lon = Number(g.lon); + if (isFinite(lat) && isFinite(lon) && lat >= -90 && lat <= 90) return { lat, lng: lon }; + } + // Format string "lat,lon" + if (typeof raw === 'string') { + const [a, b] = raw.split(',').map((s) => Number(s.trim())); + if (isFinite(a) && isFinite(b) && a >= -90 && a <= 90) return { lat: a, lng: b }; + } + // Format tableau [lon, lat] (ES geo_point array) + if (Array.isArray(raw) && raw.length === 2) { + const lon = Number(raw[0]); + const lat = Number(raw[1]); + if (isFinite(lat) && isFinite(lon) && lat >= -90 && lat <= 90) return { lat, lng: lon }; + } + return null; +} + /** * Résout les coordonnées par clé Duniter (Cesium+ _id). * Plus fiable que par nom car la clé est unique et indépendante de la casse. @@ -70,12 +92,13 @@ export async function resolveGeoByKeys( const result = new Map(); for (const hit of parsed.hits.hits) { const src = hit._source; - if (!src.geoPoint) continue; + const geo = parseGeoPoint(src.geoPoint); + if (!geo) continue; result.set(hit._id, { name: src.title ?? '', city: src.city ?? 'Inconnue', - lat: src.geoPoint.lat, - lng: src.geoPoint.lon, + lat: geo.lat, + lng: geo.lng, }); } return result; @@ -127,12 +150,13 @@ export async function resolveGeoByNames( const result = new Map(); for (const hit of parsed.hits.hits) { const src = hit._source; - if (src.geoPoint && src.title) { + const geo = parseGeoPoint(src.geoPoint); + if (geo && src.title) { result.set(src.title.toLowerCase(), { name: src.title, city: src.city ?? 'Inconnue', - lat: src.geoPoint.lat, - lng: src.geoPoint.lon, + lat: geo.lat, + lng: geo.lng, }); } }