Loading src/components/container/App.jsx +142 −2 Original line number Diff line number Diff line import React from "react"; import React, { useEffect, useState } from "react"; import UsgsHeader from "../presentational/UsgsHeader.jsx"; import UsgsFooter from "../presentational/UsgsFooter.jsx"; import GeoStacApp from "./GeoStacApp.jsx"; import SplashScreen from "../presentational/SplashScreen.jsx"; /** * App is the parent component for all of the other components in the project. * It loads the data needed to initialize GeoStac. * It includes the main GeoStacApp and OCAP compliant headers and footers. * * @component */ export default function App() { const [mainComponent, setMainComponent] = useState(() => { return( <SplashScreen /> ); }) useEffect(() => { // Astro Web Maps, has the tile data const astroWebMaps = "https://astrowebmaps.wr.usgs.gov/webmapatlas/Layers/maps.json"; // STAC API, has footprint data const stacApiCollections = "https://stac.astrogeology.usgs.gov/api/collections"; // Async tracking let fetchStatus = {} let fetchPromise = {} let jsonPromise = {} // Fetched Maps var mapsJson = {} // Combined Data var aggregateMapList = {} // Init fetchStatus[astroWebMaps] = "Not Started"; fetchPromise[astroWebMaps] = "Not Started"; jsonPromise[astroWebMaps] = "Not Started"; mapsJson[astroWebMaps] = []; fetchStatus[stacApiCollections] = "Not Started"; fetchPromise[stacApiCollections] = "Not Started"; jsonPromise[stacApiCollections] = "Not Started"; mapsJson[stacApiCollections] = []; // Fetch JSON and read into object async function ensureFetched(targetUrl) { if(fetchStatus[targetUrl] === "Not Started") { fetchStatus[targetUrl] = "Started" fetchPromise[targetUrl] = fetch( targetUrl ).then((res)=>{ jsonPromise[targetUrl] = res.json().then((jsonData)=>{ mapsJson[targetUrl] = jsonData; }).catch((err)=>{ console.log(err) }); }).catch((err) => { console.log(err); }); } await fetchPromise[targetUrl]; await jsonPromise[targetUrl]; } // Combine data from Astro Web Maps and STAC API into one new object function organizeData(astroWebMaps, stacApiCollections) { // Initialize Objects let mapList = { "systems" : [] }; let stacList = []; // Check for Planets that have STAC footprints from the STAC API for (let i = 0; i < stacApiCollections.collections.length; i++) { if (stacApiCollections.collections[i].hasOwnProperty("summaries")){ let stacTarget = stacApiCollections.collections[i].summaries["ssys:targets"][0].toLowerCase(); if(!stacList.find(targetBody => targetBody == stacTarget)){ stacList.push(stacTarget.toLowerCase()); } } } for (const target of astroWebMaps.targets){ // Check for/add system if (!mapList.systems.some(system => system.name === target.system)) { mapList.systems.push({ "name" : target.system, "bodies" : [] }) } // Index of System let sysIndex = mapList.systems.map(sys => sys.name).indexOf(target.system); // Check for/add body if (!mapList.systems[sysIndex].bodies.some(body => body.name === target.name)) { // A flag that indicates whether or not the body has footprints let hasFootprints = stacList.includes(target.name.toLowerCase()) mapList.systems[sysIndex].bodies.push({ "name" : target.name, "has-footprints" : hasFootprints, "maps" : [] }) } // Index of Body let bodIndex = mapList.systems[sysIndex].bodies.map(bod => bod.name).indexOf(target.name); // Add maps for (const wmap of target.webmap) { mapList.systems[sysIndex].bodies[bodIndex].maps.push(wmap.displayname); // More properties? } } return mapList; } // Fetch and organize data from async function getStacAndAstroWebMapsData() { // Start fetching from AWM and STAC API concurrently ensureFetched(astroWebMaps); ensureFetched(stacApiCollections); // Wait for both to complete before moving on await ensureFetched(astroWebMaps); await ensureFetched(stacApiCollections); return organizeData(mapsJson[astroWebMaps], mapsJson[stacApiCollections]); } (async () => { aggregateMapList = await getStacAndAstroWebMapsData(); setMainComponent(<GeoStacApp mapList={aggregateMapList}/>); })(); }, []) return ( <> <UsgsHeader /> <GeoStacApp /> {mainComponent} <UsgsFooter /> </> ); Loading src/components/container/GeoStacApp.jsx +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ let css = { * * @component */ export default function GeoStacApp() { export default function GeoStacApp(props) { const [targetPlanet, setTargetPlanet] = React.useState("Mars"); const [footprintData, setFootprintData] = React.useState([]); Loading Loading @@ -64,6 +64,7 @@ export default function GeoStacApp() { <div className="flex col"> <ConsoleAppBar target={targetPlanet} mapList={props.mapList} bodyChange={handleTargetBodyChange} /> <div id="map-area"> Loading src/components/presentational/ConsoleAppBar.jsx +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ export default function ConsoleAppBar(props) { <div className="flexbar-item"> <ConsoleTargetInfo target={props.target} mapList={props.mapList} bodyChange={props.bodyChange} /> </div> Loading src/components/presentational/ConsoleTargetInfo.jsx +59 −26 Original line number Diff line number Diff line Loading @@ -9,15 +9,19 @@ import DialogTitle from "@mui/material/DialogTitle"; import Avatar from "@mui/material/Avatar"; import List from "@mui/material/List"; import ListSubheader from "@mui/material/ListSubheader"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemAvatar from "@mui/material/ListItemAvatar"; import ListItemText from "@mui/material/ListItemText"; import Collapse from '@mui/material/Collapse'; import { blue } from "@mui/material/colors"; // Icons import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import ScatterPlotIcon from '@mui/icons-material/ScatterPlot'; import PublicIcon from "@mui/icons-material/Public"; import DarkModeIcon from "@mui/icons-material/DarkMode"; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; /** * Controls css styling for this component using js to css Loading @@ -42,6 +46,11 @@ let css = { }, }; // Delete if new data loading works // Unless we add images here // Why is Puck not on this list? const planets = [ ["Mercury"], ["Venus"], Loading Loading @@ -81,6 +90,19 @@ const moons = [ * @returns Planet Selection Dialog */ function PlanetDialog(props) { const [openSys, setOpenSys] = React.useState(Array(props.mapList.systems.length).fill(false)); function handleSysOpen(index){ const nextOpenSys = openSys.map((isOpen, curIndex) => { if (index === curIndex) { return !isOpen; } return false; }); setOpenSys(nextOpenSys); } const { onClose, selectedValue, open } = props; const handleClose = () => { Loading @@ -91,35 +113,45 @@ function PlanetDialog(props) { onClose(value); }; console.log(props.mapList) return ( <Dialog onClose={handleClose} open={open}> <DialogTitle>Select Target Body</DialogTitle> <List sx={{ pt: 0 }}> <ListSubheader value="Mars">Planets</ListSubheader> {planets.map((planet) => ( <ListItem button onClick={() => handleListItemClick(planet[0])} key={planet[0]} <ListSubheader value="None">Systems</ListSubheader> {props.mapList.systems.map((system, sysIndex) => ( <> <ListItemButton onClick={() => handleSysOpen(sysIndex)} key={system.name} > <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <PublicIcon /> <ScatterPlotIcon /> </Avatar> </ListItemAvatar> <ListItemText primary={planet[0]} /> </ListItem> ))} <ListSubheader value="Moon">Moons and Other Bodies</ListSubheader> {moons.map((moon) => ( <ListItem button onClick={() => handleListItemClick(moon)} key={moon}> <ListItemText primary={system.name} /> </ListItemButton> <Collapse in={openSys[sysIndex]} timeout="auto" unmountOnExit> <List component="div" disablePadding> {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( <ListItemButton sx={{ pl: 4 }} onClick={() => handleListItemClick(body.name)} key={body.name} > <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <DarkModeIcon /> {body.name === system.name ? <PublicIcon /> : <DarkModeIcon/>} </Avatar> </ListItemAvatar> <ListItemText primary={moon} /> </ListItem> <ListItemText primary={body.name} /> </ListItemButton> ))} </List> </Collapse> </> ))} </List> </Dialog> Loading Loading @@ -177,6 +209,7 @@ export default function ConsoleTargetInfo(props) { </Typography> </Grid> <PlanetDialog mapList={props.mapList} selectedValue={selectedValue} open={open} onClose={handleClose} Loading src/components/presentational/SplashScreen.jsx 0 → 100644 +26 −0 Original line number Diff line number Diff line import React from "react"; import SvgIcon from "@mui/material/SvgIcon"; import loadingImage from "../../images/logos/geostac-logo.svg"; export default function SplashScreen() { return( <div className="flex col scroll-parent"> <div className="white loading-slate"> <h1>GeoSTAC</h1> <p>Fetching map data...</p> <SvgIcon viewBox="0 0 375 375" style={{ color: "#343a40", top: 3, width: 200, height: 150, position: "relative", paddingBottom: 20 }} component={loadingImage} /> </div> </div> ) } No newline at end of file Loading
src/components/container/App.jsx +142 −2 Original line number Diff line number Diff line import React from "react"; import React, { useEffect, useState } from "react"; import UsgsHeader from "../presentational/UsgsHeader.jsx"; import UsgsFooter from "../presentational/UsgsFooter.jsx"; import GeoStacApp from "./GeoStacApp.jsx"; import SplashScreen from "../presentational/SplashScreen.jsx"; /** * App is the parent component for all of the other components in the project. * It loads the data needed to initialize GeoStac. * It includes the main GeoStacApp and OCAP compliant headers and footers. * * @component */ export default function App() { const [mainComponent, setMainComponent] = useState(() => { return( <SplashScreen /> ); }) useEffect(() => { // Astro Web Maps, has the tile data const astroWebMaps = "https://astrowebmaps.wr.usgs.gov/webmapatlas/Layers/maps.json"; // STAC API, has footprint data const stacApiCollections = "https://stac.astrogeology.usgs.gov/api/collections"; // Async tracking let fetchStatus = {} let fetchPromise = {} let jsonPromise = {} // Fetched Maps var mapsJson = {} // Combined Data var aggregateMapList = {} // Init fetchStatus[astroWebMaps] = "Not Started"; fetchPromise[astroWebMaps] = "Not Started"; jsonPromise[astroWebMaps] = "Not Started"; mapsJson[astroWebMaps] = []; fetchStatus[stacApiCollections] = "Not Started"; fetchPromise[stacApiCollections] = "Not Started"; jsonPromise[stacApiCollections] = "Not Started"; mapsJson[stacApiCollections] = []; // Fetch JSON and read into object async function ensureFetched(targetUrl) { if(fetchStatus[targetUrl] === "Not Started") { fetchStatus[targetUrl] = "Started" fetchPromise[targetUrl] = fetch( targetUrl ).then((res)=>{ jsonPromise[targetUrl] = res.json().then((jsonData)=>{ mapsJson[targetUrl] = jsonData; }).catch((err)=>{ console.log(err) }); }).catch((err) => { console.log(err); }); } await fetchPromise[targetUrl]; await jsonPromise[targetUrl]; } // Combine data from Astro Web Maps and STAC API into one new object function organizeData(astroWebMaps, stacApiCollections) { // Initialize Objects let mapList = { "systems" : [] }; let stacList = []; // Check for Planets that have STAC footprints from the STAC API for (let i = 0; i < stacApiCollections.collections.length; i++) { if (stacApiCollections.collections[i].hasOwnProperty("summaries")){ let stacTarget = stacApiCollections.collections[i].summaries["ssys:targets"][0].toLowerCase(); if(!stacList.find(targetBody => targetBody == stacTarget)){ stacList.push(stacTarget.toLowerCase()); } } } for (const target of astroWebMaps.targets){ // Check for/add system if (!mapList.systems.some(system => system.name === target.system)) { mapList.systems.push({ "name" : target.system, "bodies" : [] }) } // Index of System let sysIndex = mapList.systems.map(sys => sys.name).indexOf(target.system); // Check for/add body if (!mapList.systems[sysIndex].bodies.some(body => body.name === target.name)) { // A flag that indicates whether or not the body has footprints let hasFootprints = stacList.includes(target.name.toLowerCase()) mapList.systems[sysIndex].bodies.push({ "name" : target.name, "has-footprints" : hasFootprints, "maps" : [] }) } // Index of Body let bodIndex = mapList.systems[sysIndex].bodies.map(bod => bod.name).indexOf(target.name); // Add maps for (const wmap of target.webmap) { mapList.systems[sysIndex].bodies[bodIndex].maps.push(wmap.displayname); // More properties? } } return mapList; } // Fetch and organize data from async function getStacAndAstroWebMapsData() { // Start fetching from AWM and STAC API concurrently ensureFetched(astroWebMaps); ensureFetched(stacApiCollections); // Wait for both to complete before moving on await ensureFetched(astroWebMaps); await ensureFetched(stacApiCollections); return organizeData(mapsJson[astroWebMaps], mapsJson[stacApiCollections]); } (async () => { aggregateMapList = await getStacAndAstroWebMapsData(); setMainComponent(<GeoStacApp mapList={aggregateMapList}/>); })(); }, []) return ( <> <UsgsHeader /> <GeoStacApp /> {mainComponent} <UsgsFooter /> </> ); Loading
src/components/container/GeoStacApp.jsx +2 −1 Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ let css = { * * @component */ export default function GeoStacApp() { export default function GeoStacApp(props) { const [targetPlanet, setTargetPlanet] = React.useState("Mars"); const [footprintData, setFootprintData] = React.useState([]); Loading Loading @@ -64,6 +64,7 @@ export default function GeoStacApp() { <div className="flex col"> <ConsoleAppBar target={targetPlanet} mapList={props.mapList} bodyChange={handleTargetBodyChange} /> <div id="map-area"> Loading
src/components/presentational/ConsoleAppBar.jsx +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ export default function ConsoleAppBar(props) { <div className="flexbar-item"> <ConsoleTargetInfo target={props.target} mapList={props.mapList} bodyChange={props.bodyChange} /> </div> Loading
src/components/presentational/ConsoleTargetInfo.jsx +59 −26 Original line number Diff line number Diff line Loading @@ -9,15 +9,19 @@ import DialogTitle from "@mui/material/DialogTitle"; import Avatar from "@mui/material/Avatar"; import List from "@mui/material/List"; import ListSubheader from "@mui/material/ListSubheader"; import ListItem from "@mui/material/ListItem"; import ListItemButton from "@mui/material/ListItemButton"; import ListItemAvatar from "@mui/material/ListItemAvatar"; import ListItemText from "@mui/material/ListItemText"; import Collapse from '@mui/material/Collapse'; import { blue } from "@mui/material/colors"; // Icons import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import ScatterPlotIcon from '@mui/icons-material/ScatterPlot'; import PublicIcon from "@mui/icons-material/Public"; import DarkModeIcon from "@mui/icons-material/DarkMode"; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; /** * Controls css styling for this component using js to css Loading @@ -42,6 +46,11 @@ let css = { }, }; // Delete if new data loading works // Unless we add images here // Why is Puck not on this list? const planets = [ ["Mercury"], ["Venus"], Loading Loading @@ -81,6 +90,19 @@ const moons = [ * @returns Planet Selection Dialog */ function PlanetDialog(props) { const [openSys, setOpenSys] = React.useState(Array(props.mapList.systems.length).fill(false)); function handleSysOpen(index){ const nextOpenSys = openSys.map((isOpen, curIndex) => { if (index === curIndex) { return !isOpen; } return false; }); setOpenSys(nextOpenSys); } const { onClose, selectedValue, open } = props; const handleClose = () => { Loading @@ -91,35 +113,45 @@ function PlanetDialog(props) { onClose(value); }; console.log(props.mapList) return ( <Dialog onClose={handleClose} open={open}> <DialogTitle>Select Target Body</DialogTitle> <List sx={{ pt: 0 }}> <ListSubheader value="Mars">Planets</ListSubheader> {planets.map((planet) => ( <ListItem button onClick={() => handleListItemClick(planet[0])} key={planet[0]} <ListSubheader value="None">Systems</ListSubheader> {props.mapList.systems.map((system, sysIndex) => ( <> <ListItemButton onClick={() => handleSysOpen(sysIndex)} key={system.name} > <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <PublicIcon /> <ScatterPlotIcon /> </Avatar> </ListItemAvatar> <ListItemText primary={planet[0]} /> </ListItem> ))} <ListSubheader value="Moon">Moons and Other Bodies</ListSubheader> {moons.map((moon) => ( <ListItem button onClick={() => handleListItemClick(moon)} key={moon}> <ListItemText primary={system.name} /> </ListItemButton> <Collapse in={openSys[sysIndex]} timeout="auto" unmountOnExit> <List component="div" disablePadding> {props.mapList.systems[sysIndex].bodies.map((body, bodIndex) => ( <ListItemButton sx={{ pl: 4 }} onClick={() => handleListItemClick(body.name)} key={body.name} > <ListItemAvatar> <Avatar sx={{ bgcolor: blue[100] }}> <DarkModeIcon /> {body.name === system.name ? <PublicIcon /> : <DarkModeIcon/>} </Avatar> </ListItemAvatar> <ListItemText primary={moon} /> </ListItem> <ListItemText primary={body.name} /> </ListItemButton> ))} </List> </Collapse> </> ))} </List> </Dialog> Loading Loading @@ -177,6 +209,7 @@ export default function ConsoleTargetInfo(props) { </Typography> </Grid> <PlanetDialog mapList={props.mapList} selectedValue={selectedValue} open={open} onClose={handleClose} Loading
src/components/presentational/SplashScreen.jsx 0 → 100644 +26 −0 Original line number Diff line number Diff line import React from "react"; import SvgIcon from "@mui/material/SvgIcon"; import loadingImage from "../../images/logos/geostac-logo.svg"; export default function SplashScreen() { return( <div className="flex col scroll-parent"> <div className="white loading-slate"> <h1>GeoSTAC</h1> <p>Fetching map data...</p> <SvgIcon viewBox="0 0 375 375" style={{ color: "#343a40", top: 3, width: 200, height: 150, position: "relative", paddingBottom: 20 }} component={loadingImage} /> </div> </div> ) } No newline at end of file