Skip to main content

Tell us about your project

Example of a coastal map with a lot of marked locations/pinned locations

In one of the projects, the specification included the use of Leaflet maps. One of the difficulties was to make the Leaflet overlays automatically retrieve the list of Drupal content types, not all of them, but only the specific ones. This task seemed to be even more difficult because the only solution was to work in JavaScript and I wasn't an expert in either Leaflet or JS.

The project was about sailing, and it was supposed to provide people who were traveling the oceans with useful information.

As a starting point for this task, I already had a Leaflet map, which was represented as a block view. In the View settings, it was set that for a country related nodes are presented as markers on the map.

Yellow icons on the maps are nodes, where each icon represents a different content type.

 

  1. jQuery(document).bind('leaflet.map', function(event, map, lMap) {
  2.   //custom code goes here
  3. });

Whenever we need to make some custom code for Leaflet maps, this is the code syntax and where we need to place our custom code.

 

  1. //map id
  2. var id = lMap._container.id;
  3. var url = window.location.href;
  4. var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
  5. var drupalSettings = JSON.parse(settingsElement.textContent);
  6. var length = drupalSettings.leaflet[ id ].features.length;
  7. var content_types = drupalSettings.contenttypes;
  8. var ct_names = Object.keys(content_types);
  9. var ct_length = ct_names.length;
  10. var ct_image = Object.values(content_types);
  11. var cnt_array = {};

In the first code snippet I've set the variable id which will retrieve the Leaflet map's ID from lMap Leaflet JavaScript core variable, and url which I'll need later in the code. Next, I've included drupalSettings ( which I'll use to pass some variables from Drupal to JavaScript ). Then, I`ve set the variables which will retreive the content type names and icon path values I`m gonna need later. The following example from theme_name.theme file is down bellow:

 

  1. function THEME_page_attachments_alter(array &$page) {
  2.  
  3.   $sql = "select data from config where name like '%leaflet_icons.settings%'";
  4.   $query = db_query($sql);
  5.   $content = $query->fetchAll();
  6.   $content = $content[0]->data;
  7.   $content = unserialize($content);
  8.   $contentTypes = \Drupal::service('entity.manager')->getStorage('node_type')->loadMultiple();
  9.   $contentTypesList = [];
  10.   // Get content Type information.
  11.   foreach ($contentTypes as $contentType) {
  12.     $contentTypesList[$contentType->id()] = $contentType->label();
  13.   }
  14.  
  15.   foreach ($contentTypesList as $key => $value) {
  16.     if (!empty($content[$key])) {
  17.       $val = $content[$key];
  18.  
  19.       $change = explode('/', $val);
  20.       $lastI = count($change);
  21.       $picture = explode('.', $change[($lastI - 1)]);
  22.  
  23.       if ($picture[0] != $key) {
  24.         unset($content[$key]);
  25.         $key = $picture[0];
  26.       }
  27.       $content[$key] = array($val, ucfirst(strtolower($value)));
  28.     }
  29.   }
  30.  
  31.   ksort($content);
  32.   $page['#attached']['drupalSettings']['contenttypes'] = $content;
  33. }

In my example above, I only needed content types that would be included in the overlays list and that have values for the icon image in the Leaflet Icons settings.

I have selected a value from the database table Leaflet icons, but as the content is serialized, we'll first have to unserialize it. After fetching them all, I've sorted everything alphabetically.

 

  1. $.each(content_types, function(key, value){
  2.   window[key] = new L.LayerGroup();
  3.   window[key+"Icon"] = L.icon({
  4.     iconUrl: value[0],
  5.     iconSize: [30, 30], // size of the icon
  6.         iconAnchor: [0, 0], // point of the icon which will correspond to marker's location
  7.         popupAnchor: [15, 15], // point from which the popup should open relative to the iconAnchor
  8.         className: 'leaflet-custom-layer'
  9.   });
  10. });

In the code above, we went through every content type dynamically and set Icon settings for each of them ( URL, position, size, etc ). We'll need that later as well.

 

  1. var base_url = "{{ url('<front>') }}";
  2.  
  3. var i;
  4. for (i = 0; i < length; i++) {
  5.  
  6.   var lat = drupalSettings.leaflet[ id ].features[i].lat; //lat
  7.   var lon = drupalSettings.leaflet[ id ].features[i].lon; //lon
  8.  
  9.   var iconUrl = drupalSettings.leaflet[ id ].features[i].icon.iconUrl;
  10.   var exloded = iconUrl.split('/');
  11.   var lengthall = exloded.length;
  12.  
  13.   var extension = exloded[(lengthall - 1)];
  14.   var split = extension.split('.');
  15.   var finalIcon = split[0];
  16.  
  17.   var popup = drupalSettings.leaflet[ id ].features[i].popup; //popup
  18.  
  19.   if (typeof window[finalIcon+'Icon'] != 'undefined') {
  20.     L.marker([lat, lon], {icon: window[finalIcon+'Icon']}).bindPopup(popup).addTo(window[finalIcon]);
  21.   }

Next, we need to set the base_url that we'll need for setting the icon path value.

We are going through every icon on the map, using a for loop to set some variables.

lat and lon variables are the coordinates of each node.

Next, we'll format the icons.

The variable popup is the value of a popup section for each node.  

In the end, we ask if typeof icon is not undefined, so we can set the node values, including its coordinates, icon URL value, and popup. All those nodes will appear on the map when we load the page.

 

  1. // do not show markers with icons on map load
  2. Drupal.Leaflet.prototype.create_point = function (marker) {
  3.   var latLng = new L.LatLng(marker.lat, marker.lon);
  4.   this.bounds.push(latLng);
  5.   var lMarker;
  6.   if (marker.icon) {
  7.     var icon = this.create_icon(marker.icon);
  8.     //lMarker = new L.Marker(latLng, {icon: icon});
  9.   }
  10.   else {
  11.     lMarker = new L.Marker(latLng);
  12.   }
  13.   return lMarker;
  14. };

As I said before, we did have icons on the map set already, before we started adding our custom code for Leaflet icons.

So, by adding these custom settings, our map would have duplicates of each icon.

We'll have to exclude those already appearing there before our changes.

That could be done by changing the JavaScript code above.

 

  1. var overlays = {};
  2.  
  3. $.each(content_types, function(key, value){
  4.   var text = '<img src="'+value[0]+'" /> '+value[1];
  5.   overlays[text] = window[key];
  6. });
  7.  
  8. L.control.layers(null, overlays).addTo(lMap);

In the end, we need to go again through all selected content types, and set the list of overlays and add them to the Leaflet map.

 

FULL CODE:

  1. jQuery(document).bind('leaflet.map', function(event, map, lMap) {
  2.   //map id
  3.   var id = lMap._container.id;
  4.   var url = window.location.href;
  5.   var settingsElement = document.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
  6.   var drupalSettings = JSON.parse(settingsElement.textContent);
  7.   var length = drupalSettings.leaflet[ id ].features.length;
  8.  
  9.   var content_types = drupalSettings.contenttypes;
  10.   var ct_names = Object.keys(content_types);
  11.   var ct_length = ct_names.length;
  12.   var ct_image = Object.values(content_types);
  13.   var cnt_array = {};
  14.  
  15.   $.each(content_types, function(key, value){
  16.     window[key] = new L.LayerGroup();
  17.     window[key+"Icon"] = L.icon({
  18.       iconUrl: value[0],
  19.       iconSize: [30, 30], // size of the icon
  20.       iconAnchor: [0, 0], // point of the icon which will correspond to marker's location
  21.       popupAnchor: [15, 15], // point from which the popup should open relative to the iconAnchor
  22.       className: 'leaflet-custom-layer'
  23.     });
  24.   });
  25.  
  26.   var base_url = "{{ url('<front>') }}";
  27.   var i;
  28.   for (i = 0; i < length; i++) {
  29.     var lat = drupalSettings.leaflet[ id ].features[i].lat; //lat
  30.     var lon = drupalSettings.leaflet[ id ].features[i].lon; //lon
  31.  
  32.     var iconUrl = drupalSettings.leaflet[ id ].features[i].icon.iconUrl;
  33.     var exloded = iconUrl.split('/');
  34.     var lengthall = exloded.length;
  35.    
  36.     var extension = exloded[(lengthall - 1)];
  37.     var split = extension.split('.');
  38.     var finalIcon = split[0];
  39.  
  40.     var popup = drupalSettings.leaflet[ id ].features[i].popup; //popup
  41.     if (typeof window[finalIcon+'Icon'] != 'undefined') {
  42.       L.marker([lat, lon], {icon: window[finalIcon+'Icon']}).bindPopup(popup).addTo(window[finalIcon]);
  43.     }
  44.   }
  45.   $.each(ct_names, function(index, value){
  46.     lMap.addLayer(window[value]);
  47.   });
  48.  
  49.   // do not show markers with icons on map load
  50.   Drupal.Leaflet.prototype.create_point = function (marker) {
  51.     var latLng = new L.LatLng(marker.lat, marker.lon);
  52.     this.bounds.push(latLng);
  53.     var lMarker;
  54.     if (marker.icon) {
  55.       var icon = this.create_icon(marker.icon);
  56.       //lMarker = new L.Marker(latLng, {icon: icon});
  57.     } else {
  58.       lMarker = new L.Marker(latLng);
  59.     }
  60.     return lMarker;
  61.   };
  62.  
  63.   var overlays = {};
  64.  
  65.   $.each(content_types, function(key, value){
  66.     var text = '<img src="'+value[0]+'" /> '+value[1];
  67.     overlays[text] = window[key];
  68.   });
  69.  
  70.   L.control.layers(null, overlays).addTo(lMap);
  71. });

 

Lazar Padjan