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

../../_images/visualize_leaflet_solution.png

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:

  1. 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              %}            
  1. Write a fleck of JavaScript to initialize the map using the JavaScript closure blueprint for organisation. Create a method chosen init_map in public/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              :              '&copy; <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            
  1. 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              ;                            }                          
  1. 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.

  1. Remove superfluous navigation links in templates/thredds_tutorial/base.html :

                            {%              block              app_navigation_items              %}              {%              endblock              %}            
  1. 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.

  1. Ascertain gizmos for the dataset selection controls in the home controller of controllers.py . Replace the contents of controllers.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              )            
  1. Add the controls to the app_navigation_items block in templates/thredds_tutorial/home.html :

                            {%              block              app_navigation_items              %}              <              li              form              =              "title"              >Query</              li              >              {%              gizmo              dataset_select              %}              {%              gizmo              variable_select              %}              {%              gizmo              style_select              %}              {%              endblock              %}            
  1. 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              ;                            }                          
  1. 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.

  1. 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.

  1. Change the home controller in controllers.py to call the parse_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.

  1. Verify that dwelling controller is using the new parse_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. The pprint calls in our controller should print the object being returned from the parse_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.

  1. Add the post-obit get_layers_for_wms function to thredds_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.

  1. Create the get_wms_layers controller in controllers.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              )            
  1. Create a new endpoint for the get_wms_layers controller by calculation a new UrlMap to the tuple located in the url_maps method of the app form in app.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.

  1. 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            
  1. 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              ;            
  1. Add together the following module function stubs to the PRIVATE Role IMPLEMENTATIONS section of public/js/leafet_map.js , just below the init_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.

  1. Phone call the init_controls method when the module initializes. Replace the INITIALIZATION / CONSTRUCTOR section of public/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.

  1. Replace the init_controls method stub in public/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'              );              };            
  1. Replace the update_variable_control method stub in public/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'              );              });              };            
  1. Replace the update_style_control method stub in public/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'              );              };            
  1. 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.

  1. 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              %}            
  1. Enable the Time Dimension command when initializing the map by replacing the init_map method in public/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              :              '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'              }).              addTo              (              m_map              );              };            
  1. 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.

  1. 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            
  1. Add the following module role declarations to the PRIVATE FUNCTION DECLARATIONS section of public/js/leafet_map.js :

  1. Insert the update_layer method just afterwards the init_map method in public/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              );              };            
  1. 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 the init_controls method in public/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              ();              });            
  1. 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 the init_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              );              });            
  1. 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.

  1. 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              %}            
  1. 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              ;            
  1. To display the legend paradigm, simply add together an paradigm element and ready the src attribute to the GetLegendGraphic request URL. Add the update_legend method after the update_style_control method in public/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              +              '">'              );              };            
  1. Clearing the legend is just a affair of removing the image chemical element. Add the clear_legend method afterward the update_legend method in public/js/leaflet_map.js :

                            clear_legend              =              part              ()              {              $              (              '#legend'              ).              html              (              ''              );              };            
  1. Replace the update_layer method in public/js/leaflet_map.js with the following implementation. update_layer will now call the clear_legend and update_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              ();              };            
  1. 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.

  1. Download this animated map loading prototype or find one that you like and save it to the public/images directory.

  2. 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.

  1. Include the new public/css/loader.css and add the prototype to the after_app_content block of the templates/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              %}            
  1. 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              ;            
  1. Add the show_loader and hide_loader methods later the clear_legend method in public/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.

  1. 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 public/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.

  1. 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 in public/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.

  1. Use the Python logging module to get a logger for this module:

                            import              logging              log              =              logging              .              getLogger              (              __name__              )            
  1. Supersede print and pprint calls with log statements in controllers.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              }              '              )              ...            
  1. Replace print and pprint calls with log statements in thredds_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:

  1. A Leaflet map should be loaded on the page with one of the datasets visualized

  2. At that place should exist three controls in the navigation menu on the left: Dataset, Variable, and Fashion

  3. There should be a legend for the current layer nether the control in the navigation carte du jour.

  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 "Fourth dimension not available".

  5. 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

peraltapriage.blogspot.com

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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel