Skip to content

Commit 6d60907

Browse files
committed
commit message for updating Lab components and settings
1 parent d060e0e commit 6d60907

File tree

11 files changed

+1313
-1
lines changed

11 files changed

+1313
-1
lines changed

backend/research_lab/space_views.py

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.

backend/research_lab/urls.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,20 @@
1616
path('space/iss/', space_views.iss_now, name='space-iss-now'),
1717
path('space/neo/', space_views.neo_summary, name='space-neo-summary'),
1818
path('space/donki/', space_views.donki_summary, name='space-donki-summary'),
19+
20+
path('space/eonet/', space_views.eonet_events, name='space-eonet-events'),
21+
path('space/epic/', space_views.epic_latest, name='space-epic-latest'),
22+
path('space/exoplanet/', space_views.exoplanet_sample, name='space-exoplanet-sample'),
23+
path('space/images/', space_views.nasa_images_search, name='space-nasa-images-search'),
24+
path('space/techport/', space_views.techport_projects, name='space-techport-projects'),
25+
path('space/techtransfer/', space_views.techtransfer_patents, name='space-techtransfer-patents'),
26+
path('space/ssd-cneos/', space_views.ssd_cneos_close_approach, name='space-ssd-cneos-cad'),
27+
path('space/tle/', space_views.tle_25544, name='space-tle'),
28+
29+
# Info endpoints for non-JSON / special services
30+
path('space/gibs/', space_views.gibs_info, name='space-gibs-info'),
31+
path('space/trek-wmts/', space_views.trek_wmts_info, name='space-trek-wmts-info'),
32+
path('space/insight/', space_views.insight_info, name='space-insight-info'),
33+
path('space/osdr/', space_views.osdr_info, name='space-osdr-info'),
34+
path('space/ssc/', space_views.ssc_info, name='space-ssc-info'),
1935
]

frontend/package-lock.json

Lines changed: 360 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"@testing-library/user-event": "^13.5.0",
1010
"axios": "^1.6.2",
1111
"html2pdf.js": "^0.12.1",
12+
"mapbox-gl": "^3.17.0",
13+
"ol": "^10.7.0",
1214
"react": "^18.2.0",
1315
"react-dom": "^18.2.0",
1416
"react-icons": "^5.5.0",

frontend/src/App.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { Suspense, lazy } from 'react';
22
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
33
import Layout from './components/Layout/Layout';
44
import FloatingChatbot from './components/FloatingChatbot';
@@ -55,6 +55,10 @@ import ChatbotAdmin from './pages/Admin/ChatbotAdmin';
5555
import ProtectedRoute from './components/ProtectedRoute';
5656
import { AuthProvider } from './context/AuthContext';
5757

58+
const SpaceApiPage = lazy(() => import('./pages/Lab/SpaceApiPage'));
59+
const SpaceMapPage = lazy(() => import('./pages/Lab/SpaceMapPage'));
60+
const MarsTrekMapPage = lazy(() => import('./pages/Lab/MarsTrekMapPage'));
61+
5862
function App() {
5963
return (
6064
<AuthProvider>
@@ -99,6 +103,30 @@ function App() {
99103

100104
{/* Space Lab */}
101105
<Route path="space" element={<SpaceLabOverview />} />
106+
<Route
107+
path="space/map"
108+
element={
109+
<Suspense fallback={<div className="py-6">Loading map…</div>}>
110+
<SpaceMapPage />
111+
</Suspense>
112+
}
113+
/>
114+
<Route
115+
path="space/mars-map"
116+
element={
117+
<Suspense fallback={<div className="py-6">Loading map…</div>}>
118+
<MarsTrekMapPage />
119+
</Suspense>
120+
}
121+
/>
122+
<Route
123+
path="space/apis/:apiId"
124+
element={
125+
<Suspense fallback={<div className="py-6">Loading…</div>}>
126+
<SpaceApiPage />
127+
</Suspense>
128+
}
129+
/>
102130
<Route path="space/astrophysics" element={<SpaceModulePage kind="astrophysics" title="Astrophysics & Orbital Mechanics" description="Orbital fundamentals, near-earth objects, and astrophysics signals" />} />
103131
<Route path="space/simulations" element={<SpaceModulePage kind="simulations" title="Orbital Simulations" description="Run gravitational and trajectory models" />} />
104132
<Route path="space/telemetry" element={<SpaceModulePage kind="telemetry" title="Satellite Telemetry" description="Monitor real-time satellite data" />} />

frontend/src/components/Lab/DomainSidebar.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const domainConfigs = {
2626
subtitle: 'Astrophysics & Orbital Mechanics',
2727
items: [
2828
{ to: '/lab/space', label: 'Dashboard Overview', icon: FiActivity, end: true },
29+
{ to: '/lab/space/map', label: 'Earth Imagery Map', icon: FiGlobe },
30+
{ to: '/lab/space/mars-map', label: 'Mars Trek Map', icon: FiGlobe },
2931
{ to: '/lab/space/astrophysics', label: 'Astrophysics & Orbital Mechanics', icon: FiGlobe },
3032
{ to: '/lab/space/simulations', label: 'Orbital Simulations', icon: FiCrosshair },
3133
{ to: '/lab/space/telemetry', label: 'Satellite Telemetry', icon: FiTarget },
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { FiArrowLeft } from 'react-icons/fi';
4+
5+
import 'ol/ol.css';
6+
import Map from 'ol/Map';
7+
import View from 'ol/View';
8+
import ImageLayer from 'ol/layer/Image';
9+
import ImageArcGISRest from 'ol/source/ImageArcGISRest';
10+
import { Projection } from 'ol/proj';
11+
import { addProjection } from 'ol/proj';
12+
13+
const MarsTrekMapPage = () => {
14+
const containerRef = useRef(null);
15+
const mapRef = useRef(null);
16+
const [error, setError] = useState(null);
17+
18+
useEffect(() => {
19+
if (!containerRef.current) return;
20+
if (mapRef.current) return;
21+
22+
try {
23+
// Mars Trek basemap (Viking MDIM21 color mosaic)
24+
// ArcGIS ImageServer reports wkid 104905 with bounds [-180..180, -90..90].
25+
const marsProj = new Projection({
26+
code: 'EPSG:104905',
27+
units: 'degrees',
28+
extent: [-180, -90, 180, 90],
29+
global: true,
30+
});
31+
addProjection(marsProj);
32+
33+
const basemapSource = new ImageArcGISRest({
34+
url: 'https://trek.nasa.gov/mars/trekarcgis/rest/services/Mars_Viking_MDIM21_ClrMosaic_global_232m/ImageServer',
35+
ratio: 1,
36+
params: {
37+
format: 'png32',
38+
transparent: false,
39+
},
40+
});
41+
42+
const basemapLayer = new ImageLayer({
43+
source: basemapSource,
44+
});
45+
46+
const map = new Map({
47+
target: containerRef.current,
48+
layers: [basemapLayer],
49+
view: new View({
50+
projection: marsProj,
51+
center: [0, 0],
52+
zoom: 1,
53+
extent: [-180, -90, 180, 90],
54+
constrainOnlyCenter: true,
55+
}),
56+
controls: [],
57+
});
58+
59+
mapRef.current = map;
60+
61+
basemapSource.on('imageloaderror', () => {
62+
setError('Failed to load Mars Trek imagery (CORS/network).');
63+
});
64+
} catch (e) {
65+
setError(e?.message || 'Failed to initialize Mars Trek map');
66+
}
67+
68+
return () => {
69+
if (mapRef.current) {
70+
mapRef.current.setTarget(null);
71+
mapRef.current = null;
72+
}
73+
};
74+
}, []);
75+
76+
return (
77+
<div className="space-y-6">
78+
<div className="flex items-start justify-between gap-4">
79+
<div>
80+
<h1 className="text-3xl font-bold text-white font-['Poppins']">Mars Trek Map</h1>
81+
<p className="text-gray-400 mt-2">
82+
Mars basemap (Viking MDIM21) rendered via OpenLayers.
83+
</p>
84+
</div>
85+
<div className="flex items-center gap-3">
86+
<Link
87+
to="/lab/space"
88+
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white font-semibold hover:bg-white/10"
89+
>
90+
<FiArrowLeft className="inline mr-2" />
91+
Space Lab
92+
</Link>
93+
<Link
94+
to="/lab/space/map"
95+
className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white font-semibold hover:bg-white/10"
96+
>
97+
Earth Map
98+
</Link>
99+
</div>
100+
</div>
101+
102+
{error && (
103+
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 text-sm text-red-200">
104+
{error}
105+
</div>
106+
)}
107+
108+
<div className="bg-white/5 backdrop-blur-md border border-white/10 rounded-xl p-4 shadow-xl">
109+
<div ref={containerRef} className="w-full h-[520px] rounded-lg overflow-hidden" />
110+
</div>
111+
112+
<div className="text-xs text-gray-400">
113+
This page uses Trek’s ArcGIS ImageServer (planetary CRS). That’s why it’s separate from the Mapbox Earth map.
114+
</div>
115+
</div>
116+
);
117+
};
118+
119+
export default MarsTrekMapPage;
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import React, { useEffect, useMemo, useState } from 'react';
2+
import { Link, useParams } from 'react-router-dom';
3+
import { FiArrowLeft, FiRefreshCw } from 'react-icons/fi';
4+
5+
import { spaceService } from '../../services';
6+
7+
const truncateJson = (value, limit = 4000) => {
8+
try {
9+
const s = JSON.stringify(value, null, 2);
10+
if (s.length <= limit) return s;
11+
return s.slice(0, limit - 1) + '…';
12+
} catch {
13+
const s = String(value ?? '');
14+
if (s.length <= limit) return s;
15+
return s.slice(0, limit - 1) + '…';
16+
}
17+
};
18+
19+
const SpaceApiPage = () => {
20+
const { apiId } = useParams();
21+
22+
const config = useMemo(() => {
23+
const defs = {
24+
apod: {
25+
title: 'APOD: Astronomy Picture of the Day',
26+
description: 'Daily astronomy image and metadata.',
27+
run: () => spaceService.getApod(),
28+
},
29+
neows: {
30+
title: 'Asteroids NeoWs',
31+
description: 'Near Earth Object Web Service summary (feed-based).',
32+
run: () => spaceService.getNeoSummary(),
33+
},
34+
donki: {
35+
title: 'DONKI',
36+
description: 'Space weather notifications summary.',
37+
run: () => spaceService.getDonkiSummary(),
38+
},
39+
eonet: {
40+
title: 'EONET',
41+
description: 'Earth Observatory Natural Event Tracker events.',
42+
run: () => spaceService.getEonetEvents({ status: 'open', limit: 10 }),
43+
},
44+
epic: {
45+
title: 'EPIC',
46+
description: 'Earth Polychromatic Imaging Camera (natural images).',
47+
run: () => spaceService.getEpicLatest(),
48+
},
49+
exoplanet: {
50+
title: 'Exoplanet Archive',
51+
description: 'Sample TAP query results from NASA Exoplanet Archive.',
52+
run: () => spaceService.getExoplanetSample(),
53+
},
54+
images: {
55+
title: 'NASA Image and Video Library',
56+
description: 'Search results from images.nasa.gov API.',
57+
run: () => spaceService.searchNasaImages({ q: 'nebula', media_type: 'image' }),
58+
},
59+
techport: {
60+
title: 'Techport',
61+
description: 'NASA technology projects (summary).',
62+
run: () => spaceService.getTechportProjects(),
63+
},
64+
techtransfer: {
65+
title: 'TechTransfer',
66+
description: 'Patents/software tech transfer search preview.',
67+
run: () => spaceService.searchTechtransferPatents({ q: 'robot' }),
68+
},
69+
ssd_cneos: {
70+
title: 'SSD/CNEOS',
71+
description: 'JPL close-approach data (CAD) summary.',
72+
run: () => spaceService.getSsdCneosCad({ 'dist_max': '0.05', limit: 10 }),
73+
},
74+
tle: {
75+
title: 'TLE API',
76+
description: 'Two-line element set (defaults to ISS / NORAD 25544).',
77+
run: () => spaceService.getTle({ catnr: 25544 }),
78+
},
79+
gibs: {
80+
title: 'GIBS',
81+
description: 'Global Imagery Browse Services (WMTS info).',
82+
run: () => spaceService.getGibsInfo(),
83+
},
84+
trek_wmts: {
85+
title: 'Vesta/Moon/Mars Trek WMTS',
86+
description: 'WMTS integration info for Trek imagery tiles.',
87+
run: () => spaceService.getTrekWmtsInfo(),
88+
},
89+
insight: {
90+
title: 'InSight: Mars Weather',
91+
description: 'Status/info for InSight weather API integration.',
92+
run: () => spaceService.getInsightInfo(),
93+
},
94+
osdr: {
95+
title: 'Open Science Data Repository',
96+
description: 'Status/info for OSDR integration.',
97+
run: () => spaceService.getOsdrInfo(),
98+
},
99+
ssc: {
100+
title: 'Satellite Situation Center',
101+
description: 'Status/info for SSC integration.',
102+
run: () => spaceService.getSscInfo(),
103+
},
104+
};
105+
106+
return defs[apiId] || null;
107+
}, [apiId]);
108+
109+
const [state, setState] = useState({ loading: true, error: null, data: null });
110+
111+
const load = async () => {
112+
if (!config) return;
113+
setState({ loading: true, error: null, data: null });
114+
try {
115+
const res = await config.run();
116+
setState({ loading: false, error: null, data: res?.data });
117+
} catch (e) {
118+
const msg = e?.response?.data?.detail || e?.response?.data?.error || e?.message || 'Request failed';
119+
setState({ loading: false, error: msg, data: null });
120+
}
121+
};
122+
123+
useEffect(() => {
124+
load();
125+
// eslint-disable-next-line react-hooks/exhaustive-deps
126+
}, [apiId]);
127+
128+
if (!config) {
129+
return (
130+
<div className="space-y-4">
131+
<div>
132+
<h1 className="text-3xl font-bold text-white font-['Poppins']">Unknown API</h1>
133+
<p className="text-gray-400 mt-2">This Space Lab integration key is not configured.</p>
134+
</div>
135+
<Link to="/lab/space" className="inline-flex items-center gap-2 text-[#00E0FF] font-semibold">
136+
<FiArrowLeft />
137+
Back to Space Lab
138+
</Link>
139+
</div>
140+
);
141+
}
142+
143+
return (
144+
<div className="space-y-6">
145+
<div className="flex items-start justify-between gap-4">
146+
<div>
147+
<h1 className="text-3xl font-bold text-white font-['Poppins']">{config.title}</h1>
148+
<p className="text-gray-400 mt-2">{config.description}</p>
149+
</div>
150+
<div className="flex items-center gap-3">
151+
<Link to="/lab/space" className="px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white font-semibold hover:bg-white/10">
152+
<FiArrowLeft className="inline mr-2" />
153+
Space Lab
154+
</Link>
155+
<button
156+
type="button"
157+
onClick={load}
158+
className="px-4 py-2 bg-gradient-to-r from-[#1A4FFF] to-[#00E0FF] text-white rounded-lg font-semibold hover:shadow-lg hover:shadow-[#1A4FFF]/50"
159+
>
160+
<FiRefreshCw className="inline mr-2" />
161+
Refresh
162+
</button>
163+
</div>
164+
</div>
165+
166+
{state.error && (
167+
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-4 text-sm text-red-200">
168+
{state.error}
169+
</div>
170+
)}
171+
172+
<div className="bg-white/5 backdrop-blur-md border border-white/10 rounded-xl p-6 shadow-xl">
173+
<div className="text-sm text-gray-400 font-semibold mb-3">Response preview</div>
174+
<pre className="text-xs text-gray-200 overflow-auto whitespace-pre-wrap">
175+
{state.loading ? 'Loading…' : truncateJson(state.data)}
176+
</pre>
177+
</div>
178+
179+
{apiId === 'apod' && state.data?.media_type === 'image' && state.data?.url && (
180+
<div className="bg-white/5 backdrop-blur-md border border-white/10 rounded-xl p-6 shadow-xl">
181+
<div className="text-lg font-bold text-white mb-3 font-['Poppins']">APOD Image</div>
182+
<a href={state.data.hdurl || state.data.url} target="_blank" rel="noreferrer" className="block rounded-lg overflow-hidden border border-white/10">
183+
<img src={state.data.url} alt={state.data.title || 'APOD'} className="w-full h-72 object-cover" loading="lazy" />
184+
</a>
185+
</div>
186+
)}
187+
</div>
188+
);
189+
};
190+
191+
export default SpaceApiPage;

0 commit comments

Comments
 (0)