Goals

Because there are a wide range of Leaflet related modules, many of whom have duplicative js code, it would be good to assemble a road-map for identifying how these all relate to the common Leaflet.js back end, identify areas of code redundancy and conflict, explore methods for relating as plugins and generally identifying the various development goals of motivated parties.

  1. User Interface
  2. Extending Leaflet
  3. Layer Control
  4. Performance & Server-Side Data - not really Leaflet specific, but things like views support for clustering and bounding box filtering.

User Interface Needs

  • Panels support (a la Leaflet GeoJSON) - Views is currently the only UI means of configuring Leaflet maps in Drupal, and while this capability is useful and convenient, there is a strong argument for using Views only as a data source to supply other Entities the raw materials for creating maps.
  • Config Form Hooks - Leaflet configuration needs to expose hooks to sub-modules to allow them to gracefully add to configuration forms for use in the existing Views Display config screen, as well as any future Panels-based config screen.
  • Javascript Events - A more robust javascript event model to allow sub-modules to modify leaflet map layers and settings gracefully.
  • Multiple Feature Layers - It would be useful to support multiple, switcheable layers (such as provided by Leaflet GeoJSON module).

Leaflet Related Modules

Module Name Provides UI Adds/Alters Layers in js Leaflet Hooks Used Triggers Used Proposed Triggers Extends leaflet.drupal.js Extends leaflet.js
Leaflet Node Field Widget
Views Display
No N/A None Provider of leaflet.drupal.js No
Leaflet More Maps No No hook_leaflet_map_info(), hook_leaflet_map_info_alter() No No No No
Leaflet GeoJSON Panels
Beans
No hook_leaflet_map_info(), hook_leaflet_map_info_alter() No No No Yes - registers an event listener for Leaflet "moveend" to call new function "makeGeoJSONLayer"

Leaflet

  • The base module integrates with leaflet.js by providing geojson export of a map definition.
  • Includes a single example map with a base layer and geojson feature from Drupal content.
  • Uses file "leaflet.drupal.js" to integrate with Leaflet.js library
    • Supports the use of a single layer class for base and overlays, "L.TileLayerZoomSwitch", by calling "extend()" method of Leaflet "TileLayer" (L.TileLayer.extend).
    • L.TileLayerZoomSwitch Adds grouping and switchability, but effectively limits access to "TileLayer" features, does not support other Leaflet.js TilerLayer types such as "Raster Layer"s TileLayer.WMS & ImageOverlay
    • This also excludes adding "Vector Layer" and "Other Layers" (such as GeoJSON)
  • Does not implement CTools plugin framework

Leaflet More Maps

  • Expands map definitions for Leaflet to include over 20 maps with multiple layers and selection styles.
  • Extends or overwrites (?) "leaflet.drupal.js" with code in "leaflet_more_maps.js"

Leaflet Marker Cluster

  • Enables marker clustering in the Leaflet map.
  • Overwrites much code from leaflet.drupal.js (even has a mea culpa in the javscript file see Code 1 below)

Code 1: Sorry, I had to.

*
 * We are overriding a large part of the JS defined in leaflet (leaflet.drupal.js).
 * Not nice, but we can't do otherwise without refactoring code in Leaflet.
 */ 

Leaflet GeoJSON

  • Provides access to a Views GeoJSON as a layer in Leaflet
  • Does NOT overwrite js code, uses "include" on object "initialize" method to apply geoJSON options onto Leaflet Lmap object (Seee Code 2 below)
  • Adds critical function to allow client to load features from http request "makeGeoJSONLayer(map, url)"
  • Could this config options js code live in Drupal in the map array created/modified by "hook_leaflet_map_info()"?

Code 2: Leaflet GeoJSON Injection.

  // Inject into leaflet initialize.
  // @todo: there should be a nicer way to do that?
  _leaflet_bbox_old_leaflet_initialize = L.Map.prototype.initialize;
  L.Map.include({
    initialize: function(/*HTMLElement or String*/ id, /*Object*/ options) {
      _leaflet_bbox_old_leaflet_initialize.apply(this, [id, options]);
      this.on('load', Drupal.leafletBBox.onMapLoad, this);
    }
  });

Leaflet OMS

  • Extends Leaflet with the Overlapping Marker Spiderfier library
  • Overwrites the whole attach code from leaflet.drupal.js - just to add a few new lines.

Leaflet Popup Outside

  • Displays Pop-Ups in a div external to the map
  • Over-rides default Pop-up behavior in js by "Attaching to Leaflet object via its API" (from code comments)

Leaflet Label

  • Displays Pop-Ups in a div external to the map
  • Over-rides default Pop-up behavior in js using "bind" method, similar to Leaflet Popup Outside (see "Code 3" below)
  • Could this be merged with LPO for toolkit of labelling solutions?

Code 3: Using javascript ".bind" method.

(function ($) {

  $(document).bind('leaflet.feature', function(e, lFeature, feature) {
    if (feature.label) {
      lFeature.bindLabel(feature.label);
    }
  });

})(jQuery);

Use cases

It is helpful to consider the types of uses of the module to see how best to provide the base API and how a user can extend it.

  • Map with markers/shapes from remote GeoJSON source (see image 1 below)-
    • Data will be dynamically tile-able, for better performance
    • Source of data is "truly remote", data sourced outside of Drupal
    • Source of data is "local remote", i.e., served by the same Drupal instance
  • "Progressive Server Side Clustering" - client side map and server each handle some of the clustering tasks.
  • Map with markers of nodes
  • Map with markers of other content
  • And markers that pop up an info window
    • Source of data is the node
    • Source of data is one or more fields on the view
  • Markers with extended behavior (hover state opens one popup, click navigates or focuses another list on screen)
  • Default marker popup
  • External event focuses and pops a marker

Image 1: Geocluster with Leaflet shows remote access use diagram (@dasjo thesis problem statement).

Geocluster Views Use Case

Coding Approaches

Javascript

The base script leaflet.drupal.js adds "trigger" access to the base map class, whereby modules that extend it make a call to "bind" to advertise that they will be including modifications during the triggered event (see "Code 4" below).

Code 4: The following two triggers are exposed by leaflet.drupal.js:

        // Allow others to do something with the feature.
        $(document).trigger('leaflet.feature', [lFeature, feature]);

        // Allow other modules to get access to the map object using jQuery's trigger method
        $(document).trigger('leaflet.map', [this.map, lMap]);

Code 5: "bind" example, adding a location control (given here by @pvhee ):

$(document).bind('leaflet.map', function(e, map, lMap) {
  // do something with your map, e.g. adding a support for LocateControl, https://github.com/domoritz/leaflet-locatecontrol
  L.control.locate().addTo(map);
}

Drupal

Javascript Inclusion

When using included js in Drupal, things such as "Leaflet GeoJSON" modify the map object after the base Drupal Leaflet module has done it's thing. In order to make this proceed without error, any script that is doing this modification must be loaded after drupal.leaflet.js as indicated in this thread and as outlined in the API.

Configuration in Drupal UI

  • Views UI for map creation
  • Fields config in "Manage Display"

Configuration via Leaflet hooks

  • hook_leaflet_map_info - From Leaflet API - modules implementing this hook can append map definitions with multiple layers via properly formatted Drupal Leaflet arrays, see "Code 6" below (which I think are translated directly into Leaflet JS Object properties?)
  • hook_leaflet_map_info_alter - Leaflet API (See "Code 7" below).

Code 6: Example Drupal Leaflet map definition array for OSM Weather Map from Leaflet More Maps. Variables $prot and $zxy

  // Use headless protocol rather than logic based on global $is_https;
  $prot = '//';
  // As used by most.
  $zxy = '{z}/{x}/{y}.png';
  // As used by Esri.
  $zyx = '{z}/{y}/{x}.png';

  $attr_owm = 'OpenWeatherMap. ' . $attr_osm;
  $map_info['osm-weather'] = array(
    'label' => 'OSM OpenWeatherMap (zoom 0..18)',
    'description' => t('Precipitation with pressure contours'),
    'settings' => $default_settings,
    'layers' => array(
      'base' => array(
        'urlTemplate' => $prot . "{s}.tile.openstreetmap.org/$zxy",
        'options' => array('attribution' => $attr_owm),
        'layer_type' => 'base', // Allowed values "base" or "overlay"
      ),
      'precipitation' => array(
        'urlTemplate' => $prot . "{s}.tile.openweathermap.org/map/precipitation/$zxy",
        'options' => array(
          'attribution' => $attr_owm,
          'transparent' => true,
        ),
        'layer_type' => 'overlay', // Allowed values "base" or "overlay"
      ),
      'pressure' => array(
        'urlTemplate' => $prot . "{s}.tile.openweathermap.org/map/pressure_cntr/$zxy",
        'options' => array('attribution' => $attr_owm),
        'layer_type' => 'overlay', // Allowed values "base" or "overlay"
      ),
    ),
  );

Code 7: Example of hook_leaflet_map_info_alter() as implemented by LeafletMoreMaps.

function leaflet_more_maps_leaflet_map_info_alter(&$map_info) {
  @ksort($map_info, SORT_NATURAL | SORT_FLAG_CASE);
}

Comments

das-peter’s picture

Issue summary: View changes

Added another module and links to the modules.

robertwb’s picture

Issue summary: View changes

Just going through the list of modules on the Leaflet Docs page and adding them here.

robertwb’s picture

Issue summary: View changes

Added leaflet popup outside

robertwb’s picture

Issue summary: View changes

Added Leaflet Label

texas-bronius’s picture

Thanks Robert- Great initiative!

As I understand it, the purpose of Leaflet for Drupal is to make building leaflet projects with Drupal data easier. The core api should be as flexible as it should be, and there should be maybe an out-of-box layer atop that provides all the easy and expected functionality as a plugin or big option. With modules that do it this way, I have a difficult time discerning between module API and provided library API: let's keep documentation in that regard clear and distinct from the beginning-- I'd love to help!

-Bronius

texas-bronius’s picture

Issue summary: View changes

Adding use cases section and brainstorming some possibilities based on what I've come across or imagine could be

robertwb’s picture

Good stuff @texas-bronius - thinking of a bit of a flow chart - especially given the different cases for source of data going to Leaflet. @chriscallip had an additional angle on this, that is, Fields themselves may use Leaflet to display and contain default config information that may reference plugins (if we had them/when we get them that is). Diagram to follow.

chriscalip’s picture

Nice I'll open up a sandbox for prototyping purposes.

robertwb’s picture

Issue summary: View changes
  • Added Use Case "Markers/shapes are Remote GeoJSON source"
  • Added Section "Coding Approaches"
dasjo’s picture

Robert just contacted me about the geocluster use case. It has leaflet integration but the clustering is independent.

robertwb’s picture

Issue summary: View changes

Added geocluster use case diagram link

robertwb’s picture

The Geocluster use case from @dasjo is a critical element here. I think Leaflet module currently lacks the ability to perform the back and forth access to json, it gets the json from views when its created, then its embedded in the map.

While such things as clustering are a natural to remain separate from the Leaflet module, basic support for server side json sourcing would really be a great base feature for Leaflet module. Especially with large data sets, with and without clustering.

robertwb’s picture

dasjo’s picture

As creator of the Geocluster module I'd strongly suggest to keep leaflet lean and clean as it is. I'm very happy to see activity here in comparing different use cases but we have to admit that one module can't solve all of them. Still I find it valuable to discuss possibilities in improving the code base. Also note that I haven't done much mapping since a year so I'm not really up to date with latest developments.

The "back and forth access to json" functionality described in #12 is the BBOX strategy that i implemented for https://www.drupal.org/project/leaflet_geojson - the name is inspired by a similar functionality in openlayers which i couldn't find in leaflet by that time.

In general, my feeling is that the javascript mapping ecosystem outside of Drupal develops ten times faster than we do. So while it is a good idea to research what is there in Drupal i would find it much more appealing to review: what are the best practices outside of Drupal and what kind of code do we still need.

Thanks again for starting this discussion, I'd be happy to follow along

chriscalip’s picture

I agree that to try and keep leaflet - lean and clean; but it does not need to be as it is. It's because the current status quo of d.o leaflet has developers stuck with wholesale alters e.g leaflet_markercluster.

It does not need to be that way and we can make use of tried and true d.o patterns of hooks and drupal_alters.

The equivalent of hooks in jquery would be trigger ie. $(document).trigger('leaflet.feature', [lFeature, feature]);

And there should be accommodations for situations like leaflet_markercluster to override what layers get added to the map through some sort of specific implementations of drupal_alter (pretty much like an implementation of module_implements). For example if we just have .trigger implementations .. d.o leaflet would be the one to implement what gets added to the map; but also lets other modules implement trigger.-- which is not the case in leaflet_markercluster.

//Instead of :
$implementorsOfGroupsToAddToMap = (leaflet.core,leaflet.markercluster);
//then just trigger those $implementorsOfGroupsToAddToMap;

//We have
$implementorsOfGroupsToAddToMap = (leaflet.core,leaflet.markercluster);
alter($implementorsOfGroupsToAddToMap);
trigger($implementorsOfGroupsToAddToMap);

//same pattern
$implementorsOfControlsAddToMap = (leaflet.core,leaflet.geosearch,leaflet.categories);
alter($implementorsOfControlsAddToMap);
trigger($implementorsOfControlsAddToMap);

This way d.o leaflet will have option to provide "What Scripts to Add to this map instance". either in field_ui on node display or a panels ctools plugin implementation..

robertwb’s picture

> leaflet lean and clean as it is

Absolutely. But, I think lean is good, it needs some cleaning, and I wonder if it misses exposing basic Leaflet functions. Before I go to far down making a case for that road, I have to implement your module and see what's going on.

Basically, I think that leaflet module should be able to either 1) expose all layer types that Leaflet is capable of, or 2) leaflet.drupal.js needs to be plug extendable to expose those layer types.

My 2nd requirement is what I think @chriscallip is laying out.

robertwb’s picture

Issue summary: View changes

Updated to add relevant code snippets from Leaflet GeoJSON. @dasjo, I think that it is the " @todo: there should be a nicer way to do that?" that we are seeking here at minimum.

robertwb’s picture

Issue summary: View changes

Added some js inclusion info.

robertwb’s picture

Issue summary: View changes

Added examples of "trigger" and "bind" in javascript coding section.

robertwb’s picture

Added section for Drupal UI coinfiguration inclusion.

robertwb’s picture

Issue summary: View changes

Details of javascript approach used by Leaflet GEOJSON

robertwb’s picture

Issue summary: View changes

formatting.

Pol’s picture

For the UI I think you may use the new OpenLayers 3.1.x module that I'm writing.

It's mainly using CTools Export UI and it's perfect.

See it for yourself: https://groups.drupal.org/node/440733, checkout some videos there.

The code is here: http://cgit.drupalcode.org/openlayers/tree/?h=7.x-3.1.x

dasjo’s picture

as i'm not a javascript coder, i think the "Leaflet GeoJSON Injection" is really a bad practice. there should be nicer ways to do this?

dasjo’s picture

@pol cool stuff in OL 3.1.x! but still openlayers man... ;)

Pol’s picture

@dasjo: I know it's OL, I just wrote this to let you know that the Drupal module could also be used as "a base" for the Leaflet module.

Actually, if I had time, I would do something for Leaflet and OL...

robertwb’s picture

> Actually, if I had time, I would do something for Leaflet and OL...

Maybe time comes available soon! :) Still - good example, and I am wondering if the CTools UI Export stuff might be used like ECK for prototyping and coding shortcuts.

@dasjo - I am wondering if this would not be possible through "trigger" "leaflet.feature", or perhaps we can set up another trigger event for adding or modifying a given layer/feature?

robertwb’s picture

@dasjo - for the Leaflet GeoJSON use case and other cases that need to add a non-stock layer to it, maybe the model would look like:

  1. Tell Leaflet about the layer through the hook_leaflet_map_info
  2. Alter properties/events in the layer via trigger & bind
dasjo’s picture

@pol i was just trying to say that i find leaflet much cooler, because openlayers feels old. on the other hand side we know that is has maany nice features.

with regards to the join-forces idea about configuration, it would be nice. still, too much abstraction makes it hard for us to keep up to date with how those libraries evolve.
i have no clue how far that went, but zzolo started mapping which seems to be further developed by mac_weber, also see this documentation page: https://www.drupal.org/node/2072765

dasjo’s picture

also, i remember phayes, the geocoder creator, had a lot of ideas for a canonical way to represent map configurations to be done via the drupal ui and to be shared across libraries. but sorry no links here :)

robertwb’s picture

+1 on map configs in Drupal UI (i.e., outside of any specific mapping module) - would help the mapping implementations stay lean.

robertwb’s picture

@dasjo - please correct me if I'm wrong but I think that the part that you inserted that allows the geojson layer to be updated when changing extents is actually a new feature that leaflet.js does not possess?

So in other words you're extending leaflet, not Drupal leaflet. And I'm not saying this is bad, on the contrary, it's totally kick-ass, I am just trying to wrap my head around the kind/nature of alterations to the Leaflet JS Object/interface that occur in the course of sub-module activity so as to help formulate a model to make it all work together nicely.

robertwb’s picture

Issue summary: View changes
das-peter’s picture

I think one of the first things we actually can do is to introduce a ton of leaflet events and ensure the information / maps handled by Drupal.leaflet are easily accessible.
I thought about having events like:

  • leaflet.init: After preparing the settings - before creating the map
  • leaflet.map_alter: After creating the map object
  • leaflet.layers_perpare: Before processing the defined layers
  • leaflet.layer_create: Before creating a layer
  • leaflet.layer_alter: After creating a layer
  • leaflet.layers_alter: After processing the layers
  • leaflet.features_prepare: Before processing the features
  • leaflet.feature_create: Before creating a feature
  • leaflet.feature_alter: After creating a feature
  • leaflet.features_alter: After processing the features
  • leaflet.alter: After the processing is done (basically the leaflet.map trigger now.)

Of course we also need to define sensible way's of how to handle return values by the functions that bind to those events. E.g. skipping creation of a feature / layer.
And it would be nice to be able to access a map (it's settings, objects and so on) through Drupal.leaflet e.g. Drupal.leaflet.getMap([key]).

I think with this events contrib modules should be able to inject / alter quite a lot without actually overwriting code and thus excluding other contrib code.

robertwb’s picture

I think that makes sense @das-peter. I would want to get a sense of the triggers that current modules would use as a means of prioritizing development. With the "leaflet.map" trigger, would perhaps want to keep that for X versions until deprecating so as not to break other installations that rely on it?

@dasjo mentioned that the geo community may be doing things such as this -- would anyone have an idea of function naming conventions in use for any other major projects that might be worth evaluating for emulation?

robertwb’s picture

"Progressive Server Side Clustering" is a real power-use case, where the client side map and server each handle some of the clustering tasks. Video demo from @chriscallip here: http://www.youtube.com/watch?v=pRYKNlaTbKk

robertwb’s picture

Issue summary: View changes

Added use case for "progressive clustering"

robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes

Added information on structure and limitations of layers provided by leaflet.drupal.js

robertwb’s picture

Issue summary: View changes
chriscalip’s picture

if the leaflet.drupal module is done correctly with its hooks and alters.. progressive clustering can be cleanly be done through a contrib module.

dasjo’s picture

with regards to the leaflet_geojson js injection, see #2121869: Use leaflet.map trigger instead of L.Map.include

robertwb’s picture

Issue summary: View changes

Added examples of hook_leaflet_map_info_alter() and hook_leaflet_map_info().

robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes
robertwb’s picture

Added "related issues" link to early conversations on bounding box and progressive clustering. Provides more background on clustering objectives and performance criteria.

robertwb’s picture

Issue summary: View changes

Added documentation for hooks and events and UI widgets for Leaflet GeoJSON

robertwb’s picture

Issue summary: View changes
das-peter’s picture

I just took a shot at the JS API issue over here: #1893660-16: Refactor leaflet.drupal.js to allow other modules to extend and hook into map creation
Feedback would be great.

robertwb’s picture

Issue summary: View changes
Pol’s picture

Hi all,

This afternoon I played with Leaflet module.
A couple of hours later, with a couple of copy/paste of the Openlayers 3.1.x codebase, I started to do a new module using Leaflet.

I just made a screenshot here: https://twitter.com/drupol/status/548812336820408320

Do you think it can be useful for you ?

chriscalip’s picture

Pol,

Yes, after seeing the youtube videos; the leaflet port is something that is interesting and useful. Link please.

Pol’s picture

Sources are not online yet, if you give me access to the repository, I can git upload my work and work on that branch.

chriscalip’s picture

Right, I am not one of the project maintainers for d.o leaflet. I would like to help to move this forward though. The configurable map plugins via ctools is a big win already and would like to build from that common core. I can either give you access to a sandbox project, or you can give me access to a sandbox project, or github.., or a link to a sandbox project (i am happy to take it from there..)

Pol’s picture

Hold on, I'll put this on a sandbox.

Pol’s picture

Sandbox created and you have write access on it.

FYI, this stuff has been created in less than one hour, it's still a bit buggy but it's mainly working. My ultimate goal would be to provide a single module for OpenLayers AND Leaflet but right now, I haven't found a way to merge but of them, but I'm almost there.

I hope you'll like it !

chriscalip’s picture

http://cgit.drupalcode.org/sandbox-Pol-2399051 has an error : No repositories found

Pol’s picture

Try again, it's working now. http://cgit.drupalcode.org/sandbox-Pol-2399051/tree/

Gotta go to sleep, I'll be back on this tomorrow, I'll be on IRC #drupal-geo if you need.

das-peter’s picture

This is amazing! I really hope we can find to merge the efforts. A lot of stuff is done twice.
If we can find a common ground for the "drupalisms" and then spin them properly off into the other libraries (leaflet, ol) this really could be a big step forward for the mapping topic.
Hope we can get to a point where we've the same php / Drupal code but two JS files - one for each mapping library.
The plugins could even declare which libraries are supported. That way we could reduce pressure of maintainers to build for both libraries. In my theory this will make it more likely that we're getting "examples"/plugins for one library - which again will work as examples on how to port it to another library.

chriscalip’s picture

Prototype working! Users are able to create maps, each map has it's own settings and activate plugins, set a map to a block, see map. More importantly contributed module devs will be able to quickly add map plugins "controls,interaction,layer types,etc.." without wholesale altering of drupal.leaflet.js because of the implementation of hook system. Also each map plugin may have settings and those are override-able settings by site-admin users.

Call to action!
Sandbox: Hopefully Leaflet 7.x-2.x has plenty of tasks and to-do's.

Important TO-DO's

  1. 7.x-2.x Geofield Display Formatter
  2. 7.x-2.x views integration
  3. Update Mechanism from 7.x-1.x to 7.x-2.x

See you all in the issue queue.

robertwb’s picture

Pol's work relies in part on a "nearly-ready-for-d8" Mapping object model, I think this is one thing that allowed him to adapt it so rapidly from OL3 to leaflet. I have begun documenting the object model here: https://www.drupal.org/node/2402479

robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes
robertwb’s picture

Issue summary: View changes