diff --git a/.gitignore b/.gitignore index 6e69c7034512eb14eeda2a55dfa035945ea0ebbb..cff4498794615013e2e0594d92ba646a02cff48e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ app/node_modules .Trashes ehthumbs.db Thumbs.db + +# IDE generated files +.vscode \ No newline at end of file diff --git a/src/components/presentational/FootprintResults.jsx b/src/components/presentational/FootprintResults.jsx index 0cad69dfc327314ad31d0dbee6630d15bfb66fc4..2aacfc3adcd6d69f9d019c76e0c58d96153666d8 100644 --- a/src/components/presentational/FootprintResults.jsx +++ b/src/components/presentational/FootprintResults.jsx @@ -61,12 +61,8 @@ export default function FootprintResults(props) { setCollectionId(newCollectionId); setMatched(featureCollections[newCollectionId].numberMatched); - // Extract the selected collection title - const selectedCollection = props.target.collections.find(collection => collection.id === newCollectionId); - const selectedCollectionTitle = selectedCollection ? selectedCollection.title : ''; - // Call the callback function to pass the selected title to the Sidebar - props.updateSelectedTitle(selectedCollectionTitle); + props.updateAvailableQueriables(props.target.collections.find(col => col.id === newCollectionId).querTitles); props.UpdateQueryableTitles(null); diff --git a/src/components/presentational/SearchAndFilterInput.jsx b/src/components/presentational/SearchAndFilterInput.jsx index 2a27c72569af4882b53ee7ea583922ca24d793b4..828d2db5ddad4a99cb4758164b0d3854de5cc960 100644 --- a/src/components/presentational/SearchAndFilterInput.jsx +++ b/src/components/presentational/SearchAndFilterInput.jsx @@ -147,9 +147,6 @@ export default function SearchAndFilterInput(props) { props.setFilterString(myFilterString); } - // initialize pyGeoAPI flag - let pyGeoAPIFlag = false; - // New state for queryable titles const [queryableTitles, setQueryableTitles] = useState([]); @@ -160,57 +157,11 @@ export default function SearchAndFilterInput(props) { const isInPyAPI = collection.filter(data => data.hasOwnProperty('itemType')); // finds and assigns the selected collection from the PYGEO api - const selectedCollection = isInPyAPI.find(data => data.title === props.selectedTitle); + const selectedCollection = isInPyAPI.find(data => data.title === props.availableQueriables); // retrieves all pyGEO titles const collectionTitles = isInPyAPI.map(data => data.title); - - - // checks if correct title selected - if (collectionTitles.includes(props.selectedTitle)) - { - //set pyGeoAPI flag - pyGeoAPIFlag = true; - - // set the selected link - let QueryableDirectoryLink = selectedCollection.links.find(link => link.rel === "queryables").href; - - // creates URL to get the properties - let QueryableURL = 'https://astrogeology.usgs.gov/pygeoapi/' + QueryableDirectoryLink; - - // fetches URL to get the properties - fetch(QueryableURL) - .then(response => response.json()) - .then(data => { - - let queryableTitlesArray = []; - - // Extract the "properties" property from the JSON response - let Queryables = data.properties; - - // loop over titles - for (const property in Queryables) { - if (Queryables.hasOwnProperty(property) && Queryables[property].hasOwnProperty("title")) { - - queryableTitlesArray.push(data.properties[property].title); - - } - } - - // Set the state with the queryable titles - setQueryableTitles(queryableTitlesArray); - - - }, []) - .catch(error => { - console.error("Error fetching data:", error); - }); - } - - - - const [selectedOptions, setSelectedOptions] = useState([]); const handleOptionChange = event => { @@ -285,6 +236,11 @@ export default function SearchAndFilterInput(props) { return () => window.removeEventListener("message", onBoxDraw); }, []); + // If Available queriables are changed, reset the ones selected to none + useEffect(() => { + setSelectedOptions([]); + }, [props.availableQueriables]); + // If target is changed, reset filter values; useEffect(() => { @@ -326,7 +282,7 @@ export default function SearchAndFilterInput(props) { <Collapse in={expandFilter}> <div className="panelSection panelBar"> <span> - <FormControl sx={{ minWidth: 150 }}> + <FormControl sx={{ minWidth: 180 }}> <InputLabel id="sortByLabel" size="small"> Sort By </InputLabel> @@ -357,32 +313,35 @@ export default function SearchAndFilterInput(props) { </span> </div> - {pyGeoAPIFlag && ( - <div className="panelSection panelBar"> - <span> - <FormControl sx={{ minWidth: 150 , minHeight: 40}}> - <InputLabel id="selectQueryLabel" size="small" style={{paddingTop: '0.2rem'}}> - Show Properties - </InputLabel> - <Select - labelId="selectQueryLabel" - label="Select Query" - multiple - value={selectedOptions} - onChange={handleOptionChange} - renderValue={(selected) => selected.join(', ')} - style={{height: 43}} - > - {queryableTitles.map((title) => ( - <MenuItem key={title} value={title}> - <Checkbox checked={selectedOptions.includes(title)} /> - <ListItemText primary={title} /> - </MenuItem> - ))} - </Select> - </FormControl> - </span> - </div> + {props.availableQueriables.length > 0 && ( + <> + <Divider/> + <div className="panelSection panelBar"> + <span> + <FormControl sx={{ minWidth: 180 , minHeight: 40}}> + <InputLabel id="showPropertiesLabel" size="small" style={{paddingTop: '0.2rem'}}> + Show Properties + </InputLabel> + <Select + labelId="showPropertiesLabel" + label="Show Properties" + multiple + value={selectedOptions} + onChange={handleOptionChange} + renderValue={(selected) => selected.join(', ')} + style={{height: 43}} + > + {props.availableQueriables.map((title) => ( + <MenuItem key={title} value={title}> + <Checkbox checked={selectedOptions.includes(title)} /> + <ListItemText primary={title} /> + </MenuItem> + ))} + </Select> + </FormControl> + </span> + </div> + </> )} <Divider/> diff --git a/src/components/presentational/Sidebar.jsx b/src/components/presentational/Sidebar.jsx index 6e26f8346724845b3f44642687360e008413e166..ce383edb67ec061f450b95e5435fedd7c4d398d3 100644 --- a/src/components/presentational/Sidebar.jsx +++ b/src/components/presentational/Sidebar.jsx @@ -44,11 +44,11 @@ export default function Sidebar(props) { }; // State to hold the selected title - const [selectedTitle, setSelectedTitle] = React.useState(""); + const [availableQueriables, setAvailableQueriables] = React.useState(""); // Callback function to update selected title - const updateSelectedTitle = (newTitle) => { - setSelectedTitle(newTitle); + const updateAvailableQueriables = (queriables) => { + setAvailableQueriables(queriables); }; // State to hold the seleced queryables @@ -72,7 +72,7 @@ export default function Sidebar(props) { setFilterString={setFilterString} targetName={props.target.name} target={props.target} - selectedTitle={selectedTitle} + availableQueriables={availableQueriables} UpdateQueryableTitles = {UpdateQueryableTitles} /> <FootprintResults @@ -80,7 +80,7 @@ export default function Sidebar(props) { filterString={filterString} queryAddress={props.queryAddress} setQueryAddress={props.setQueryAddress} - updateSelectedTitle={updateSelectedTitle} + updateAvailableQueriables={updateAvailableQueriables} selectedQueryables = {updatedQueryableTitles} UpdateQueryableTitles = {UpdateQueryableTitles} /> diff --git a/src/js/AstroMap.js b/src/js/AstroMap.js index a05cf8b9eb8506f0340b3d82bd8e64a421c5a3c5..ca320aa80a8ec5d037ecef969063d8782d3035e8 100644 --- a/src/js/AstroMap.js +++ b/src/js/AstroMap.js @@ -173,8 +173,12 @@ export default L.Map.AstroMap = L.Map.extend({ for(const feature of myFeatures) { // Check if feature or feature.geometry is null or undefined - if(!feature || !feature.geometry){ - console.warn("Invalid feature or missing geometry: ", feature); + if(!feature){ + console.log("Invalid/Null Feature", feature); + continue; + } + else if(!feature.geometry){ + console.log("Feature with missing geometry", feature) continue; } diff --git a/src/js/FetchData.js b/src/js/FetchData.js index 6224fac81b448e910afa7337daf746829fa17e5a..75527bfae87b33653ce3815b20169639f68704dd 100644 --- a/src/js/FetchData.js +++ b/src/js/FetchData.js @@ -65,7 +65,7 @@ export default async function Initialize(){ } // Combine data from Astro Web Maps and STAC API into one new object - function organizeData(astroWebMaps, stacApiCollections, vectorApiCollections) { + async function organizeData(astroWebMaps, stacApiCollections, vectorApiCollections) { // Initialize Objects let mapList = { "systems" : [] }; @@ -97,6 +97,8 @@ export default async function Initialize(){ let sysIndex = mapList.systems.map(sys => sys.name).indexOf(target.system); // ID the system. This seems to get the main planet of the system. + // https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/naif_ids.html + // "A planet is always considered to be the 99th satellite of its own barycenter" if (target.naif % 100 === 99){ mapList.systems[sysIndex].naif = target.naif; } @@ -113,7 +115,9 @@ export default async function Initialize(){ for (const collection of stacApiCollections.collections){ if (target.name == collection.summaries["ssys:targets"][0].toUpperCase()) { // Add a specification to the title in order to show what kind of data the user is requesting - collection.title = collection.title.concat(" (Raster)") + collection.dataType = "raster"; + collection.querTitles = []; + collection.title = collection.title.concat(" (Raster)"); myCollections.push(collection); } } @@ -122,8 +126,33 @@ export default async function Initialize(){ // view the collection as GEOJSON let target_name = pycollection.id.split('/')[0]; if (target.name == target_name.toUpperCase()) { + + // Set links GeoSTAC needs later pycollection.links.find(link => link.rel === "items").href = "https://astrogeology.usgs.gov/pygeoapi" + pycollection.links.find(link => link.rel === "items").href; + pycollection.itemsLink = "https://astrogeology.usgs.gov/pygeoapi" + pycollection.links.find(link => link.rel === "items").href; + pycollection.queryablesLink = "https://astrogeology.usgs.gov/pygeoapi" + pycollection.links.find(link => link.rel === "queryables").href; + + // Fetch and await queriables + fetchStatus[pycollection.queryablesLink] = "Not Started"; + fetchPromise[pycollection.queryablesLink] = "Not Started"; + jsonPromise[pycollection.queryablesLink] = "Not Started"; + mapsJson[pycollection.queryablesLink] = []; + ensureFetched(pycollection.queryablesLink); + await ensureFetched(pycollection.queryablesLink); + + // put queryable titles in array + let querData = mapsJson[pycollection.queryablesLink]; + let querTitles = []; + let querProps = querData.properties; + for (const property in querProps) { + if (querProps.hasOwnProperty(property) && querProps[property].hasOwnProperty("title")) { + querTitles.push(querData.properties[property].title); + } + } + // Add a specification to the title in order to show what kind of data the user is requesting + pycollection.dataType = "vector"; + pycollection.querTitles = querTitles; pycollection.title = pycollection.title.concat(" (Vector)"); myCollections.push(pycollection); } @@ -228,7 +257,9 @@ export default async function Initialize(){ await ensureFetched(stacApiCollections); await ensureFetched(vectorApiCollections); - return organizeData(mapsJson[astroWebMaps], mapsJson[stacApiCollections], mapsJson[vectorApiCollections]); + let organizedData = await organizeData(mapsJson[astroWebMaps], mapsJson[stacApiCollections], mapsJson[vectorApiCollections]); + + return organizedData; } aggregateMapList = await getStacAndAstroWebMapsData(); diff --git a/src/js/FootprintFetcher.js b/src/js/FootprintFetcher.js index 47acf86452f5d22023c698ef74af91b30a9bb442..728205a2886761f6499da8b0b04dea5e6e425154 100644 --- a/src/js/FootprintFetcher.js +++ b/src/js/FootprintFetcher.js @@ -17,19 +17,21 @@ export async function FetchObjects(objInfo) { // For each url given for(const key in objInfo) { - // Fetch JSON from url and read into object - fetchPromise[key] = fetch( - objInfo[key] - ).then((res)=>{ - jsonPromise[key] = res.json().then((jsonData)=>{ - jsonRes[key] = jsonData; - }).catch((err)=>{ + // The stylesheet ones \/ get 404s so I'm discarding them for now + if (!key.includes(": stylesheet")){ + fetchPromise[key] = fetch( + objInfo[key] + ).then((res) => { + jsonPromise[key] = res.json().then((jsonData) => { + jsonRes[key] = jsonData; + }).catch((err) => { + console.log(err); + }); + }).catch((err) => { console.log(err); }); - }).catch((err) => { - console.log(err); - }); + } } // Wait for each query to complete @@ -50,86 +52,35 @@ export async function FetchObjects(objInfo) { * @param {string} queryString - The query to narrow the results returned from the collection. */ export async function FetchFootprints(collection, page, step){ - let collectionUrl; - let offsetMulitiplier; + const stacDefaultLimit = 10; + const pyDefaultLimit = 25; + let baseURL = collection.url; let pageInfo = ""; + + // get rid of default limit present in some pygeoapi urls + if(baseURL.slice(-9) == "&limit=10") { + baseURL = baseURL.slice(0, -9); + } + if(collection.url.slice(-1) !== "?") { pageInfo += "&" } - pageInfo += "page=" + page; - if (step != 10){ - pageInfo += "&limit=" + step; - } - // check for pyGeo API - if (!collection.url.includes('stac')) + if (collection.url.includes('stac')) { - - // set offset for 5 & 10 steps - offsetMulitiplier = (page * 10 - step); - pageInfo = "&offset=" + offsetMulitiplier; - - - // checks for 5 change in step - if (step <= 10) - { - - // splice limit and change to new limit - collectionUrl = collection.url.split('&limit=')[0]; - collection.url = collectionUrl; - - - // update page pageInfo - pageInfo = "&offset=" + offsetMulitiplier + "&limit=" + step; - - + pageInfo += "page=" + page; + if (step !== stacDefaultLimit) { + pageInfo += "&limit=" + step; } - // checks for 50 & 100 step - else if (step == 50 || step == 100) - { - - // splice limit and change to new limit - collectionUrl = collection.url.split('&limit=')[0]; - collection.url = collectionUrl; - - // check for first page - if (page == 1) - { - // set multiplier to 0 - offsetMulitiplier = 0; - } - // check for second page - else if (page == 2) - { - // set multiplier to step - offsetMulitiplier = step; - - } - else - { - // check for 50 and set pages according - if (step == 50) - { - offsetMulitiplier = page * step - 50; - } - // check for 100 and set pages according - else - { - offsetMulitiplier = page * step - 100; - } - } - - // update page pageInfo - pageInfo = "&offset=" + offsetMulitiplier + "&limit=" + step; + } + else { + pageInfo += "offset=" + step * (page - 1); + if (step !== pyDefaultLimit) { + pageInfo += "&limit=" + step; } - - } - - // reset offset - offsetMulitiplier = 0; - let jsonRes = await FetchObjects(collection.url + pageInfo); + let jsonRes = await FetchObjects(baseURL + pageInfo); return jsonRes.features; }