const { fetchPeeringDB } = require("../../services/peeringdb"); const { CITY_COORDS } = require("../../data/city-coords"); const { IX_CITY_MAP } = require("../../data/ix-city-map"); // Batch-fetch facility lat/lon by ID, 25 per request (PeeringDB /fac?id__in= limit), // bounded by an overall race timeout. Returns {fac_id: {lat, lon}}. async function fetchFacCoordsBatch(facIds, timeoutMs) { const coordMap = {}; if (facIds.length === 0) return coordMap; try { const chunks = []; for (let i = 0; i < facIds.length; i += 25) chunks.push(facIds.slice(i, i + 25)); const coordResults = await Promise.race([ Promise.all(chunks.map(chunk => fetchPeeringDB("/fac?id__in=" + chunk.join(",") + "&fields=id,latitude,longitude").catch(() => null) )), new Promise(r => setTimeout(() => r([]), timeoutMs)), ]); (coordResults || []).forEach(res => { (res?.data || []).forEach(f => { if (f.latitude && f.longitude) coordMap[f.id] = { lat: f.latitude, lon: f.longitude }; }); }); } catch (e) { /* graceful degradation */ } return coordMap; } // Resolve facility coordinates for the map, then IX locations via ixfac -> fac // coordinates (max 20 IXs), then the name/CITY_COORDS/IX_CITY_MAP geocode // fallbacks for IXs PeeringDB has no facility coordinates for. async function resolveFacilitiesAndIxLocations(facilitiesRaw, ixConnections) { // Batch-fetch facility coordinates for map (max 50 facilities) const facIds = facilitiesRaw.map(f => f.fac_id).filter(Boolean).slice(0, 50); let facCoordMap = await fetchFacCoordsBatch(facIds, 5000); const facilities = facilitiesRaw.map(f => ({ ...f, latitude: facCoordMap[f.fac_id] ? facCoordMap[f.fac_id].lat : null, longitude: facCoordMap[f.fac_id] ? facCoordMap[f.fac_id].lon : null, })); // Get IX locations for map via ixfac -> fac coordinates (max 20 IXs) const uniqueIxIds = [...new Set(ixConnections.map(c => c.ix_id))].filter(Boolean).slice(0, 20); let ixLocations = []; if (uniqueIxIds.length > 0) { try { const ixFacData = await Promise.race([ fetchPeeringDB("/ixfac?ix_id__in=" + uniqueIxIds.join(",")), new Promise(r => setTimeout(() => r(null), 5000)), ]); const ixFacs = ixFacData?.data || []; // Collect unique fac_ids we don't already have coords for const extraFacIds = [...new Set(ixFacs.map(f => f.fac_id).filter(id => id && !facCoordMap[id]))].slice(0, 30); if (extraFacIds.length > 0) { const extraCoords = await fetchFacCoordsBatch(extraFacIds, 4000); facCoordMap = { ...facCoordMap, ...extraCoords }; } // Build IX locations: pick first facility with coords per IX const ixNameMap = {}; ixConnections.forEach(c => { if (c.ix_id && c.ix_name) ixNameMap[c.ix_id] = c.ix_name; }); const seenIx = {}; ixFacs.forEach(f => { if (seenIx[f.ix_id]) return; const coords = facCoordMap[f.fac_id]; if (coords) { seenIx[f.ix_id] = true; ixLocations.push({ ix_id: f.ix_id, name: ixNameMap[f.ix_id] || f.name || "", city: f.city || "", country: f.country || "", latitude: coords.lat, longitude: coords.lon }); } }); } catch (e) { /* graceful degradation */ } } applyIxGeocodeFallback(ixLocations, ixConnections); return { facilities, ixLocations }; } // === IX Location Geocode Fallback === // Some IXPs have no facility coordinates in PeeringDB. // Use ix_name city extraction + hard-coded IX→city map as fallback. // Mutates ixLocations in place (matches original control flow). function applyIxGeocodeFallback(ixLocations, ixConnections) { var ixIdsWithCoords = new Set(ixLocations.map(function(l) { return l.ix_id; })); ixConnections.forEach(function(conn) { if (ixIdsWithCoords.has(conn.ix_id)) return; var name = conn.ix_name || ""; if (name) { var words = name.toLowerCase().replace(/[^a-z\s]/g, " ").split(/\s+/).filter(Boolean); for (var w = 0; w < words.length; w++) { if (CITY_COORDS[words[w]]) { ixLocations.push({ ix_id: conn.ix_id, name: name, city: words[w].charAt(0).toUpperCase() + words[w].slice(1), country: "", latitude: CITY_COORDS[words[w]][0], longitude: CITY_COORDS[words[w]][1], source: "name_geocode" }); ixIdsWithCoords.add(conn.ix_id); return; } if (w < words.length - 1) { var tw = words[w] + " " + words[w + 1]; if (CITY_COORDS[tw]) { ixLocations.push({ ix_id: conn.ix_id, name: name, city: tw, country: "", latitude: CITY_COORDS[tw][0], longitude: CITY_COORDS[tw][1], source: "name_geocode" }); ixIdsWithCoords.add(conn.ix_id); return; } } } } }); // Hard-coded IX ID → city for well-known IXPs whose names don't contain city ixConnections.forEach(function(conn) { if (ixIdsWithCoords.has(conn.ix_id)) return; var city = IX_CITY_MAP[conn.ix_id]; if (city && CITY_COORDS[city]) { ixLocations.push({ ix_id: conn.ix_id, name: conn.ix_name || ("IX " + conn.ix_id), city: city.charAt(0).toUpperCase() + city.slice(1), country: "", latitude: CITY_COORDS[city][0], longitude: CITY_COORDS[city][1], source: "ix_city_map" }); } }); } module.exports = { resolveFacilitiesAndIxLocations };