Best Practices

This article outlines a number of best practices aiming to help you use the HERE Maps API for JavaScript in the most efficient way regarding optimal map responsiveness.

Icon Reuse

In Maps API for JavaScript, the H.map.Icon class represents a reusable visual map marker. To optimize map performance, reuse an icon instance in as many marker objects as possible instead of creating a new H.map.Icon instance for each marker.

The code below represents the non-recommended method of creating a new icon object for each new marker:

// Array of anchor points for markers
var points = [...],
    markers = [];

for (var i = 0; i < points.length; i++) {
    markers.push(new H.map.Marker(points[i], {
        // A new identical Icon instance is created at each iteration 
        // of the loop - do not do this:
        icon: new H.map.Icon('graphics/markerHouse.png')
      }));
}

Instead of creating a new icon object for each marker, reuse the single H.map.Icon instance across all H.map.Marker instances as shown below:

// Array of anchor points for markers
var points = [...],
    markers = [],
    // Create single Icon instance
    icon = new H.map.Icon('graphics/markerHouse.png');

for (var i = 0; i < points.length; i++) {
    markers.push(new H.map.Marker(points[i], {
        // Reuse the Icon instance:
        icon: icon
      }));
}

The approach demonstrated above allows achieving better performance and a lower memory footprint.

Note

When you need to display a large number of map markers, consider marker clustering.

Keep "onAttach" and "onDetach" Callbacks Simple

When using DomMarker, you may wish to pass onAttach and onDetach options to the DomIcon constructor to add event listeners for all DomMarkers using the DomIcon instance.

Note that those callbacks are called each time the DomMarker appears on the map or disappears from the map viewport. For this reason, we recommend not to perform any expensive computations in those callbacks, otherwise, map panning and zooming performance may noticeably degrade.

The example below shows the non-recommended approach that causes such performance degradation:

// Assume that domElement is an already defined DOM element
// and contains a large number of children:
var domIcon = new H.map.DomIcon(domElement, {
    onAttach: function(clonedElement, domIcon, domMarker) {
        // Avoid expensive computation inside onAttach 
        // Such as quering the DOM:
        var defaultButton = clonedElement.querySelector('.btn-default');
        var infoButton = clonedElement.querySelector('.btn-info');

        defaultButton.addEventListener('click', onDefaultClick);
        infoButton.addEventListener('click', onInfoClick);
    },
    onDetach: function(clonedElement, domIcon, domMarker) {
        // Avoid expensive computation inside onDetach
        // such as quering the DOM
        var defaultButton = clonedElement.querySelector('.btn-default');
        var infoButton = clonedElement.querySelector('.btn-info');

        defaultButton.removeEventListener('click', onDefaultClick);
        infoButton.removeEventListener('click', onInfoClick);
    }
});

Make sure you compute all the required assets beforehand or offload the computation from onAttach()/onDetach() to event callbacks by using techniques such as event delegation demonstrated in the code below:

// Use event bubbling to handle all events on the parent node itself,
// and perform computations only when needed.
function onMarkerClick(e) {
    var targetClassName = e.target.className;
    if (targetClassName  === 'btn-default') {
        onDefaultClick(e);
    } else if (targetClassName  === 'btn-info') {
        onInfoClick(e);
    }
}

var domIcon = new H.map.DomIcon(domElement, {
    onAttach: function(clonedElement, domIcon, domMarker) {
        // Simply subscribe to the event:
        domElement.addEventListener('click', onMarkerClick);
    },
    onDetach: function(clonedElement, domIcon, domMarker) {
        // Simply unsubscribe from the event:
        domElement.removeEventListener('click', onMarkerClick);
    }
});

Use "mapviewchangeend" Event rather than "mapviewchange"

The events mapviewchange and mapviewchangeend seem similar – both can be used for handling changes to the map viewport – but their effect is different. mapviewchange is called on each animation step in the process of changing a state of the viewport. In many cases, this operation is excessive. For example, updating a UI element with the current zoom level multiple times each time the user double-clicks on the map is inefficient.

// Displays current zoom level to the end user:
function displayZoomLevel() {...}

// A listener updates the map zoom level on each map view change
// -- this occurs more than 20 times on a double-click on the map, 
// inefficient and to be avoided:
map.addEventListener('mapviewchange', function () {
    var zoom = map.getZoom();

    // This function is called more than 20 times on a double-click on the map!
    displayZoomLevel(zoom);
});

mapviewchangeend, on the other hand, is called only once, when the viewport change is complete, therefore, we recommend using this event to update the map viewport:

/* 
    Displays current zoom level to the end user 
 */
function displayZoomLevel() {...}

// A listener updates the map zoom level -- it is called once when the map 
// view change is complete.
map.addEventListener('mapviewchangeend', function () {
    var zoom = map.getZoom();

    // The function that displays the zoom level is called only once, 
    // after zoom level changed
    displayZoomLevel(zoom);
});

The mapviewchangeend event is a 'debounced' version of mapviewchange, so, as a rule, it is more efficient to use mapviewchangeend.

Use Built-in Animation Capabilities

If you need to animate a transition between different map states, for example, a transition resulting from a map center change, a zoom change or both, always try to use built-in animation capabilities of the map engine.

Map instance methods such as setZoom(), setCenter() and setViewBounds() accept a second argument indicating whether an animated transition should be applied.

We recommend not to create your own zoom animations as shown in the snippet below:

// Assumption: 'map' is initialized and available.
var currentZoom = map.getZoom();
var endZoom = currentZoom + 3;

// Update zoom level on each animation frame,
// till we reach endZoom:
function step() {
    currentZoom += 0.05;
    map.setZoom(currentZoom);

    (currentZoom < endZoom) && requestAnimationFrame(step);
}

// Start zoom animation
step();

Instead, call the method setZoom() with an optional second parameter to perform an animation:

/**
 * Assumption: 'map' is initialized and available
 */
// Call getZoom() with an optional second parameter,
// indicating that an animation is to be performed:
map.setZoom(map.getZoom() + 3, true);

Use "SpatialStyle" Instances as Read-Only Objects

Instances of SpatialStyle are intended to be used as read-only objects. Modifying their properties is not safe and may lead to visual inconsistencies.

The snippet below shows an example of reusing SpatialStyle of one Spatial object on another. This operation is valid as it does not modify the SpatialStyle object.

// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.setStyle();

// Reusing an existing SpatialStyle object is fine,
// because we are not modifying it:
spatial2.setStyle(style);

If you want to reuse a style from one Spatial object on another, but with a single fillColor modification, modifying an object returned by getStyle method can have non-deterministic result on a visual representation of the first spatial:

// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.getStyle();

// Modification of a SpatialStyle object -- never do this
style.fillColor = 'red';

// If we now apply the modified style to the second spatial, the results may be
// inconsistent -- sometimes the fillColor change takes effect, but not always!
spatial2.setStyle(style);

The correct way to change the style is to create a new SpatialStyle object or to use the getCopy() method on an existing object as shown in the following code example:

/**
 * Assumption: 'spatial1' and 'spatial2' are initialized and available.
 */
// Get the current style of the first spatial and get an extended version
// by calling getCopy() method on it
var style = spatial1.setStyle().getCopy({
    fillColor: 'red'
});

// Now changing the style of the second class is completely safe and 
// produces the expected results consistently:
spatial2.setStyle(style);

Thus, the general rule is to reuse an existing SpatialStyle object multiple times, but if you need to modify it, use getCopy() or construct a new one.

results matching ""

    No results matching ""