0

I have been using ngMap with my angularjs code for displaying markers. However, with more than 100 markers I have noticed that there is a considerable decrease in performance mainly related to ng-repeat and two way binding. I would like to add markers with custom HTML elements similar to CustomMarker but using ordinary Markers and modified from controller when required.

Challenges faced :

  1. I have SVG images which need to be dynamically coloured based on the conditions (These SVGs are not single path ones, hence doesn't seem to work well when I used it with Symbol)
  2. These are vehicle markers and hence should support rotation
jose
  • 143
  • 2
  • 13

1 Answers1

1

I have solved this by creating CustomMarker with Overlay and then adding the markers that are only present in the current map bounds to the map so that map doesn't lag.

Below is the code snippet with which I achieved it.

createCustomMarkerComponent();

/**
 * options  : [Object]  : options to be passed on
 *                                          -   position    : [Object]  : Google maps latLng object
 *                                          -   map             : [Object]  : Google maps instance
 *                                          -   markerId    : [String]  : Marker id
 *                                          - innerHTML : [String]  : innerHTML string for the marker
 **/
function CustomMarker(options) {
    var self = this;

    self.options = options || {};

    self.el = document.createElement('div');
    self.el.style.display = 'block';
    self.el.style.visibility = 'hidden';

    self.visible = true;
    self.display = false;

    for (var key in options) {
        self[key] = options[key];
    }

    self.setContent();

    google.maps.event.addListener(self.options.map, "idle", function (event) {
        //This is the current user-viewable region of the map
        var bounds = self.options.map.getBounds();
        checkElementVisibility(self, bounds);
    });

    if (this.options.onClick) {
        google.maps.event.addDomListener(this.el, "click", this.options.onClick);
    }

}

function checkElementVisibility(item, bounds) {
    //checks if marker is within viewport and displays the marker accordingly - triggered by google.maps.event "idle" on the map Object
    if (bounds.contains(item.position)) {
        //If the item isn't already being displayed
        if (item.display != true) {
            item.display = true;
            item.setMap(item.options.map);
        }
    } else {
        item.display = false;
        item.setMap(null);
    }
}

var supportedTransform = (function getSupportedTransform() {
    var prefixes = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' ');
    var div = document.createElement('div');
    for (var i = 0; i < prefixes.length; i++) {
        if (div && div.style[prefixes[i]] !== undefined) {
            return prefixes[i];
        }
    }
    return false;
})();


function createCustomMarkerComponent() {

    if (window.google) {

        CustomMarker.prototype = new google.maps.OverlayView();

        CustomMarker.prototype.setContent = function () {
            this.el.innerHTML = this.innerHTML;
            this.el.style.position = 'absolute';
            this.el.style.cursor = 'pointer';
            this.el.style.top = 0;
            this.el.style.left = 0;
        };

        CustomMarker.prototype.getPosition = function () {
            return this.position;
        };

        CustomMarker.prototype.getDraggable = function () {
            return this.draggable;
        };

        CustomMarker.prototype.setDraggable = function (draggable) {
            this.draggable = draggable;
        };

        CustomMarker.prototype.setPosition = function (position) {
            var self = this;
            return new Promise(function () {
                position && (self.position = position); /* jshint ignore:line */
                if (self.getProjection() && typeof self.position.lng == 'function') {
                    var setPosition = function () {
                        if (!self.getProjection()) {
                            return;
                        }
                        var posPixel = self.getProjection().fromLatLngToDivPixel(self.position);
                        var x = Math.round(posPixel.x - (self.el.offsetWidth / 2));
                        var y = Math.round(posPixel.y - self.el.offsetHeight + 10); // 10px for anchor; 18px for label if not position-absolute
                        if (supportedTransform) {
                            self.el.style[supportedTransform] = "translate(" + x + "px, " + y + "px)";
                        } else {
                            self.el.style.left = x + "px";
                            self.el.style.top = y + "px";
                        }
                        self.el.style.visibility = "visible";
                    };
                    if (self.el.offsetWidth && self.el.offsetHeight) {
                        setPosition();
                    } else {
                        //delayed left/top calculation when width/height are not set instantly
                        setTimeout(setPosition, 300);
                    }
                }
            });
        };

        CustomMarker.prototype.setZIndex = function (zIndex) {
            if (zIndex === undefined) return;
            (this.zIndex !== zIndex) && (this.zIndex = zIndex); /* jshint ignore:line */
            (this.el.style.zIndex !== this.zIndex) && (this.el.style.zIndex = this.zIndex);
        };

        CustomMarker.prototype.getVisible = function () {
            return this.visible;
        };

        CustomMarker.prototype.setVisible = function (visible) {
            if (this.el.style.display === 'none' && visible) {
                this.el.style.display = 'block';
            } else if (this.el.style.display !== 'none' && !visible) {
                this.el.style.display = 'none';
            }
            this.visible = visible;
        };

        CustomMarker.prototype.addClass = function (className) {
            var classNames = this.el.className.trim().split(' ');
            (classNames.indexOf(className) == -1) && classNames.push(className); /* jshint ignore:line */
            this.el.className = classNames.join(' ');
        };

        CustomMarker.prototype.removeClass = function (className) {
            var classNames = this.el.className.split(' ');
            var index = classNames.indexOf(className);
            (index > -1) && classNames.splice(index, 1); /* jshint ignore:line */
            this.el.className = classNames.join(' ');
        };

        CustomMarker.prototype.onAdd = function () {
            this.getPanes().overlayMouseTarget.appendChild(this.el);
            // this.getPanes().markerLayer.appendChild(label-div); // ??

        };

        CustomMarker.prototype.draw = function () {
            this.setPosition();
            this.setZIndex(this.zIndex);
            this.setVisible(this.visible);
        };

        CustomMarker.prototype.onRemove = function () {
            this.el.parentNode.removeChild(this.el);
            // this.el = null;
        };

    } else {
        setTimeout(createCustomMarkerComponent, 200);
    }

}

The checkElementVisibility function helps in identifying whether a marker should appear or not.

In case there are better solutions please add it here.Thanks!

jose
  • 143
  • 2
  • 13