Inverse Distance Weighting in Leaflet – Leaflet.idw

with 17 Comments

For some time now it has bugged me that there are no interpolation plugins for Leaflet. I believe that future mapping and GIS tasks will increasingly be performed in the cloud or on the fly in web applications, therefore an IDW plugin can become quite useful for Leaflet. Will There are a few heatmap plugins, but these do not always serve the purpose of the map. If for example one was to make a map with continuous temperature or wind interpolation, heatmaps are not the right choice. One of the more simple interpolation algorithms is the inverse distance weighting algorithm or IDW. The example below shows the working Leaflet plugin.

The current progress and the plugin can be seen and downloaded on github:

Leaflet.idw

Improvements, comments or questions are welcome 🙂

Example

Example ~100 points; CellSize: 3; Exp: 3

Example ~10 points; CellSize: 1; Exp: 1

Example ~10 points; CellSize: 1; Exp: 4

Code of Examples

   <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v1.0.0-beta.2/leaflet.css"/>

    <style>

        #map,#map2,#map3 {
            height: 500px;
        }
    </style>
            <h4>Example ~100 points; CellSize: 3; Exp: 3</h4>
            <div id="map"></div>
            <h4>Example ~10 points; CellSize: 1; Exp: 1</h4>
            <div id="map2"></div>
            <h4>Example ~10 points; CellSize: 1; Exp: 4</h4>
            <div id="map3"></div>

    <script src="http://cdn.leafletjs.com/leaflet/v1.0.0-beta.2/leaflet.js"></script>    
    <script src="Leaflet.idw/src/leaflet-idw.js"> </script>
    <script src="Leaflet.idw/example/exampledata.js"></script>
    <script src="Leaflet.idw/example/meteodata.js"></script>
    
    <script>

    var map = L.map('map').setView([-37.87, 175.475], 12);

    var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map);

    var idw1 = L.idwLayer(addressPoints,{
            opacity: 0.3,
            maxZoom: 18,
            cellSize: 3,
            exp: 3,
            max: 1200
        }).addTo(map);

var meteoPoints = [
            [ 47.11285 , 7.222309, 33], //Ipsach
            [ 47.085272, 7.20377 , 25], //Mörigen
            [ 47.092285, 7.156734, 8], //Twann
            [ 47.13294 , 7.220936, 12], //Vingelz
            [ 47.088311, 7.128925, 9], //Twannberg
            [ 47.124765, 7.234669, 39], //Nidau
            [ 47.055107, 7.07159 , 6]  //lelanderon
        ];
       
    var map2 = L.map('map2').setView([47.085107, 7.13], 12);

    var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map2);


    var idw2 = L.idwLayer(meteoPoints,{
            opacity: 0.3,
            maxZoom: 18,
            cellSize: 1,
            exp: 1,
            max: 50
        }).addTo(map2);

         for (var i=0; i<meteoPoints.length; i++) {
         
            var lat = meteoPoints[i][0];
            var lon = meteoPoints[i][1];
            var popupText = meteoPoints[i][2];
            
             var markerLocation = new L.LatLng(lat, lon);
             var marker = new L.Marker(markerLocation).bindPopup(popupText).addTo(map2);        
         
         }

    var map3 = L.map('map3').setView([47.085107, 7.13], 12);

    var tiles = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
    }).addTo(map3);


    var idw3 = L.idwLayer(meteoPoints,{
            opacity: 0.3,
            maxZoom: 18,
            cellSize: 1,
            exp: 4,
            max: 50
        }).addTo(map3);

         for (var i=0; i<meteoPoints.length; i++) {
         
            var lat = meteoPoints[i][0];
            var lon = meteoPoints[i][1];
            var popupText = meteoPoints[i][2];
            
             var markerLocation = new L.LatLng(lat, lon);
             var marker = new L.Marker(markerLocation).bindPopup(popupText).addTo(map3);        
         
         }

    </script>

17 Responses

  1. Greg
    |

    Hi
    I’m battling to render results with 10,000 + points – browser can’t handle the calculations. Any advice?

    • JoranBeaufort
      |

      Hi! This plugin was made to interpolate only a rather small number of points. This is because everything is calculated on the fly in the browser and for each pixel every point has to be visited to calculate the distance and thus the weighting. What do your 10’000 points represent? For such a density of points you might want to look at a voronoi-tessellation? If you really need IDW, you could cluster the points first (DBSCAN…) and then use the centroids of the clusters as input values for this plugin. Another idea would be to have a look at hosting a GeoServer, which will allow your calculations to run back-end (much faster!) and then you can use leaflet to display the pre-calculated tiles. In addition, if the values of your points do not change, I would recommend calculating the interpolation using a desktop GIS and use leaflet to serve the tiles. Hope this helps?

      • Greg
        |

        Thanks Joran
        I’m trying to use IDW instead of the Leaflet heatmap plugin. We’re using health facility coordinates in South Africa to map HIV/AIDS testing coverage as well as HIV+ test results. The input array (addressPoints) has 3 variables (first 2 are coordinate points). I can reduce the number of repeating coordinates and pass the 3rd value as the count of events at that point – but i’m not sure if your code will support this? Right now i’m repeating coordinates for all cases at that location, hence such high numbers…

        • JoranBeaufort
          |

          Hi Greg. If you have a look at the small documentation on GitHub: https://github.com/JoranBeaufort/Leaflet.idw you will see that the plugin supports 3 values ([lat, lng, intensity]), so you could reduce your amounts of points and add the number as the third argument. On another note, are you sure that inverse distance weighting is needed to map HIV/AIDS coverage? In my opinion, this would call more for a density/hotspot visualisation like a heatmap because the hotspots of known positive test results are of interest. IDW will interpolate your points continuously, which might not be the desired result?

          • Greg
            |

            Hi Joran
            Thank you, I adapted the array as you suggested and I’m getting much better results! Heat maps are contentious in our project – each time i introduce an alternative method for visualizing health outcome (sentiment) a critical voice points out the gaps, e.g. “how do you use smoothing with a heat map”? At the end of the day everything comes back to ArcGIS and how i should be using that instead but I’m pursuing open methods and open source tools.

            Any suggestions for when or when not to use IDW?

          • JoranBeaufort
            |

            Hi, nice to hear your getting better results! OpenSource can be just as powerful as commercial software depending on the task (both have their respective advantages and disadvantages). To the critical voices: I would ask myself what represents the data better and in what direction would you like to steer policy making decisions? An example: If you have two large cities with a large number of HIV/AIDS counts and in between the cities you have uninhabited desert, then IDW will probably show the uninhabited desert as also having a high HIV/AIDS count, where in reality, nothing should show because no-one lives there (potentially leading to drastic over-estimations). A Heatmap on the other hand will show you large hotspots in the cities and nothing in the uninhabited areas (potentially leading to drastic under-estimations). Maybe a combination of the two would be most fitting? In my opinion, IDW is best suited for continuous data with rather low fluctuations for example wind speeds, temperatures, ground water levels, rain fall and so on…

  2. Greg
    |

    Hi Joran
    We work with both aggregated totals (number of people testing HIV+) and indicator percentages (HIV+ tests / HIV tests done). Perhaps IDW is better suited to these indicator values? Ideally i should be researching the use of different GIS methods to suite particular data sets but I don’t have the courage quite yet! I will gladly take advice and feed back my own exploratory tests. Thank you for doing this work on IDW and for responding – it’s opening my eyes to possible research gaps …

    • JoranBeaufort
      |

      Hi Greg! Just wanted to ask how things are going in your project? There are quite a few scientific papers around discussing which interpolation methods are suited for which questions. Another approach which you might be interested in is using PostgreSQL with the PostGIS spatial plugin. If your data is not highly dynamic then using the mentioned database for storing and processing your spatial data might prove to be a perfect way of achieving good performance with a large amount of points! Cheers

  3. songsgroup
    |

    thanks for you code.is any code for openlayers 3?

    • JoranBeaufort
      |

      Hi SongsGroup. Thank you for your comment! There are currently no plans to port the plugin to OpenLayers3. This is mainly because I currently do not have the spare time. But feel free to take the source code and play around with it in OpenLayers and see what you can achieve. Why do you need to use OpenLayers3? Do you want to integrate the plugin into an existing project? Another idea would be to install a PostgreSQL / PostGIS database, generate a IDW raster and send the tiles to your OpenLayers3 project as a tiled overlay (This is more the old school version in which the IDW is not generated on the client dynamically but on the server). Cheers

  4. EPnd
    |

    Hi there,

    Complexity of IDW comes from width x height x control points nested loops. Therefore, probably if you try to achieve 1000 x 1000 with 500 control points your browser will stuck. It is funny to suggest geoserver, postgres etc while providing javascript example. In real life it is hard to find datasets which have only 5-10 control points. I believe your solution can be improved without spending too much effort.

    From checking your code on git I see that you are clearly locking the ui thread. Oh, I just realized that It is being done on every redraw. That means when you zoom in, zoom out or pan; you re-apply idw once more. Instead you could calculate the matrix once and use the same calculated matrix on every redraw. By the way even internet explorer 10 (2012) knows what is “web worker”, so I assume it is safe to use it.

    Your examples are not working with cellsize:1 unfortunately. Or they will work one day if I cannot find a way to close this browser. Because ui thread is locked and it doesn’t allow me to kill these tabs 🙂

    Anyways, good job,

    • JoranBeaufort
      |

      Hi EPnd.

      Thank you for your thoughts. I am aware of the complexity of IDW and yes, the browser cant handle large datasets (which I mention). GeoServer/PostGres were mentioned to provide people who would like to do IDW on larger datasets with further possibilities, this has nothing to do with the plugin but is merely a hint in another direction to solve the IDW problem. I dont think its hard to find real world examples with 5 – 10 control points. In fact, if you reduce the area of interest enough, every dataset can be reduced to a small number of points. And I would also like to point out that this plugin can handle ~100 points fairly well (first example). In regards to the redrawing of the IDW, I know that redrawing is not efficient, but it is very dynamic and the IDW adapts to the scale. This means that if you calculate a fixed raster and zoom in, the raster will eventually become obsolete because the pixels of the raster have a fixed size in real world coordinates. This plugin allows to zoom in and out whilst always having the same pixel size in device coordinates. Another point is that this plugin only calculates the map area which can be seen, how would you handle saving a raster? Saving tiles? And where would you store the raster? In a javascript object? This might also make the browser crash quite soon and you would also have to check on every zoom and pan which areas of the map must be calculated and which areas are already available as a raster. Seeing the great complexity of this, I chose to recalculate on every pan and zoom.

      If you would like to add to the Plugin or implement something you mentioned, that would be great and highly appreciated 🙂

      Cheers

  5. Roland Hansson
    |

    Hi, Thanks for your excellent work.
    I have implemented a tool using this to provide dynamic visualization of statistical variables as described here: https://www.linkedin.com/pulse/simple-web-app-suitability-analysis-location-roland-hansson

    • JoranBeaufort
      |

      Hi Roland
      Thanks for the comment! Your dynamic visualization looks great! What are you using as a backend? NodeJS or pure clientside javascript? If your ok with it, I could link to your app on the Leaflet.IDW GitHub page, as an additional example?
      Cheers

  6. Prads
    |

    Hello,

    Wanted to know if we can use custom colours or classification in this plugin.
    Thanks!

    • JoranBeaufort
      |

      You mean using the “gradient – color gradient config, e.g. {0.4: ‘blue’, 0.65: ‘lime’, 1: ‘red’}” option?

      • Prads
        |

        Apologies, I think I should have mentioned what exactly I wanted.
        I am trying to interpolate data which has Temperature in Celsius.
        There is a range, e.g from -4 to 0, color XXX, from 0 to 4 color YYY and so on.
        So the data is classified and a range has color. I am not getting, how to use this in the plugin.
        Will you please help me?

        Thanks Joran !!