Com And Hosting

Leaflet JS is a very popular lightweight js library for displaying data in a map and creating an interactive map. It is quite flexible in terms to adding custom layers, styling controls, and integrating events. In this post, I will try to demonstrate how it can be integrated with your MVC application instead using Google map API. I will also integrate the clustering since, in my application, there is a possibility to have multiple records with the same latitude and longitude. I guess it will be easier to understand if you have some basic knowledge on map marker clustering. You can read more about map clustering at https://developers.google.com/maps/documentation/javascript/marker-clustering .

The finished product will be like the image below –

map_1

Setup Model

In the MVC application, I have created a model which holds the GPS coordinates along with other fields. Just to make it simple, I have omitted unnecessary codes to make it simple. Here is my model.

01namespace my_app.Models
02{
03    public class Location
04    {
05        [Key]
06        public int LocId { get; set; }
07 
08      [Display(Name = "Description"), MaxLength(8000, ErrorMessage = "Title cannot be more than 8000 characters long."), Required(ErrorMessage ="You must enter a description in {0} field")]
09        [AllowHtml]
10        [DataType(DataType.MultilineText)]
11        public string Description { get; set; }      
12 
13        [Display(Name = "Park/Location Name"), MaxLength(120, ErrorMessage = "Park or Location name is too long"), Required]
14        public string Name { get; set; }
15         
16        [DisplayFormat(DataFormatString = "{0:N10}", ApplyFormatInEditMode = true), Required]
17        public decimal Longitude { get; set; }
18 
19        [DisplayFormat(DataFormatString = "{0:N10}", ApplyFormatInEditMode = true), Required]
20        public decimal Latitude { get; set; }
21                 
22       //other codes omitted
23        
24    }
25}

[note title=”Optional Title”]In my application I have another model which links with this model. Basically, I am using a relational database. But just to make this demonstration simple, I am just using the relevant model. [/note]

Setup Controller

In my controller, I am using a get request to fetch data in JSON format so that I can bind them in the Leaflet map. Here is the sample code of the controller. The Map action result produces the JSON data which is accessible through GET request.

1// GET: Locations
2        [HttpGet]
3        public ActionResult map()
4        {
1var q = (from a in db.Locations
2select new { a.Title, a.Description, a.Latitude, a.Longitude, a.Name, a.alertType.IconUrl })
3.OrderBy(a=>a.Name);
4// return PartialView("_map", q.ToList());
5return Json(q, JsonRequestBehavior.AllowGet);
6}

map_2

I have used Linq query in the above example however you can use simply EF instead like below.

1// GET: Locations
2        [HttpGet]
3        public ActionResult map()
4        {
5            
6            var q= db.Alerts.Where(a => a.Published == "Yes").ToList();
7//return the result in json
8            return Json(q, JsonRequestBehavior.AllowGet);
9        }

 

Setup View

Again, I have omitted irrelevant codes from the view. I am interested in the map view section of the page where the Map will be loaded through ajax call. The basic HTML code has been used in the bootstrap tab. In the below code block, you might have noticed an overlay block which has been used to show loading indicator to end user while fetching the data from the remote server. It will hide through CSS class switcher once data has been successfully loaded.

 

01<div class="tab-pane fade" id="mapview">
02 
03           <br />
04           
05           <div class="loadingOverlay">
06               <div class="loading-spinner">
07                   <img src="~/img/googleballs.gif" title="Loading" />
08                   <span class="loading-text">Loading...</span>
09               </div>
10           </div>
11           <!-- This is the div that will contain the Google Map -->
12           <div id="lmap" style="width:100%; height:600px"></div>
13       </div>

 

In the header section, let’s call the all associated plugins. Remember you must require jQuery and Bootstrap js libraries and style sheets to work with this demo.

2<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.2/dist/leaflet.css" />
4 
5<script src='~/Scripts/Leaflet.GoogleMutant.js'></script>
6<script src="~/Scripts/leaflet.markercluster-src.js"></script>
7<link href="~/Content/MarkerCluster.css" rel="stylesheet" />
8<link href="~/Content/MarkerCluster.Default.css" rel="stylesheet" />

I have included Google API key since I am using Google layer instead of  OpenStreet map in this example. Same output of the json data.

json_data

The idea is to load the map only when a user clicks on the map tab. I don’t want to load all the content on initial page load since it can increase the payload and user might have to wait longer to load the page. To give better user experience, the page will load data on demand means when user requests for the additional data.

001$(function(){
002 
003        var latlng = L.latLng(-30.81881, 116.16596);
004        var map = L.map('lmap', { center: latlng, zoom: 6 });
005        var lcontrol = new L.control.layers();
006         
007        //clear map first
008        clearMap();
009        //resize the map
010        map.invalidateSize(true);
011        //load the map once all layers cleared
012        loadMap();
013        //reset the map size on dom ready
014        map.invalidateSize(true);
015        function loadMap() {
016 
017            $(".loadingOverlay").show();
018 
019            var roadMutant = L.gridLayer.googleMutant({
020                type: 'roadmap' // valid values are 'roadmap', 'satellite', 'terrain' and 'hybrid'
021            }).addTo(map);
022            var satMutant = L.gridLayer.googleMutant({
023                maxZoom: 24,
024                type: 'satellite'
025            });
026 
027            var terrainMutant = L.gridLayer.googleMutant({
028                maxZoom: 24,
029                type: 'terrain'
030            });
031 
032            var hybridMutant = L.gridLayer.googleMutant({
033                maxZoom: 24,
034                type: 'hybrid'
035            });
036 
037            //add the control on the map
038             
039           lcontrol= L.control.layers({
040                Roadmap: roadMutant,
041                Aerial: satMutant,
042                Terrain: terrainMutant,
043                Hybrid: hybridMutant //,Styles: styleMutant
044 
045            }, {}, {
046                collapsed: false
047            }).addTo(map);
048 
049         
050        var markers = L.markerClusterGroup({ chunkedLoading: true, spiderfyOnMaxZoom: true, maxClusterRadius: 80 });
051 
052         
053        //clear markers and remove all layers
054        markers.clearLayers();
055         
056         
057 
058        $.ajax({
059            type: "GET",
060            url: '@Url.Action("map", "Home")',
061           // data: {'atype': st},
062            dataType: 'json',
063            contentType: 'application/x-www-form-urlencoded',
064            success: function (data) {
065 
066                $.each(data, function (i, item) {
067                    var img = (item.IconUrl).replace("~", "");
068                    var dpawIcon = L.icon({ iconUrl: img, iconSize: [42, 42] });
069 
070                    var marker = L.marker(L.latLng(item.Latitude, item.Longitude), { icon: dpawIcon }, { title: item.Name });
071                    var content = "<div class='infoDiv'><h3><img src='" + appUrl + img + "' width='24' />" + item.Name + "</h3><p>" + item.Title + "</p><a href='#' data-value='" + item.AlertId + "' class='btn btn-success btn-sm alertInfo' data-toggle='modal' data-target='#alertDetails'>Details</a></div>";
072                    marker.bindPopup(content);
073                    markers.addLayer(marker);
074 
075                });
076 
077 
078            }
079 
080        })
081       .done(function () {
082           $(".loadingOverlay").hide();
083           map.invalidateSize(true);
084       });
085 
086        //add the markers layer to the map
087        map.addLayer(markers);
088 
089         
090        }
091 
092        
093 
094    $('#gmap').on('shown.bs.tab', function (e) {
095        //clear map first
096        clearMap();
097        //resize the map
098       map.invalidateSize(true);
099       //load the map once all layers cleared
100       loadMap();
101    })
102 
103    //this function to clear the map before another ajax call
104    function clearMap()
105    {
106        // clear all layers before it reloads;
107        map.eachLayer(function (layer) {
108            map.removeLayer(layer);
109        });
110        map.removeControl(lcontrol);
111        map.removeControl(eb);
112    }
113 
114    map.on('focus', function () { map.scrollWheelZoom.enable(); });
115    map.on('blur', function () { map.scrollWheelZoom.disable(); });
116});

Place the above codes inside the script block. Remember  I have not shown the jQuery and Bootstrap js files in the above script reference since I have added them in the layout file.

1<script type="text/javascript">
2//place your code here
3 
4</script>

In the above javascript, I have to create another function to clear the map. It is a bit tricky to display map inside a bootstrap tab or Modal. If you are using this map in a single page then you don’t have to do this. You can clean up your code.

map_3

 

 

7 thought on “Leafletjs interactive map and clustering with ASP.NET MVC 5”
  1. Excellent article, just what I need.

    Could you please the visual studio solution source code available for this example?

    1. Hi Ernest,
      This is part of big project. I hope I have explained in details on my post so that you can build it by yourself. Let me know if you struggle at any stage.

      Good luck.

  2. Hi, dear sir.
    I’m in Vietnam, I’m not an IT person. But I want to make a website about maps myself, to track the changes of my devices located at various points.
    I’m very impressed with your article, but I haven’t been able to implement myself how to load data from SQL partial to web according to menu options. Also how to update data in real time.
    Hope you can give me some sample code, so I can do it myself.
    Thank you very much.

Leave a Reply to praveen Cancel reply

Your email address will not be published. Required fields are marked *