Legend Getting Added Again on Map Initialization Leaflet
Visualize THREDDS Services with Leaflet¶
Concluding Updated: March 2020
In this tutorial you will learn how to add a Leaflet map to a Tethys App for visualizing layers from a THREDDS server. This tutorial is adapted from Time Dimension Example 1 and the Siphon NCSS Time Series Instance. The post-obit topics will exist covered in this tutorial:
-
Using external JavaScript libraries in Tethys Apps
-
AJAX calls with JavaScript
-
Recursive Python Functions
-
Logging in Python
-
Leaflet Map
-
Leaflet Plugins: Time-Dimension
-
Siphon
-
OWSLib
0. Start From Previous Solution (Optional)¶
If yous wish to utilise the previous solution equally a starting point:
git clone https://github.com/tethysplatform/tethysapp-thredds_tutorial.git cd tethysapp-thredds_tutorial git checkout -b thredds-service-solution thredds-service-solution-3.4
i. Add Leaflet Map to Domicile View¶
Leaflet is not officially supported by Tethys Platform as a Gizmo, simply it tin can hands be added manually as follows:
-
Include the Leaflet libraries in your app. Leaflet can be added a number of different ways as documented on their Download page. For this tutorial use the CDN option. Replace the contents of
templates/thredds_tutorial/home.html
with:
{% extends "thredds_tutorial/base of operations.html" %} {% load tethys_gizmos %} {% block styles %} {{ cake .super }} < link rel = "stylesheet" href = "https://unpkg.com/leaflet@i.6.0/dist/leaflet.css" integrity = "sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin = "" /> {% endblock %} {% block global_scripts %} {{ cake .super }} < script src = "https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" integrity = "sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin = "" ></ script > {% endblock %} {% block header_buttons %} {% endblock %} {% cake app_content %} < div id = "leaflet-map" ></ div > {% endblock %} {% block app_actions_override %} {% endblock %}
-
Write a fleck of JavaScript to initialize the map using the JavaScript closure blueprint for organisation. Create a method chosen
init_map
inpublic/js/leaflet_map.js
with the post-obit contents:
/***************************************************************************** * FILE: Leaflet Map Module for THREDDS Tutorial * DATE: thirteen Dec 2019 * AUTHOR: Nathan Swain * COPYRIGHT: (c) Aquaveo 2019 * LICENSE: BSD 2-Clause *****************************************************************************/ /***************************************************************************** * LIBRARY WRAPPER *****************************************************************************/ var LEAFLET_MAP = ( role () { "utilize strict" ; // And enable strict fashion for this library /************************************************************************ * MODULE LEVEL / GLOBAL VARIABLES *************************************************************************/ var public_interface , // Object returned past the module m_map ; // The Leaflet Map /************************************************************************ * PRIVATE Function DECLARATIONS *************************************************************************/ // Map Methods var init_map ; /************************************************************************ * Individual Role IMPLEMENTATIONS *************************************************************************/ // Map Methods init_map = part () { // Create Map m_map = Fifty . map ( 'leaflet-map' , { zoom : iii , middle : [ 0 , 0 ], fullscreenControl : truthful , }); // Add together Basemap L . tileLayer ( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , { attribution : '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }). addTo ( m_map ); }; /************************************************************************ * DEFINE PUBLIC INTERFACE *************************************************************************/ /* * Library object that contains public facing functions of the package. * This is the object that is returned past the library wrapper function. * See beneath. * NOTE: The functions in the public interface have access to the private * functions of the library because of JavaScript function telescopic. */ public_interface = {}; /************************************************************************ * INITIALIZATION / CONSTRUCTOR *************************************************************************/ // Initialization: jQuery function that gets called when // the DOM tree finishes loading $ ( function () { init_map (); }); render public_interface ; }()); // Cease of package wrapper
-
Remove the padding around the content area and override the styles for the app actions area so that the map fills the content area. Create
public/css/leaflet_map.css
with the following contents:
/* Map Format */ # app-content-wrapper # app-content { height : 100 % ; } # inner-app-content { height : 100 % ; padding : 0 ; } # leaflet-map { pinnacle : 100 % ; } /* Remove padding on bottom where app-deportment department used to be */ # app-content-wrapper # app-content { padding-bottom : 0 ; }
-
Include the new stylesheet and JavaScript modules in
templates/thredds_tutorial/home.html
:
{% extends "thredds_tutorial/base.html" %} {% load tethys_gizmos static %} {% block styles %} {{ block .super }} < link rel = "stylesheet" href = "https://unpkg.com/leaflet@ane.6.0/dist/leaflet.css" integrity = "sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin = "" /> < link rel = "stylesheet" href = " {% static 'thredds_tutorial/css/leaflet_map.css' %} " /> {% endblock %} {% cake global_scripts %} {{ block .super }} < script src = "https://unpkg.com/leaflet@1.half-dozen.0/dist/leaflet.js" integrity = "sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin = "" ></ script > {% endblock %} {% block scripts %} {{ block .super }} < script src = " {% static 'thredds_tutorial/js/leaflet_map.js' %} " blazon = "text/javascript" ></ script > {% endblock %}
Tip
Load the static
library and utilise the static
tag to reference scripts, stylesheets, and other resources in your public
directory.
-
Remove superfluous navigation links in
templates/thredds_tutorial/base.html
:
{% block app_navigation_items %} {% endblock %}
-
Verify that the Leaflet map is now in the app. Browse to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. The leaflet map should announced in the content surface area of the app and fill it.
2. Create Controls for Selecting Datasets¶
In this step, you'll create controls to allow the user to search for and select a dataset and variable to visualize on the map. THREDDS WMS services provide a number of color ramps and styles out-of-the-box. Yous'll besides create a control for changing the mode of the layer.
-
Ascertain gizmos for the dataset selection controls in the
home
controller ofcontrollers.py
. Replace the contents ofcontrollers.py
with:
from django.shortcuts import render from tethys_sdk.permissions import login_required from tethys_sdk.gizmos import SelectInput @login_required () def home ( request ): """ Controller for the app home page. """ # Retrieve dataset options from the THREDDS service datasets = [] dataset_select = SelectInput ( display_text = 'Dataset' , name = 'dataset' , multiple = False , options = datasets , initial = None , select2_options = { 'placeholder' : 'Select a dataset' , 'allowClear' : Simulated } ) variable_select = SelectInput ( display_text = 'Variable' , proper name = 'variable' , multiple = False , options = (), select2_options = { 'placeholder' : 'Select a variable' , 'allowClear' : Faux } ) style_select = SelectInput ( display_text = 'Style' , name = 'fashion' , multiple = Faux , options = (), select2_options = { 'placeholder' : 'Select a style' , 'allowClear' : Faux } ) context = { 'dataset_select' : dataset_select , 'variable_select' : variable_select , 'style_select' : style_select , } return return ( request , 'thredds_tutorial/home.html' , context )
-
Add the controls to the
app_navigation_items
block intemplates/thredds_tutorial/home.html
:
{% block app_navigation_items %} < li form = "title" >Query</ li > {% gizmo dataset_select %} {% gizmo variable_select %} {% gizmo style_select %} {% endblock %}
-
Left align the section titles in the navigation by calculation the post-obit to
public/css/main.css
:
# app-content-wrapper # app-content # app-navigation . nav li . title { padding-left : 0 ; }
-
Verify that the controls are in the app and functioning properly. Browse to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. There should be three controls in the app navigation area on the left: Dataset, Variable, and Way.
Tip
If the app navigation window is non open, employ the hamburger bill of fare next to the app icon to open information technology.
3. Initialize Dataset Select Control¶
At this point the select controls are empty and don't do annihilation. In this step, you lot'll query the THREDDS service to populate the dataset select control with a list of bachelor datasets to visualize. You lot'll narrow the query to only those datasets that have the WMS service enabled.
-
Create a new Python module
thredds_methods.py
with the following contents:
def parse_datasets ( catalog ): """ Collect all bachelor datasets that have the WMS service enabled. Args: catalog(siphon.catalog.TDSCatalog): A Siphon itemize object bound to a valid THREDDS service. Returns: list<two-tuple<dataset_name, wms_url>: One 2-tuple for each dataset. """ datasets = [] for dataset_name , dataset_obj in catalog . datasets . items (): dataset_wms_url = dataset_obj . access_urls . get ( 'wms' , None ) if dataset_wms_url : datasets . append (( dataset_name , f ' { dataset_name } ; { dataset_wms_url } ' )) for catalog_name , catalog_obj in catalog . catalog_refs . items (): d = parse_datasets ( catalog_obj . follow ()) datasets . extend ( d ) return datasets
Annotation
This function is recursive, meaning information technology calls itself. Since THREDDS datasets tin be located at capricious paths, sometimes nested in deep binder hierarchies, the function needs to be able to follow the paths down to find all the datasets. In this case, information technology searches for both datasets and new catalogs. When it encounters a new itemize, it calls itself again, initiating a search for dataset and new catalogs at that level. The dataset are collected and returned back up the telephone call stack.
Tip
Depending on the size of the catalog and the connexion speed, this office tin take quite bit of time to parse all of the datasets. This can exist particularly annoying when developing. One strategy to deal with wearisome itemize services during development is to temporarily mock the data.
If you print the information returned by the office and copy it into a temporary variable, you can take the function render that instead. They the function will run instantaneously during development. Don't forget to modify the code back when you lot are washed.
Mocking the data wait something like this:
temp_datasets = [( 'Full Collection (Reference / Forecast Time) Dataset' , 'Total Collection (Reference / Forecast Fourth dimension) ' 'Dataset;https://thredds.ucar.edu/thredds/wms/grib/NCEP/GFS/Global_0p5deg/TwoD' ), ( 'All-time GFS Half Degree Forecast Fourth dimension Series' , 'Best GFS Half Degree Forecast Time ' 'Series;https://thredds.ucar.edu/thredds/wms/grib/NCEP/GFS/Global_0p5deg/Best' ), ( 'Latest Collection for GFS Half Caste Forecast' , 'Latest Collection for GFS Half Degree Forecast;https://thredds.ucar.edu/thredds/wms/grib/NCEP/GFS/Global_0p5deg/GFS_Global_0p5deg_20200228_0000.grib2' )] def parse_datasets ( catalog ): """ Collect all available datasets that have the WMS service enabled. Args: itemize(siphon.catalog.TDSCatalog): A Siphon catalog object bound to a valid THREDDS service. Returns: list<ii-tuple<dataset_name, wms_url>: One 2-tuple for each dataset. """ # datasets = [] # # for dataset_name, dataset_obj in catalog.datasets.items(): # dataset_wms_url = dataset_obj.access_urls.get('wms', None) # if dataset_wms_url: # datasets.append((dataset_name, f'{dataset_name};{dataset_wms_url}')) # # for catalog_name, catalog_obj in catalog.catalog_refs.items(): # d = parse_datasets(catalog_obj.follow()) # datasets.extend(d) # # return datasets # TODO: DON'T FORGET TO UNCOMMENT return temp_datasets
Handling the slow connexion or large itemize problem in production is trickier. I option would exist to implement a cache. A simple caching mechanism could be implemented past writing the results to a file the first time the function is called so loading the results from that file every time after that. This introduces new problem though: how do you update the cache when the catalog updates?
If your app requires only a specific subset of datasets and the entire THREDDS catalog, then it would probably be ameliorate to provide a list of hard-coded datasets, similar to what was washed in the Google Earth Engine tutorial. How y'all handle this trouble is ultimately dependent on the needs of your awarding.
-
Change the
home
controller incontrollers.py
to call theparse_datasets
function to get a listing of all datasets available on the THREDDS service:
from django.shortcuts import render from tethys_sdk.permissions import login_required from tethys_sdk.gizmos import SelectInput from .app import ThreddsTutorial equally app from .thredds_methods import parse_datasets
@login_required () def habitation ( request ): """ Controller for the app dwelling page. """ catalog = app . get_spatial_dataset_service ( app . THREDDS_SERVICE_NAME , as_engine = True ) # Think dataset options from the THREDDS service print ( 'Retrieving Datasets...' ) datasets = parse_datasets ( catalog ) initial_dataset_option = datasets [ 0 ] from pprint import pprint pprint ( datasets ) pprint ( initial_dataset_option ) dataset_select = SelectInput ( display_text = 'Dataset' , proper name = 'dataset' , multiple = Imitation , options = datasets , initial = initial_dataset_option , select2_options = { 'placeholder' : 'Select a dataset' , 'allowClear' : False } ) ...
Tip
If you run across HTTPS/SSL verification bug (e.chiliad. due to using a self-signed SSL certificate during development), you may want to disable SSL verification of the THREDDS catalog engine. To practice so, import the Siphon session manager then gear up the verify
setting to False
before retrieving your itemize engine:
from siphon.http_util import session_manager session_manager . set_session_options ( verify = False ) catalog = app . get_spatial_dataset_service ( 'my_thredds_service' , as_engine = Truthful )
Warning
Do Not DISABLE SSL VERIFICATION FOR APPS IN PRODUCTION.
-
Verify that
dwelling
controller is using the newparse_dataset
function to detect THREDDS datasets. Browse to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. After the home page loads, inspect the log messages in the concluding where Tethys is running. Thepprint
calls in our controller should print the object being returned from theparse_dataset
office in the terminal. It should also populate the options for the Dataset control.
4. Create Endpoint for Getting Available WMS Layers¶
Each time a new dataset is selected, the options in the variable and style controls demand to be updated to lucifer the variables and styles of the new dataset. This information can be found by querying the WMS endpoint of the dataset provided by THREDDS. Querying the WMS endpoint is nearly easily accomplished by using the OWSLib Python library. In this stride you will implement a new controller that will use OWSLib to retrieve the information and telephone call it using AJAX someday a new dataset is selected.
-
Add the post-obit
get_layers_for_wms
function tothredds_methods.py
:
from owslib.wms import WebMapService
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<layer_name:dict<styles,bbox>>: A dictionary with a central for each WMS layer available and a dictionary value containing metadata nigh the layer. """ wms = WebMapService ( wms_url ) layers = wms . contents from pprint import pprint print ( 'WMS Contents:' ) pprint ( 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 } }) print ( 'Layers Dict:' ) pprint ( layers_dict ) return layers_dict
Tip
If you encounter HTTPS/SSL verification issues (eastward.thousand. due to using a self-signed SSL certificate during development), you may want to disable SSL verification of the WebMapService
engine. To exercise so, import the OWSLib Authentication
class and create an auth
object with verify
fix to Simulated
. Then laissez passer this auth
object to the WebMapService
constructor:
from owslib.util import Authentication auth = Authentication ( verify = Fake ) wms = WebMapService ( wms_url , auth = auth )
Annotation
At the time of writing there was an open issue with the verify
parameter of an Authentication
object beingness negated when set up to Faux
, making this piece of work around not work. Encounter: OWSLib Issue 609.
Warning
DO NOT DISABLE SSL VERIFICATION FOR APPS IN Product.
-
Create the
get_wms_layers
controller incontrollers.py
:
from django.http import HttpResponseNotAllowed , JsonResponse from .thredds_methods import parse_datasets , get_layers_for_wms
@login_required () def get_wms_layers ( request ): json_response = { 'success' : False } if request . method != 'Go' : return HttpResponseNotAllowed ([ 'GET' ]) effort : wms_url = request . GET . get ( 'wms_url' , None ) print ( f 'Retrieving layers for: { wms_url } ' ) layers = get_layers_for_wms ( wms_url ) json_response . update ({ 'success' : True , 'layers' : layers }) except Exception : json_response [ 'error' ] = f 'An unexpected error has occurred. Delight attempt once more.' return JsonResponse ( json_response )
-
Create a new endpoint for the
get_wms_layers
controller by calculation a newUrlMap
to the tuple located in theurl_maps
method of the app form inapp.py
:
def url_maps ( self ): """ Add together controllers """ UrlMap = url_map_maker ( self . root_url ) url_maps = ( UrlMap ( proper noun = 'domicile' , url = 'thredds-tutorial' , controller = 'thredds_tutorial.controllers.habitation' ), UrlMap ( name = 'get_wms_layers' , url = 'thredds-tutorial/get-wms-layers' , controller = 'thredds_tutorial.controllers.get_wms_layers' ), ) return url_maps
v. Stub Out the Variable and Way Control JavaScript Methods¶
In this pace you will employ the new get-wms-layers
endpoint to get a list of layers and their attributes (e.g. styles) to update the variable and fashion controls.
-
Add the post-obit new variables to the MODULE LEVEL / GLOBAL VARIABLES department of
public/js/leafet_map.js
:
var m_layer_meta , // Map of layer metadata indexed by variable m_curr_dataset , // The electric current selected dataset m_curr_variable , // The current selected variable/layer m_curr_style , // The current selected style m_curr_wms_url ; // The electric current WMS url
-
Add the following module function declarations to the Private FUNCTION DECLARATIONS section of
public/js/leafet_map.js
:
// Control Methods var init_controls , update_variable_control , update_style_control ;
-
Add together the following module function stubs to the PRIVATE Role IMPLEMENTATIONS section of
public/js/leafet_map.js
, just below theinit_map
method:
// Control Methods init_controls = office () { console . log ( 'Initializing controls...' ); }; // Query the current WMS for available layers and add them to the variable control update_variable_control = function () { panel . log ( 'Updating variable control...' ); }; // Update the bachelor fashion options on the fashion control update_style_control = function () { console . log ( 'Updating style control...' ); };
Annotation
These functions are method stubs that will exist implemented in the following steps.
-
Phone call the
init_controls
method when the module initializes. Replace the INITIALIZATION / CONSTRUCTOR section ofpublic/js/leafet_map.js
with the post-obit updated implementation:
/************************************************************************ * INITIALIZATION / CONSTRUCTOR *************************************************************************/ // Initialization: jQuery function that gets chosen when // the DOM tree finishes loading $ ( function () { init_map (); init_controls (); });
half-dozen. Implement Variable and Fashion Command Methods¶
In this step you will implement the dataset controll JavaScript methods in public/js/leaflet_map.js
.
Here is a brief explanation of each method that will be implemented in this step:
-
init_controls: adds on-change handlers for each control then that y'all can implement the logic that happens whenever a control is changed. Chosen when the module initializes subsequently folio load.
-
update_variable_control: volition call the new
go-wms-layers
endpoint and create new select options for the variable control with the returned list of layers. It will likewise save the layer data for utilize by other methods. -
update_style_control: volition use the saved layer metadata to generate style options for the manner select.
-
Replace the
init_controls
method stub inpublic/js/leaflet_map.js
with the following implementation:
init_controls = function () { // Define what happens when the dataset select input changes $ ( '#dataset' ). on ( 'change' , function () { allow dataset_wms = $ ( '#dataset' ). val (); let dataset_wms_parts = dataset_wms . dissever ( ';' ); m_curr_dataset = dataset_wms_parts [ 0 ]; m_curr_wms_url = dataset_wms_parts [ one ]; // Update variable control with layers provided past the new WMS update_variable_control (); }); // Define what happens when the variable select input changes $ ( '#variable' ). on ( 'change' , function () { m_curr_variable = $ ( '#variable' ). val (); // Update the styles update_style_control (); }); // Ascertain what happens when the style select input changes $ ( '#style' ). on ( 'change' , part () { m_curr_style = $ ( '#style' ). val (); }); $ ( '#dataset' ). trigger ( 'alter' ); };
-
Replace the
update_variable_control
method stub inpublic/js/leaflet_map.js
with the following implementation:
update_variable_control = function () { // Use AJAX endpoint to get WMS layers $ . ajax ({ url : './become-wms-layers/' , method : 'GET' , data : { 'wms_url' : m_curr_wms_url } }). done ( function ( data ) { if ( ! data . success ) { panel . log ( 'An unexpected error occurred!' ); render ; } // 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 modify to refresh the select box $ ( '#variable' ). trigger ( 'change' ); }); };
-
Replace the
update_style_control
method stub inpublic/js/leaflet_map.js
with the following implementation:
update_style_control = function () { let first_option = truthful ; for ( var style in m_layer_meta [ m_curr_variable ]. styles ) { if ( first_option ) { m_curr_style = manner ; } let new_option = new Option ( mode , fashion , first_option , first_option ); $ ( '#way' ). suspend ( new_option ); first_option = false ; } $ ( '#style' ). trigger ( 'alter' ); };
-
Verify that the Variable and Style controls are updated properly when the dataset changes. Browse to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. Apply the Dataset command to select a new dataset and verify that the Variable and Style options update appropriately. Inspect the last where Tethys is running to encounter the output from the print argument we added for debugging in Step 4.
7. Add together Fourth dimension-Dimension Plugin to Leaflet Map¶
Many of the datasets hosted on THREDDS servers have time as a dimension. In this step you will add the Fourth dimension-Dimension plugin to the Leaflet map so that it tin can visualize data with the fourth dimension dimension. The plugin adds a time slider control to the map and provides a way to load and visualize WMS layers with a time dimension.
-
Include the Fourth dimension-Dimension Leaflet plugin libraries to
templates/thredds_tutorial/habitation.html
:
{% block styles %} {{ block .super }} < link rel = "stylesheet" href = "https://unpkg.com/leaflet@1.half-dozen.0/dist/leaflet.css" integrity = "sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin = "" /> < link rel = "stylesheet" href = "https://cdn.jsdelivr.internet/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.control.min.css" /> < link rel = "stylesheet" href = " {% static 'thredds_tutorial/css/leaflet_map.css' %} " /> {% endblock %} {% cake global_scripts %} {{ block .super }} < script src = "https://unpkg.com/leaflet@1.six.0/dist/leaflet.js" integrity = "sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin = "" ></ script > < script type = "text/javascript" src = "https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js" ></ script > < script type = "text/javascript" src = "https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.ane.1/dist/leaflet.timedimension.min.js" ></ script > {% endblock %}
-
Enable the Time Dimension command when initializing the map by replacing the
init_map
method inpublic/js/leaflet_map.js
with this updated implementation:
init_map = function () { // Create Map m_map = 50 . map ( 'leaflet-map' , { zoom : iii , middle : [ 0 , 0 ], fullscreenControl : true , timeDimension : true , timeDimensionControl : truthful }); // Add Basemap L . tileLayer ( 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , { attribution : '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }). addTo ( m_map ); };
-
Verify that the Time-Dimension control is enabled. Browse to http://localhost:8000/apps/thredds-tutorial in a spider web browser and login if necessary. In that location should now be a time slider control at the bottom of the map.
8. Add Selected Dataset Layer to Map¶
In this footstep, yous'll create the update_layer
method that volition add together the THREDDS dataset WMS layer to the Leaflet map.
-
Add together the post-obit new variables to the MODULE LEVEL / GLOBAL VARIABLES department of
public/js/leafet_map.js
:
var m_layer , // The layer m_td_layer ; // The Time-Dimension layer
-
Add the following module role declarations to the PRIVATE FUNCTION DECLARATIONS section of
public/js/leafet_map.js
:
-
Insert the
update_layer
method just afterwards theinit_map
method inpublic/js/leaflet_map.js
:
update_layer = office () { if ( m_td_layer ) { m_map . removeLayer ( m_td_layer ); } // 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 = Fifty . timeDimension . layer . wms ( m_layer , { updateTimeDimension : truthful }); // Add Time-Dimension-Wrapped WMS layer to the Map m_td_layer . addTo ( m_map ); };
-
Call the
update_layer
method when the mode changes. Supplant the on-change handler for the mode control (i.e.$('#fashion').on('change', ...);
) divers in theinit_controls
method inpublic/js/leaflet_map.js
with this updated implementation:
// Define what happens when the style select input changes $ ( '#fashion' ). on ( 'change' , function () { m_curr_style = $ ( '#style' ). val (); // Update the layer with the new styles update_layer (); });
-
Use the bounding box retrieved from the WMS service to automatically frame the selected layer on the map. Replace the on-change handler for the variable control (i.e.
$('#variable').on('change', ...);
) defined in theinit_controls
method with this updated implementation:
$ ( '#variable' ). on ( 'alter' , role () { m_curr_variable = $ ( '#variable' ). val (); // Update the styles update_style_control (); // Zoom to the bounding box of the new layer permit bbox = m_layer_meta [ m_curr_variable ]. bbox ; m_map . fitBounds ( bbox ); });
-
Verify that the layers show up on the map. Browse to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. Select the "Best GFS Half Caste Forecast Time Series" dataset using the Dataset control to test a time-varying layer. Printing the Play button on the Time-Dimension command to animate the layer.
ix. Implement Legend for Layers¶
The THREDDS implementation of the WMS standard includes support for the GetLayerGraphic
request. In this footstep you'll utilize this asking to generate a legend image for the layer and style selected.
-
Add together an HTML element for the legend but under the dataset select controls to
templates/thredds_tutorial/domicile.html
:
{% block app_navigation_items %} < li grade = "title" >Query</ li > {% gizmo dataset_select %} {% gizmo variable_select %} {% gizmo style_select %} < div id = "legend" > </ div > {% endblock %}
-
Add the following module function declarations to the PRIVATE Role DECLARATIONS section of
public/js/leafet_map.js
:
// Fable Methods var update_legend , clear_legend ;
-
To display the legend paradigm, simply add together an paradigm element and ready the
src
attribute to theGetLegendGraphic
request URL. Add theupdate_legend
method after theupdate_style_control
method inpublic/js/leaflet_map.js
:
// Legend Methods update_legend = part () { let legend = m_layer_meta [ m_curr_variable ]. styles [ m_curr_style ]. legend ; $ ( '#legend' ). html ( '<li course="championship">Legend<h1></li><img src="' + legend + '">' ); };
-
Clearing the legend is just a affair of removing the image chemical element. Add the
clear_legend
method afterward theupdate_legend
method inpublic/js/leaflet_map.js
:
clear_legend = part () { $ ( '#legend' ). html ( '' ); };
-
Replace the
update_layer
method inpublic/js/leaflet_map.js
with the following implementation.update_layer
will now call theclear_legend
andupdate_legend
methods before and later updating the layer, respectively:
update_layer = office () { 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 : 'paradigm/png' , transparent : true , colorscalerange : '250,350' , // Difficult-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 = 50 . 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 (); };
-
Verify that the fable has been added to the app. Browse to http://localhost:8000/apps/thredds-tutorial 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.
x. Implement a Map Loading Indicator¶
Depending on the speed of the THREDDS server and the user's net connectedness, loading the layers on the map may take some fourth dimension. In this stride y'all'll add a loading indicator so that the user knows when the app is working on loading layers.
-
Download this
animated map loading prototype
or find one that you like and save it to thepublic/images
directory. -
Create a new stylesheet called
public/css/loader.css
with styles for the loader elements:
# loader { display : none ; position : absolute ; height : calc ( 50 vh - 185 px ); left : calc ( fifty vw - 186 px ); } # loader img { border-radius : 10 % ; box-shadow : 0 0 10 px rgba ( 0 , 0 , 0 , 0.2 ); } # loader . testify { brandish : block ; }
Annotation
The loading paradigm is prepare to be hidden by default ( display: none;
). Notwithstanding, if the evidence
grade is added to the loading image it will appear ( brandish: cake
). You can test this past inspecting the folio, finding the #loader
chemical element and adding or removing the evidence
course manually.
-
Include the new
public/css/loader.css
and add the prototype to theafter_app_content
block of thetemplates/thredds_tutorial/home.html
template:
{% block styles %} {{ block .super }} < link rel = "stylesheet" href = "https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity = "sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin = "" /> < link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.ane.1/dist/leaflet.timedimension.control.min.css" /> < link rel = "stylesheet" href = " {% static 'thredds_tutorial/css/leaflet_map.css' %} " /> < link rel = "stylesheet" href = " {% static 'thredds_tutorial/css/loader.css' %} " /> {% endblock %}
{% block after_app_content %} < div id = "loader" > < img src = " {% static 'thredds_tutorial/images/map-loader.gif' %} " > </ div > {% endblock %}
-
Add the following module function declarations to the Individual Office DECLARATIONS department of
public/js/leafet_map.js
:
// Loader Methods var show_loader , hide_loader ;
-
Add the
show_loader
andhide_loader
methods later theclear_legend
method inpublic/js/leaflet_map.js
:
// Loader Methods show_loader = function () { $ ( '#loader' ). addClass ( 'prove' ); }; hide_loader = function () { $ ( '#loader' ). removeClass ( 'show' ); };
Note
The show_loader
and hide_loader
methods are very elementary, because all they demand to practice is add together or remove the show
grade to the #loader
element. The manner definitions in public/css/loader.css
handle the rest.
-
Bind the
show_loader
andhide_loader
methods to the tile loading events of the layer when it is created. Replace theupdate_layer
method inpublic/js/leaflet_map.js
with this updated implementation:
update_layer = function () { if ( m_td_layer ) { m_map . removeLayer ( m_td_layer ); } // Clear the legend clear_legend (); // Layer m_layer = 50 . 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 piece of work for all layers abovemaxcolor : "extend" , belowmincolor : "extend" , numcolorbands : 100 , styles : m_curr_style }); // Wrap WMS layer in Fourth dimension Dimension Layer m_td_layer = L . timeDimension . layer . wms ( m_layer , { updateTimeDimension : true }); // Add events for loading m_layer . on ( 'loading' , office () { show_loader (); }); m_layer . on ( 'load' , role () { hide_loader (); }); // Add Fourth dimension-Dimension-Wrapped WMS layer to the Map m_td_layer . addTo ( m_map ); // Update the legend graphic update_legend (); };
Annotation
The loading
event is chosen whenever tile layers kickoff loading and the load
event is chosen when the visible tiles of a tile layer have finished loading. See: TileLayer.WMS reference.
-
Besides show the map loader when the variable control is updating (the AJAX call to become the WMS layers could take some time to run). Supersede the
update_variable_control
method inpublic/js/leaflet_map.js
with the post-obit updated implementation:
update_variable_control = part () { // Testify loader show_loader (); // Use AJAX endpoint to go WMS layers $ . ajax ({ url : './get-wms-layers/' , method : 'Become' , data : { 'wms_url' : m_curr_wms_url } }). washed ( function ( data ) { if ( ! data . success ) { console . log ( 'An unexpected mistake occurred!' ); render ; } // Clear current variable select options $ ( '#variable' ). select2 (). empty (); // Salvage layer metadata m_layer_meta = data . layers ; // Create new variable select options allow first_option = true ; for ( var layer in data . layers ) { if ( first_option ) { m_curr_variable = layer ; } let new_option = new Choice ( 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 information technology is mutual to utilize impress statements. Rather than delete these when you are washed, turn them into log statements and then that y'all can employ them for debugging in the time to come.
-
Use the Python logging module to get a logger for this module:
import logging log = logging . getLogger ( __name__ )
-
Supersede
print
andpprint
calls with log statements incontrollers.py
:
@login_required () def dwelling ( request ): """ Controller for the app abode 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 ) ...
@login_required () def get_wms_layers ( request ): json_response = { 'success' : False } if request . method != 'GET' : return HttpResponseNotAllowed ([ 'Go' ]) endeavour : wms_url = request . Go . get ( 'wms_url' , None ) log . info ( f 'Retrieving layers for: { wms_url } ' ) ...
-
Replace
print
andpprint
calls with log statements inthredds_methods.py
:
import logging log = logging . getLogger ( __name__ )
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<layer_name:dict<styles,bbox>>: A lexicon with a key for each WMS layer bachelor 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 [ three ], layer_bbox [ two ]]] 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 tin touch the performance of your app. Apply 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 data to help you assess bugs or other issues with your app without needing to modify the code. In product, the Tethys Portal can be configured to log at different levels of detail using these classifications. See: Python Logging HOWTO and Tethys Portal Configuration.
12. Test and Verify¶
Scan to http://localhost:8000/apps/thredds-tutorial in a web browser and login if necessary. Verify the following:
-
A Leaflet map should be loaded on the page with one of the datasets visualized
-
At that place should exist three controls in the navigation menu on the left: Dataset, Variable, and Fashion
-
There should be a legend for the current layer nether the control in the navigation carte du jour.
-
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 "Fourth dimension not available".
-
Select the "All-time GFS One-half Degree Forecast Time Series" dataset using the Dataset command to test a fourth dimension-varying layer. Printing the Play push button on the Time-Dimension control to animate the layer.
13. Solution¶
This concludes the New App Project portion of the THREDDS Tutorial. Y'all can view the solution on GitHub at https://github.com/tethysplatform/tethysapp-thredds_tutorial/tree/thredds-service-solution-three.0 or clone it equally follows:
git clone https://github.com/tethysplatform/tethysapp-thredds_tutorial.git cd tethysapp-thredds_tutorial git checkout -b visualize-leaflet-solution visualize-leaflet-solution-iii.4
Source: http://docs.tethysplatform.org/en/stable/tutorials/thredds/visualize_leaflet.html
0 Response to "Legend Getting Added Again on Map Initialization Leaflet"
Post a Comment