Aria-experience / aria

Earth has a Voice. Take a listen
https://aria.earth
MIT License
3 stars 0 forks source link

Request GIBS catalog to populate data set options #8

Closed digitaltopo closed 5 years ago

digitaltopo commented 5 years ago

Instead of hard coding our datasets, we can make a request to the GIBS API (NASA Global Imagery Browse Services) to dynamically populate the datasets.

Resources

Notes

digitaltopo commented 5 years ago

The as mentioned above, the GIBS api has an endpoint that let's us request all the available datasets:

We're using the EPSG:3857 or "Web Mercator" projection for our map, so compatible results will be found at:

REST GetCapabilities endpoint for EPSG:3857: gibs.earthdata.nasa.gov/wmts/epsg3857/best/1.0.0/WMTSCapabilities.xml

which we get from the GIBS API Developers doc

The format which that api delivers data in is in xml which isn't very friendly for our app to use. Thankfully the web mapping library we're using, OpenLayers, has a great utility to parse those options and give us back some JSON we can use in the app!

In OpenLayers, this is done by using an instance of the WMTSCapabilities class. There isn't much on it in the docs, but they have a great example that shows us how to use this!

The important parts are making an async request to the API by using the browser fetch api, which processes the request to a remote API and returns a promise with the results! (basically its a variable that can exist and wait for the "promised" data once it's available, in this case, once the request to the api comes back successfully!)

One caveat with fetch is it may not be supported in older browsers, so it's smart to add a polyfill on our app that grabs it for anyone that doesn't have it available. We can use polyfill.io to implement this, by adding the line to our html scripts section in the <head> of our index.html:

<script src="https://cdn.polyfill.io/v2/polyfill.js?features=fetch"></script>

Back to our actual request:

First we need to make an actual request to the correct url, which we have from the GIBS docs. Fetch requests look like this:

fetch(THE_URL_YOU_WANT_TO_FETCH)
        // we chain a "then" which will fire when results come back successfully
        // we pass that response as an argument into our function
        .then(response => {
            // we convert the response to text, because it's xml
            return response.text();
        })
        // Next we do something with our text!
        .then(text => {
                // DO STUFF HERE!
        });

Now that we have the data from the API, we need to parse it!

So we need to make an instance of the parser so it can understand what's coming back from the API:

const parser = new WMTSCapabilities();

So in the "//DO STUFF HERE" section above, where we process the text we got back from api, let's use our parser to make sense of the text, and save it into a variable called result:

...
.then(text => {
    const result = parser.read(text);
});

Now we have parsed the capabilities from the api! So how do we use that? We have a couple options:

  1. We can save that to a json object and have our function doing all this return it so we can use it in other parts of the app. To do that, we'd want to first convert to json using JSON.stringify and then return it from our function:
... 
// Parse the response text
const result = parser.read(text);

// Create JSON object from result
const json = JSON.stringify(result, null, 2);

return json
...

Quick note, we should probably put this whole fetch utility in it's own function (we could put it in the /Components/Map/utils.js file for example), in which case if we want the function to return something, we actually also need to return the whole fetch part too. Ex):

export const fetchGibsCatalog = () => {

    return fetch(...);

}

Her is a gist of what the final GIBS capabilities json object we created looks like at so we can understand the structure of it in order to extract what we want, which is the layers list and all their properties.

Here's a gist of an individual layer from that list, to demonstrate what all data we are getting back about the layer.

So now we have that json of the capabilities and layers available, maybe we can add it to our map state and hook our layers list up to populate items from there, and then read that whenever we build our tile layers.

BONUS/EVEN BETTER:

  1. Right now we use a function called createDataLayerXYZUrl to create urls. There is another OpenLayers utility called optionsFromCapabilities that can help us do this without all the manual looking up just by giving it a Identifier (from the layer list we got from the capabilities) as the layer option in the function. We could refactor our function to use this instead, which should be able to give us what we need to update our map layer source when we change what data set we want to view!

To better let OpenLayers handle all of this for us, we could switch out the current xyz layer to use a TileLayer with the openlayers WMTS source.

Here's an example of WMTS layer in action, although you'll notice that here the source property and all it's options are hardcoded in, so they do some extra work to calculate a tileGrid, but we'd have all those options directly from the API using the optionsFromCapabilities function we described above.

Some of this part may require some more familiarity with web mapping and specifically the OpenLayers library, but we can do the fetch part first then work together to learn and implement the second part together!