Unverified Commit 7600e0e8 authored by Jacob Cain's avatar Jacob Cain Committed by GitHub
Browse files

Load footprints per collection (#1)

* put initial fetch in it's own file

* Select collection from results pane

* css clean up for FootprintResults

* Load More instead of Pagination

* load more features without rerendering all

* matched const, collection id bug and selection

* Sorting, load footprints when limit changes

* more fetch logic, removed Query Console URLs

* store collections in obj by ID

* switching planets/collections, load more logic

* fix for collection select id out of range

* include limit parameter in initial request

* load more features go to leaflet

* css and layout, layer wrap and color

* comments

* add footprints instead of replacing

* update query text but disable run from console

* clear filter on target change, no extra requests
parent 54be2867
Loading
Loading
Loading
Loading
+4 −206
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import UsgsFooter from "../presentational/UsgsFooter.jsx";
import Menubar from "../presentational/Menubar.jsx";
import GeoStacApp from "./GeoStacApp.jsx";
import SplashScreen from "../presentational/SplashScreen.jsx";
import Initialize from "../../js/FetchData.js";

/**
 * App is the parent component for all of the other components in the project.
@@ -27,219 +28,16 @@ export default function App() {
  })

  useEffect(() => {

    // Astro Web Maps, has the tile base data for the map of each planetary body
    const astroWebMaps =
        "https://astrowebmaps.wr.usgs.gov/webmapatlas/Layers/maps.json";

    // STAC API, has footprint data for select planetary bodies
    const stacApiCollections = 
        "https://stac.astrogeology.usgs.gov/api/collections";

    // Async tracking
    let fetchStatus = {};
    let fetchPromise = {};
    let jsonPromise = {};

    // Fetched Maps
    let mapsJson = {};

    // Combined Data
    let 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();
        let initialData = await Initialize();
        setMainComponent(
            <GeoStacApp 
                mapList={aggregateMapList}
                astroWebMaps={mapsJson[astroWebMaps]}
                mapList={initialData.aggregateMapList}
                astroWebMaps={initialData.astroWebMaps}
                showHeaderFooter={showHeaderFooter}
                setShowHeaderFooter={setShowHeaderFooter}
            />);
    })();

  }, [])

  return (
+7 −8
Original line number Diff line number Diff line
@@ -15,8 +15,9 @@ import Sidebar from "../presentational/Sidebar.jsx";
export default function GeoStacApp(props) {
  const [targetPlanet, setTargetPlanet] = React.useState(props.mapList.systems[4].bodies[0]);

  const [queryString, setQueryString] = React.useState("?");
  const [collectionUrls, setCollectionUrls] = React.useState([]);
  const [queryAddress, setQueryAddress] = React.useState(
    props.mapList.systems[4].bodies[0].collections[0].links.find(link => link.rel === "items").href + "?"
  );

  /**
   * Handles target body selection
@@ -39,14 +40,12 @@ export default function GeoStacApp(props) {
            <MapContainer target={targetPlanet.name} astroWebMaps={props.astroWebMaps}/>
          </div>
          <QueryConsole
            queryString={queryString}
            setQueryString={setQueryString}
            collectionUrls={collectionUrls}/>
            queryAddress={queryAddress}
            setQueryAddress={setQueryAddress}/>
        </div>
        <Sidebar
          queryString={queryString}
          setQueryString={setQueryString}
          setCollectionUrls={setCollectionUrls}
          queryAddress={queryAddress}
          setQueryAddress={setQueryAddress}
          target={targetPlanet}
        />
      </div>
+16 −18
Original line number Diff line number Diff line
@@ -14,24 +14,15 @@ import AstroControlManager from "../../js/AstroControlManager";
export default function MapContainer(props) {
  
  const [oldTarget, setOldTarget] = React.useState("");

  /**
   * Invoked when the component is successfully mounted to the DOM, then
   * handles all of the map intialization and creation.
   */
  useEffect( () => {
    let map = new AstroMap("map-container", props.target, props.astroWebMaps, {});
    let controlManager = new AstroControlManager(map);
    controlManager.addTo(map);
    setOldTarget(props.target)
  }, []);
  const [map, setMap] = React.useState("");

  /**
   * Invoked after the component's state has changed when the
   * target selector passes down a new target name from props.
   */
  useEffect( () => {
    if (props.target != oldTarget ) {
    
    if(oldTarget !== ""){
      // remove old map container and append new container to its parent
      let oldContainer = document.getElementById("map-container");
      let parent = oldContainer.parentNode;
@@ -47,13 +38,20 @@ export default function MapContainer(props) {
        .classList.remove("disabled");
      document.getElementById("projectionSouthPole").classList.remove("disabled");

      // create new map with updated target
      let map = new AstroMap("map-container", props.target, props.astroWebMaps, {});
      let controlManager = new AstroControlManager(map);
      controlManager.addTo(map);
      setOldTarget(props.target)
      // remove the old message listener so footprint messages aren't received multiple times.
      map.removeListener();
    }
  });

    // create new map with updated target
    let myMap = new AstroMap("map-container", props.target, props.astroWebMaps, {});
    let controlManager = new AstroControlManager(myMap);
    controlManager.addTo(myMap);

    // Set into states for future reference
    setOldTarget(props.target);
    setMap(myMap);
    
  }, [props.target]);

  return (
    <div id="map-container" />
+0 −70
Original line number Diff line number Diff line
import React from "react";
import Typography from "@mui/material/Typography";
import Link from "@mui/material/Link";
import Divider from "@mui/material/Divider";
import GitHubIcon from "@mui/icons-material/GitHub";
import SvgIcon from "@mui/material/SvgIcon";
import GeoSTACIcon from "../../images/logos/geostac-logo.svg";

export default function CreditsDisplay() {
  return (
    <div id="credits-bar">
      <Divider orientation="vertical" />
      <div className="credit-item">
        <Link
          target="_blank"
          rel="noopener"
          color="inherit"
          style={{ fontWeight: 600 }}
          variant="caption"
          href="https://www.ceias.nau.edu/capstone/projects/CS/2022/GeoSTAC/documents/usermanual.pdf"
        >
          User Manual
        </Link>
      </div>
      <Divider orientation="vertical" />
      <div className="credit-item">
        <Typography style={{ fontSize: 12 }} variant="caption">
          <Link
            target="_blank"
            rel="noopener"
            variant="caption"
            color="inherit"
            style={{ fontWeight: 600 }}
            href="https://www.ceias.nau.edu/capstone/projects/CS/2022/GeoSTAC/"
          >
            GeoSTAC Project Website
          </Link>
        </Typography>
        <SvgIcon
          viewBox="0 0 375 375"
          style={{
            color: "#343a40",
            top: 3,
            width: 20,
            height: 13,
            position: "relative",
          }}
          component={GeoSTACIcon}
        />
      </div>
      <Divider orientation="vertical" />
      <div className="credit-item">
        <Link
          target="_blank"
          rel="noopener"
          href="https://github.com/GeoSTAC/CartoCosmos-with-STAC"
        >
          <GitHubIcon
            style={{
              color: "#343a40",
              fontSize: 16,
              top: 2,
              position: "relative",
            }}
          />
        </Link>
      </div>
    </div>
  );
}
+196 −323

File changed.

Preview size limit exceeded, changes collapsed.

Loading