Visualization chooser
In the previous examples, we have seen a true color composite, a false color composite, and an NDVI rendering of the same Sentinel-2 image. It would be nice to allow users to choose from one of these visualizations and more without having to change our code each time. To do this, we'll create a list of available visualizations and add a <select>
element to our page to let users choose which to display.
In addition to the true color, false color, and NDVI visualizations, we'll add a new Normalized Difference Water Index (NDWI). This is similar to NDVI except that it can be used to monitor changes in water bodies.
NDWI = (GREEN - NIR) / (GREEN + NIR)
As we've seen, each visualization needs to have an array of sources
(these are the URLs for single- or multi-band GeoTIFFs), an optional max
value for scaling GeoTIFF values, and an optional style
for rendering the layer. In addition, we'll give each visualization a name
for displaying to the user.
Edit your main.js
to include the visualizations data below:
const visualizations = [
{
name: 'True Color',
sources: ['TCI'],
},
{
name: 'False Color',
sources: ['B08', 'B04', 'B03'],
max: 5000,
},
{
name: 'NDVI',
sources: ['B04', 'B08'],
max: 10000,
style: {
color: [
'interpolate',
['linear'],
['/', ['-', ['band', 2], ['band', 1]], ['+', ['band', 2], ['band', 1]]],
...getColorStops('earth', -0.5, 1, 10, true),
],
},
},
{
name: 'NDWI',
sources: ['B03', 'B08'],
max: 10000,
style: {
color: [
'interpolate',
['linear'],
['/', ['-', ['band', 1], ['band', 2]], ['+', ['band', 1], ['band', 2]]],
...getColorStops('viridis', -1, 1, 10, true),
],
},
},
];
Now instead of creating our GeoTIFF source and layer once, we need a function to create these when the user chooses a visualization. This function will take a base
URL and a visualization
and will return a layer
. Edit your main.js
to remove the source and layer definitions and include this function instead:
function createLayer(base, visualization) {
const source = new GeoTIFF({
sources: visualization.sources.map((id) => ({
url: `${base}/${id}.tif`,
max: visualization.max,
})),
});
return new TileLayer({
source: source,
style: visualization.style,
});
}
Next we can change the map definition in main.js
so it doesn't include any layers at all (these will be added when the user selects a visualization):
const map = new Map({
target: 'map-container',
});
Now we need a way for users to choose which visualization to show. For this, we'll add a <select>
element to the index.html
just before the <script>
tag:
<div id="controls">
<select id="visualization"></select>
</div>
To get this <select>
element to display over the map in the top right corner, add the following block to the <style>
tag in your index.html
:
#controls {
position: absolute;
top: 20px;
right: 20px;
}
With the <select>
element in place, we need to populate it with an <option>
for each of the visualization names. To do this, add the following to your main.js
somwhere below the visualizations
array:
const visualizationSelector = document.getElementById('visualization');
visualizations.forEach((visualization) => {
const option = document.createElement('option');
option.textContent = visualization.name;
visualizationSelector.appendChild(option);
});
Finally, we will create a function to update the map with a new layer based on the selected visualization. We'll add this function as a change
listener on our <select>
element and call it to initalize our application:
function updateVisualization() {
const visualization = visualizations[visualizationSelector.selectedIndex];
const base =
'https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A';
const layer = createLayer(base, visualization);
map.setLayers([layer]);
map.setView(layer.getSource().getView());
}
visualizationSelector.addEventListener('change', updateVisualization);
updateVisualization();
Now http://localhost:5173/ should show a visualization chooser.
Nice! Now the user can choose what type of visualization to render. But wouldn't it be nice to be able to change the imagery source too? That's next.