Maps API for JavaScript Developer's Guide

Best Practices

The article outlines a number best-practises aiming to help you use the HERE Maps API for JavaScript in the most efficient way and implement the optimal map responsiveness.

Icon Reuse

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 following code creates a new icon object for each new marker – this is the practise to avoid:
// 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')
    }));
}
A more efficient way to accomplish the same task is to reuse the single H.map.Icon instance across all H.map.Marker instances as shown in this code:
// 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 offers 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 utilizing the DomIcon instance.

Keep in mind that those callbacks are called each time the DomMarker appears on the map or disappears from the map viewport. For this reason, it is crucial not to perform any expensive computations in those callbacks, otherwise map panning and zooming performance may noticeably degrade. The example below uses code that causes such performance degradation – this is the practise to avoid:
// 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 this code:
// 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);
  }
});

Prefer mapviewchangeend Event to mapviewchange

The events mapviewchange and mapviewchangeend seem similar – both can be used for handling changes to the map viewport – but they are not the same. mapviewchange is called on each animation step in the process of changing a state of the viewport. In many cases, this appears to be excessive. For example, updating a UI element with the current zoom level twenty 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 it is a better event on which to implement an update:
/* 
  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);
});

mapviewchangeend event is a 'debounced' version of the 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 in the map engine.

Map instance methods such as setZoom(), setCenter() and setViewBounds() all accept a second argument, which indicates weather animated transition should be applied.

So instead of animating zoom change on your own as in this code:
// 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();

... call the method setZoom() with an optional second parameter to perform an animation as demonstrated by the code below:
/**
 * 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);

SpatialStyle Is Read Only

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

Consider an example of reusing SpatialStyle of one Spatial object on another, which is absolutely valid, because we do not modify it.
// 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);
Now lets imagine that we want to reuse a style from one Spatial 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 is to change the style, is to create a new SpatialStyle object or to use the method getCopy() method on an existing object as shown in this 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);

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