Commit bc4769ab authored by Cain, Jacob Ryan's avatar Cain, Jacob Ryan
Browse files

Merge branch 'load-in-react' into 'main'

Data Fetching/Splash, auto-populate planet menu

See merge request asc/geostac!9
parents 4753b331 d9530b0f
Loading
Loading
Loading
Loading
+222 −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++) {
            let stacTarget = stacApiCollections.collections[i].summaries["ssys:targets"][0].toLowerCase();
            if(!stacList.find(targetBody => targetBody == stacTarget)){
                stacList.push(stacTarget.toLowerCase());
            }
        }

        // Scan through every target in the Astro Web Maps JSON
        for (const target of astroWebMaps.targets){

            // Check for, add system if system is not in array
            if (!mapList.systems.some(system => system.name === target.system)) {
                mapList.systems.push({
                    "name" : target.system,
                    "naif" : 0,
                    "bodies" : []
                })
            }
            
            // Index of System
            let sysIndex = mapList.systems.map(sys => sys.name).indexOf(target.system);

            // ID the system
            if (target.naif % 100 === 99){
                mapList.systems[sysIndex].naif = target.naif;
            }

            // Check for/add body if not already incl
            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())

                // Add STAC collections
                let myCollections = []
                if (hasFootprints) {
                    for (const collection of stacApiCollections.collections){
                        if (target.name == collection.summaries["ssys:targets"][0].toUpperCase()) {
                            myCollections.push(collection);
                        }
                    }
                }

                // Add a body data entry
                mapList.systems[sysIndex].bodies.push({
                    "name" : target.name,
                    "naif" : target.naif,
                    "hasFootprints" : hasFootprints,
                    "layers" : {
                        "base" : [],
                        "overlays" : [],
                        "nomenclature" : []
                    },
                    "collections" : myCollections
                })
            }

            // Index of Body
            let bodIndex = mapList.systems[sysIndex].bodies.map(bod => bod.name).indexOf(target.name);

            // Sort through AstroWebMaps to get the right ones for GeoSTAC
            function getWmsMaps(webMaps) {
                let myLayers = {
                    "base" : [],
                    "overlays" : [],
                    "nomenclature" : [],
                    /* "wfs" : [] */
                };

                // Add maps
                for (const wmap of webMaps) {
                    if(wmap.type === "WMS" && wmap.layer != "GENERIC") {
                        if(wmap.transparent == "false") {
                            // Non-transparent layers are base maps
                            myLayers.base.push(wmap);
                        } else if (wmap.layer == "NOMENCLATURE") {
                            // Feature Name Layers
                            myLayers.nomenclature.push(wmap);
                        } else {
                            // OthTransparent layers are overlays
                            myLayers.overlays.push(wmap);
                        }
                    }
                    // else if (wmap.type === "WFS") {
                    //     // Currently in AstroMap but doesn't seem to be used.
                    //     myLayers.wfs.push(wmap);
                    // }
                }
                return myLayers;
            }

            // Add base and overlay maps (but not empty arrays!)
            let myLayers = getWmsMaps(target.webmap);
            if (myLayers.base.length > 0){
                mapList.systems[sysIndex].bodies[bodIndex].layers.base.push(...myLayers.base);
            }
            if (myLayers.nomenclature.length > 0){
                mapList.systems[sysIndex].bodies[bodIndex].layers.nomenclature.push(...myLayers.nomenclature);
            }
            if (myLayers.overlays.length > 0){
                mapList.systems[sysIndex].bodies[bodIndex].layers.overlays.push(...myLayers.overlays);
            }
        }

        // Sort systems by NAIF ID
        mapList.systems.sort((a, b)=>{return a.naif - b.naif})

        // Go through each System
        for (let sysIndex = 0; sysIndex < mapList.systems.length; sysIndex++){

            // Remove bodies with no base maps
            for (let bodIndex = mapList.systems[sysIndex].bodies.length - 1; bodIndex >= 0; bodIndex--){
                if(mapList.systems[sysIndex].bodies[bodIndex].layers.base.length < 1){
                    mapList.systems[sysIndex].bodies.splice(bodIndex, 1);
                }
            }
            // Sort targets by naif id
            mapList.systems[sysIndex].bodies.sort((a, b)=>{
                let valA = a.naif;
                let valB = b.naif;
                if (a.naif % 100 == 99) valA = 0; // Planet IDs end with 99,
                if (b.naif % 100 == 99) valB = 0; // but put them first.
                return valA - valB;
            })
        }

        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 />
    </>
  );
+2 −1
Original line number Diff line number Diff line
@@ -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([]);
@@ -64,6 +64,7 @@ export default function GeoStacApp() {
        <div className="flex col">
          <ConsoleAppBar
            target={targetPlanet}
            mapList={props.mapList}
            bodyChange={handleTargetBodyChange}
          />
          <div id="map-area">
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ export default function ConsoleAppBar(props) {
      <div className="flexbar-item">
        <ConsoleTargetInfo
          target={props.target}
          mapList={props.mapList}
          bodyChange={props.bodyChange}
        />
      </div>
+73 −30
Original line number Diff line number Diff line
@@ -9,15 +9,26 @@ 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 PublicIcon from "@mui/icons-material/Public";
import DarkModeIcon from "@mui/icons-material/DarkMode";
import ScatterPlotIcon from '@mui/icons-material/ScatterPlot'; // Systems
import PublicIcon from "@mui/icons-material/Public"; // Planets
import DarkModeIcon from "@mui/icons-material/DarkMode"; // Moons
import CookieIcon from '@mui/icons-material/Cookie'; // Asteroids
import TravelExploreIcon from '@mui/icons-material/TravelExplore'; // Footprints.
// import PetsIcon from '@mui/icons-material/Pets';                 // Other
// import SatelliteAltIcon from '@mui/icons-material/SatelliteAlt'; // possible
// import ViewTimelineIcon from '@mui/icons-material/ViewTimeline'; // footprint
// import WhereToVoteIcon from '@mui/icons-material/WhereToVote';   // icons.
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import { textTransform } from "@mui/system";

/**
 * Controls css styling for this component using js to css
@@ -42,6 +53,11 @@ let css = {
  },
};


// Delete if new data loading works
// Unless we add images here
// Why is Puck/Titania not on this list?

const planets = [
  ["Mercury"],
  ["Venus"],
@@ -81,6 +97,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 = () => {
@@ -91,35 +120,48 @@ function PlanetDialog(props) {
    onClose(value);
  };

  console.log(props.mapList)

  return (
    <Dialog onClose={handleClose} open={open}>
      <DialogTitle>Select Target Body</DialogTitle>
    <Dialog PaperProps={{sx: {overflowY: "scroll"}}} onClose={handleClose} open={open}>
      <DialogTitle sx={{ minWidth: 225 }}>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 sx={{ textTransform: "capitalize"}} primary={system.name.toLowerCase()} />
              {props.mapList.systems[sysIndex].bodies.map(bod => bod.hasFootprints).includes(true) ? <TravelExploreIcon/> : null}
              {openSys[sysIndex] ? <ExpandLess /> : <ExpandMore />}
            </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 />
                        {system.name === "ASTEROIDS" ? <CookieIcon/> : body.name === system.name ? <PublicIcon /> : <DarkModeIcon/>}
                      </Avatar>
                    </ListItemAvatar>
            <ListItemText primary={moon} />
          </ListItem>
                    <ListItemText sx={{textTransform: "capitalize"}} primary={body.name.toLowerCase()} secondary={"Maps: " + body.layers.base.length} />
                    {body.hasFootprints ? <TravelExploreIcon/> : null}
                  </ListItemButton>
                ))}
              </List>
            </Collapse>
          </>
        ))}
      </List>
    </Dialog>
@@ -177,6 +219,7 @@ export default function ConsoleTargetInfo(props) {
        </Typography>
      </Grid>
      <PlanetDialog
        mapList={props.mapList}
        selectedValue={selectedValue}
        open={open}
        onClose={handleClose}
+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