{% endblock %}
2. Add the following module function declarations to the *PRIVATE FUNCTION DECLARATIONS* section of :file:`public/js/leafet_map.js`:
.. code-block:: javascript
// Legend Methods
var update_legend, clear_legend;
3. To display the legend image, simply add an image element and set the ``src`` attribute to the ``GetLegendGraphic`` request URL. **Add** the ``update_legend`` method after the ``update_style_control`` method in :file:`public/js/leaflet_map.js`:
.. code-block:: javascript
// Legend Methods
update_legend = function() {
let legend = m_layer_meta[m_curr_variable].styles[m_curr_style].legend;
$('#legend').html('
Legend
');
};
4. Clearing the legend is just a matter of removing the image element. **Add** the ``clear_legend`` method after the ``update_legend`` method in :file:`public/js/leaflet_map.js`:
.. code-block:: javascript
clear_legend = function() {
$('#legend').html('');
};
5. **Replace** the ``update_layer`` method in :file:`public/js/leaflet_map.js` with the following implementation. ``update_layer`` will now call the ``clear_legend`` and ``update_legend`` methods before and after updating the layer, respectively:
.. code-block:: javascript
update_layer = function() {
if (m_td_layer) {
m_map.removeLayer(m_td_layer);
}
// Clear the legend
clear_legend();
// Layer
m_layer = L.tileLayer.wms(m_curr_wms_url, {
layers: m_curr_variable,
format: 'image/png',
transparent: true,
colorscalerange: '250,350', // Hard-coded color scale range won't work for all layers
abovemaxcolor: "extend",
belowmincolor: "extend",
numcolorbands: 100,
styles: m_curr_style
});
// Wrap WMS layer in Time Dimension Layer
m_td_layer = L.timeDimension.layer.wms(m_layer, {
updateTimeDimension: true
});
// Add Time-Dimension-Wrapped WMS layer to the Map
m_td_layer.addTo(m_map);
// Update the legend graphic
update_legend();
};
6. Verify that the legend has been added to the app. Browse to ``_ in a web browser and login if necessary. The legend should appear under the Query controls in the navigation window on the left. Change the style and verify that the legend updates accordingly.
10. Implement a Map Loading Indicator
=====================================
Depending on the speed of the THREDDS server and the user's internet connection, loading the layers on the map may take some time. In this step you'll add a loading indicator so that the user knows when the app is working on loading layers.
1. Download this :download:`animated map loading image <./resources/map-loader.gif>` or find one that you like and save it to the :file:`public/images` directory.
2. Create a new stylesheet called :file:`public/css/loader.css` with styles for the loader elements:
.. code-block:: css
#loader {
display: none;
position: absolute;
top: calc(50vh - 185px);
left: calc(50vw - 186px);
}
#loader img {
border-radius: 10%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
#loader.show {
display: block;
}
.. note::
The loading image is set to be hidden by default (``display: none;``). However, if the ``show`` class is added to the loading image it will appear (``display: block``). You can test this by inspecting the page, finding the ``#loader`` element and adding or removing the ``show`` class manually.
3. Include the new :file:`public/css/loader.css` and add the image to the ``after_app_content`` block of the :file:`templates/thredds_tutorial/home.html` template:
.. code-block:: html+django
{% block styles %}
{{ block.super }}
{% endblock %}
.. code-block:: html+django
{% block after_app_content %}
{% endblock %}
4. Add the following module function declarations to the *PRIVATE FUNCTION DECLARATIONS* section of :file:`public/js/leafet_map.js`:
.. code-block:: javascript
// Loader Methods
var show_loader, hide_loader;
5. **Add** the ``show_loader`` and ``hide_loader`` methods after the ``clear_legend`` method in :file:`public/js/leaflet_map.js`:
.. code-block:: javascript
// Loader Methods
show_loader = function() {
$('#loader').addClass('show');
};
hide_loader = function() {
$('#loader').removeClass('show');
};
.. note::
The ``show_loader`` and ``hide_loader`` methods are very simple, because all they need to do is add or remove the ``show`` class to the ``#loader`` element. The style definitions in :file:`public/css/loader.css` handle the rest.
6. Bind the ``show_loader`` and ``hide_loader`` methods to the tile loading events of the layer when it is created. **Replace** the ``update_layer`` method in :file:`public/js/leaflet_map.js` with this updated implementation:
.. code-block:: javascript
update_layer = function() {
if (m_td_layer) {
m_map.removeLayer(m_td_layer);
}
// Clear the legend
clear_legend();
// Layer
m_layer = L.tileLayer.wms(m_curr_wms_url, {
layers: m_curr_variable,
format: 'image/png',
transparent: true,
colorscalerange: '250,350', // Hard-coded color scale range won't work for all layers
abovemaxcolor: "extend",
belowmincolor: "extend",
numcolorbands: 100,
styles: m_curr_style
});
// Wrap WMS layer in Time Dimension Layer
m_td_layer = L.timeDimension.layer.wms(m_layer, {
updateTimeDimension: true
});
// Add events for loading
m_layer.on('loading', function() {
show_loader();
});
m_layer.on('load', function() {
hide_loader();
});
// Add Time-Dimension-Wrapped WMS layer to the Map
m_td_layer.addTo(m_map);
// Update the legend graphic
update_legend();
};
.. note::
The ``loading`` event is called whenever tile layers start loading and the ``load`` event is called when the visible tiles of a tile layer have finished loading. See: `TileLayer.WMS reference `_.
7. Also show the map loader when the variable control is updating (the ``fetch`` call to get the WMS layers could take some time to run). **Replace** the ``update_variable_control`` method in :file:`public/js/leaflet_map.js` with the following updated implementation:
.. code-block:: javascript
update_variable_control = function() {
// Show loader
show_loader();
// Use REST endpoint to get WMS layers
fetch('./get-wms-layers/?' + new URLSearchParams({'wms_url': m_curr_wms_url}))
.then((response) => response.json())
.then((data) => {
if (!data.success) {
console.log('An unexpected error occurred!');
return;
}
// Clear current variable select options
$('#variable').select2().empty();
// Save layer metadata
m_layer_meta = data.layers;
// Create new variable select options
let first_option = true;
for (var layer in data.layers) {
if (first_option) {
m_curr_variable = layer;
}
let new_option = new Option(layer, layer, first_option, first_option);
$('#variable').append(new_option);
first_option = false;
}
// Trigger a change to refresh the select box
$('#variable').trigger('change');
// Hide the loader
hide_loader();
});
};
11. Clean Up
============
During development it is common to use print statements. Rather than delete these when you are done, turn them into log statements so that you can use them for debugging in the future.
1. Use the Python logging module to setup logging in :file:`controllers.py`:
.. code-block:: python
import logging
log = logging.getLogger(__name__)
2. Replace ``print`` and ``pprint`` calls with log statements in :file:`controllers.py`:
.. code-block:: python
@controller
def home(request):
"""
Controller for the app home page.
"""
catalog = app.get_spatial_dataset_service(app.THREDDS_SERVICE_NAME, as_engine=True)
# Retrieve dataset options from the THREDDS service
log.info('Retrieving Datasets...')
datasets = parse_datasets(catalog)
initial_dataset_option = datasets[0]
log.debug(datasets)
log.debug(initial_dataset_option)
...
.. code-block:: python
@controller
def get_wms_layers(request):
json_response = {'success': False}
if request.method != 'GET':
return HttpResponseNotAllowed(['GET'])
try:
wms_url = request.GET.get('wms_url', None)
log.info(f'Retrieving layers for: {wms_url}')
...
3. Replace ``print`` and ``pprint`` calls with log statements in :file:`thredds_methods.py`:
.. code-block:: python
import logging
log = logging.getLogger(__name__)
.. code-block:: python
def get_layers_for_wms(wms_url):
"""
Retrieve metadata from a WMS service including layers, available styles, and the bounding box.
Args:
wms_url(str): URL to the WMS service endpoint.
Returns:
dict>: A dictionary with a key for each WMS layer available and a dictionary value containing metadata about the layer.
"""
wms = WebMapService(wms_url)
layers = wms.contents
log.debug('WMS Contents:')
log.debug(layers)
layers_dict = dict()
for layer_name, layer in layers.items():
layer_styles = layer.styles
layer_bbox = layer.boundingBoxWGS84
leaflet_bbox = [[layer_bbox[1], layer_bbox[0]], [layer_bbox[3], layer_bbox[2]]]
layers_dict.update({
layer_name: {
'styles': layer_styles,
'bbox': leaflet_bbox
}
})
log.debug('Layers Dict:')
log.debug(layers_dict)
return layers_dict
.. tip::
Logging excessively can impact the performance of your app. Use ``info``, ``error``, and ``warning`` to log minimal, summary information that is useful for monitoring normal operation of the app. Use ``debug`` to log more detailed information to help you assess bugs or other issues with your app without needing to modify the code. In production, the Tethys Portal can be configured to log at different levels of detail using these classifications. See: `Python Logging HOWTO `_ and :ref:`tethys_configuration`.
12. Test and Verify
===================
Browse to ``_ in a web browser and login if necessary. Verify the following:
1. A Leaflet map should be loaded on the page with one of the datasets visualized
2. There should be 3 controls in the navigation menu on the left: **Dataset**, **Variable**, and **Style**
3. There should be a legend for the current layer under the control in the navigation menu.
4. The map should feature an animation slider. If the dataset selected has time varying data, the slider should display a time step. Otherwise it will say "Time not available".
5. Select the "Best GFS Half Degree Forecast Time Series" dataset using the **Dataset** control to test a time-varying layer. Press the **Play** button on the Time-Dimension control to animate the layer.
13. Solution
============
This concludes the New App Project portion of the THREDDS Tutorial. You can view the solution on GitHub at ``_ or clone it as follows:
.. parsed-literal::
git clone https://github.com/tethysplatform/tethysapp-thredds_tutorial.git
cd tethysapp-thredds_tutorial
git checkout -b visualize-leaflet-solution visualize-leaflet-solution-|version|