diff --git a/frontend/src/components/SatelliteMap.tsx b/frontend/src/components/SatelliteMap.tsx
index 7905edb..e7414b9 100644
--- a/frontend/src/components/SatelliteMap.tsx
+++ b/frontend/src/components/SatelliteMap.tsx
@@ -246,7 +246,7 @@ export function SatelliteMap({ events, currentTime, aircraft, satellites, ships,
)}
{/* Overlay layers */}
- {layers.aircraft &&
}
+ {layers.aircraft &&
}
{layers.satellites &&
}
{layers.ships &&
}
diff --git a/frontend/src/services/celestrak.ts b/frontend/src/services/celestrak.ts
index 0d9b6ce..913f189 100644
--- a/frontend/src/services/celestrak.ts
+++ b/frontend/src/services/celestrak.ts
@@ -80,16 +80,21 @@ function isNearMiddleEast(sat: Satellite): boolean {
let satCache: { sats: Satellite[]; ts: number } | null = null;
const SAT_CACHE_TTL = 10 * 60_000;
-export async function fetchSatelliteTLE(): Promise
{
- // Return cache if fresh
- if (satCache && Date.now() - satCache.ts < SAT_CACHE_TTL) {
- return satCache.sats;
+async function fetchSatellitesFromBackend(region: 'iran' | 'korea' = 'iran'): Promise {
+ try {
+ const res = await fetch(`/api/kcg/satellites?region=${region}`, { credentials: 'include' });
+ if (!res.ok) return [];
+ const data = await res.json();
+ return (data.satellites ?? []) as Satellite[];
+ } catch {
+ return [];
}
+}
+async function fetchSatelliteTLEFromCelesTrak(): Promise {
const allSats: Satellite[] = [];
const seenIds = new Set();
- // Fetch TLE groups from CelesTrak sequentially (avoid hammering)
for (const { group, category } of CELESTRAK_GROUPS) {
try {
const url = `/api/celestrak/NORAD/elements/gp.php?GROUP=${group}&FORMAT=tle`;
@@ -111,12 +116,10 @@ export async function fetchSatelliteTLE(): Promise {
}
}
- if (allSats.length === 0) {
- console.warn('CelesTrak: no data fetched, using fallback');
- return FALLBACK_SATELLITES;
- }
+ return allSats;
+}
- // For GEO/MEO sats keep all, for LEO filter to Middle East region
+function filterSatellitesByRegion(allSats: Satellite[], isNearFn: (sat: Satellite) => boolean): Satellite[] {
const filtered: Satellite[] = [];
for (const sat of allSats) {
try {
@@ -126,12 +129,10 @@ export async function fetchSatelliteTLE(): Promise {
const geo = satellite.eciToGeodetic(pv.position, satellite.gstime(new Date()));
const altKm = geo.height;
- // GEO (>30000km) and MEO (>5000km): always include (they cover wide areas)
if (altKm > 5000) {
filtered.push(sat);
} else {
- // LEO: only keep if passes near Middle East
- if (isNearMiddleEast(sat)) {
+ if (isNearFn(sat)) {
filtered.push(sat);
}
}
@@ -139,12 +140,30 @@ export async function fetchSatelliteTLE(): Promise {
// skip bad TLE
}
}
+ return filtered.slice(0, 100);
+}
- // Cap at ~100 satellites to keep rendering performant
- const capped = filtered.slice(0, 100);
+export async function fetchSatelliteTLE(): Promise {
+ if (satCache && Date.now() - satCache.ts < SAT_CACHE_TTL) {
+ return satCache.sats;
+ }
+ // 백엔드 API 우선
+ let allSats = await fetchSatellitesFromBackend('iran');
+
+ // 백엔드 실패 시 CelesTrak 직접 호출 fallback
+ if (allSats.length === 0) {
+ allSats = await fetchSatelliteTLEFromCelesTrak();
+ }
+
+ if (allSats.length === 0) {
+ console.warn('CelesTrak: no data fetched, using fallback');
+ return FALLBACK_SATELLITES;
+ }
+
+ const capped = filterSatellitesByRegion(allSats, isNearMiddleEast);
satCache = { sats: capped, ts: Date.now() };
- console.log(`CelesTrak: loaded ${capped.length} satellites (from ${allSats.length} total)`);
+ console.log(`Satellites: loaded ${capped.length} (from ${allSats.length} total)`);
return capped;
}
@@ -176,46 +195,19 @@ export async function fetchSatelliteTLEKorea(): Promise {
return satCacheKorea.sats;
}
- const allSats: Satellite[] = [];
- const seenIds = new Set();
+ // 백엔드 API 우선
+ let allSats = await fetchSatellitesFromBackend('korea');
- for (const { group, category } of CELESTRAK_GROUPS) {
- try {
- const url = `/api/celestrak/NORAD/elements/gp.php?GROUP=${group}&FORMAT=tle`;
- const res = await fetch(url);
- if (!res.ok) continue;
- const text = await res.text();
- const parsed = parseTLE(text, category);
- for (const sat of parsed) {
- if (!seenIds.has(sat.noradId)) {
- seenIds.add(sat.noradId);
- allSats.push(sat);
- }
- }
- } catch { /* skip */ }
+ // 백엔드 실패 시 CelesTrak 직접 호출 fallback
+ if (allSats.length === 0) {
+ allSats = await fetchSatelliteTLEFromCelesTrak();
}
if (allSats.length === 0) return FALLBACK_SATELLITES;
- const filtered: Satellite[] = [];
- for (const sat of allSats) {
- try {
- const satrec = satellite.twoline2satrec(sat.tle1, sat.tle2);
- const pv = satellite.propagate(satrec, new Date());
- if (!pv || typeof pv.position === 'boolean' || !pv.position) continue;
- const geo = satellite.eciToGeodetic(pv.position, satellite.gstime(new Date()));
- const altKm = geo.height;
- if (altKm > 5000) {
- filtered.push(sat);
- } else {
- if (isNearKorea(sat)) filtered.push(sat);
- }
- } catch { /* skip */ }
- }
-
- const capped = filtered.slice(0, 100);
+ const capped = filterSatellitesByRegion(allSats, isNearKorea);
satCacheKorea = { sats: capped, ts: Date.now() };
- console.log(`CelesTrak Korea: loaded ${capped.length} satellites`);
+ console.log(`Satellites Korea: loaded ${capped.length} (from ${allSats.length} total)`);
return capped;
}
diff --git a/frontend/src/services/navWarning.ts b/frontend/src/services/navWarning.ts
index 720abaa..5f10d33 100644
--- a/frontend/src/services/navWarning.ts
+++ b/frontend/src/services/navWarning.ts
@@ -45,12 +45,12 @@ function dms(d: number, m: number, s: number): number {
}
/** Compute center of polygon */
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
function center(pts: [number, number][]): [number, number] {
const lat = pts.reduce((s, p) => s + p[0], 0) / pts.length;
const lng = pts.reduce((s, p) => s + p[1], 0) / pts.length;
return [lat, lng];
}
+void center;
// ═══════════════════════════════════════════════════════
// 해상사격장 구역 데이터 (WGS-84)
diff --git a/frontend/src/services/osint.ts b/frontend/src/services/osint.ts
index b5d1456..16d05f2 100644
--- a/frontend/src/services/osint.ts
+++ b/frontend/src/services/osint.ts
@@ -715,7 +715,25 @@ const PINNED_KOREA: OsintItem[] = [
];
// ── Main fetch: merge all sources, deduplicate, sort by time ──
+async function fetchOsintFromBackend(region: 'iran' | 'korea'): Promise {
+ try {
+ const res = await fetch(`/api/kcg/osint?region=${region}`, { credentials: 'include' });
+ if (!res.ok) return [];
+ const data = await res.json();
+ return (data.items ?? []) as OsintItem[];
+ } catch {
+ return [];
+ }
+}
+
export async function fetchOsintFeed(focus: 'iran' | 'korea' = 'iran'): Promise {
+ // 백엔드 API 우선 시도
+ const backendItems = await fetchOsintFromBackend(focus);
+ if (backendItems.length > 0) {
+ return backendItems;
+ }
+
+ // 백엔드 실패 시 직접 호출 fallback
const gdeltKw = focus === 'korea' ? GDELT_KEYWORDS_KOREA : GDELT_KEYWORDS_IRAN;
const gnKrKw = focus === 'korea' ? GNEWS_KR_KOREA : GNEWS_KR_IRAN;
const gnEnKw = focus === 'korea' ? GNEWS_EN_KOREA : GNEWS_EN_IRAN;