Bringing a Marker to Front in Leaflet

January 23rd 2016 Leaflet AngularJS

Leaflet is an open-source JavaScript library for interactive maps. It's very feature rich, but its sometimes hard to find resources on approaches that deviate from the most common ones. When handling many markers in a small region, clustering is the go-to solution, but certain scenarios can require every markers to be rendered all the time. Leaflet has features to keep individual markers easily accessible nevertheless.

Let's start with a couple of markers rendered in close vicinity:

var app = angular.module('demoapp', ['leaflet-directive']);
app.controller('MapController', ['$scope', function($scope) {
    $scope.center = {
        lat: 46.119944,
        lng: 14.815333,
        zoom: 8
    };
    $scope.markers = {
        jesenice: {
            lat: 46.4367,
            lng: 14.0526,
            label: {
                message: 'Jesenice',
                options: {
                    noHide: true
                }
            }
        },
        hrusica: {
            lat: 46.4489,
            lng: 14.0113,
            label: {
                message: 'Hrušica',
                options: {
                    noHide: true
                }
            }
        },
        koroskaBela: {
            lat: 46.4262,
            lng: 14.0988,
            label: {
                message: 'Koroška Bela',
                options: {
                    noHide: true
                }
            }
        },
        blejskaDobrava: {
            lat: 46.4077,
            lng: 14.0989,
            label: {
                message: 'Blejska Dobrava',
                options: {
                    noHide: true
                }
            }
        }
    };
}]);

Here's the hosting HTML:

<!DOCTYPE html>
<html lang="en" ng-app="demoapp">
<head>
    <meta charset="UTF-8">
    <title>Leaflet Bring to Front</title>
    <script src="../bower_components/angular/angular.min.js"></script>
    <script src="../bower_components/leaflet/dist/leaflet.js"></script>
    <script src="../bower_components/Leaflet.label/dist/leaflet.label.js"></script>
    <script src="../bower_components/angular-leaflet-directive/dist/angular-leaflet-directive.min.js"></script>
    <script src="scripts/MapController.js"></script>
    <link rel="stylesheet" href="../bower_components/leaflet/dist/leaflet.css">
    <link rel="stylesheet" href="../bower_components/Leaflet.label/dist/leaflet.label.css">
</head>
<body ng-controller="MapController">
    <leaflet lf-center="center" markers="markers" width="100%" height="480px"></leaflet>
</body>
</html>

As you've probably noticed, I'm using Angular Leaflet Directive and taking advantage of Leaflet.label plugin.

At the initial zoom level, the markers are heavily ovelapping, making all but the top label impossible to read. The simplest way to improve the experience is by enabling the riseOnHover option for the marker. This will bring the marker and its label to front when the mouse is over it. Adding the option to each of the markers is enough to make it work:

jesenice: {
    lat: 46.4367,
    lng: 14.0526,
    riseOnHover: true,
    label: {
        message: 'Jesenice',
        options: {
            noHide: true
        }
    }
}

As soon as you move the mouse away, the marker moves back to its original place. To implement the concept of a selected marker that's always rendered on top of the others, zIndexOffset option can be used. Every time the user clicks on marker, this makes it the selected one. By implementing an AngularJS $watch, zIndexOffset value for the marker can be changed every time the selected marker changes:

$scope.selectedMarker = null;

$scope.$on('leafletDirectiveMarker.click', function(event, args) {
    $scope.selectedMarker = args.modelName;
})

$scope.$watch('selectedMarker', function(newVal, oldVal) {
    if (oldVal !== null) {
        $scope.markers[oldVal].zIndexOffset = 0;
    }
    if (newVal !== null) {
        $scope.markers[newVal].zIndexOffset = 100;
    }
});

I'm using the not all that well documented events feature of Angular Leaflet directive to detect clicks on markers. The zIndexOffset value should be large enough to bring any marker in front of all the others. Keep in mind that each marker has its own offset value set by Leaflet based on its latitude, i.e. offset must be larger than the total number of markers.

If that's not enough, labels can also be made clickable, so that in their event handler the corresponding marker gets selected. Since I couldn't find any property identifying the marker in the event, I had to add the marker name to the label options:

jesenice: {
    lat: 46.4367,
    lng: 14.0526,
    riseOnHover: true,
    label: {
        message: 'Jesenice',
        options: {
            clickable: true,
            noHide: true,
            markerName: 'jesenice'
        }
    }
},

All of the label options are passed into the event handler, so this value can be retrieved:

$scope.$on('leafletDirectiveLabel.click', function(event, args) {
    $scope.selectedMarker = args.leafletObject.options.markerName;
})

This makes the solution useable enough. You can try it out yourself in the embeded Plunk:

Copyright
Creative Commons License