• JavaScript
  • HTML
 * Make clustering of markers with a custom theme
 * Note that the maps clustering module https://js.api.here.com/v3/3.1/mapsjs-clustering.js
 * must be loaded to use the Clustering
 * @param {H.Map} map A HERE Map instance within the application
 * @param {H.ui.UI} ui Default ui component
 * @param {Function} getBubbleContent Function returning detailed information about photo
 * @param {Object[]} data Raw data containing information about each photo
function startClustering(map, ui, getBubbleContent, data) {
  // First we need to create an array of DataPoint objects for the ClusterProvider
  var dataPoints = data.map(function(item) {
    // Note that we pass "null" as value for the "altitude"
    // Last argument is a reference to the original data to associate with our DataPoint
    // We will need it later on when handling events on the clusters/noise points for showing
    // details of that point
    return new H.clustering.DataPoint(item.latitude, item.longitude, null, item);

  // Create a clustering provider with a custom theme
  var clusteredDataProvider = new H.clustering.Provider(dataPoints, {
    clusteringOptions: {
      // Maximum radius of the neighborhood
      eps: 64,
      // minimum weight of points required to form a cluster
      minWeight: 3
    theme: CUSTOM_THEME
  // Note that we attach the event listener to the cluster provider, and not to
  // the individual markers
  clusteredDataProvider.addEventListener('tap', onMarkerClick);

  // Create a layer that will consume objects from our clustering provider
  var layer = new H.map.layer.ObjectLayer(clusteredDataProvider);

  // To make objects from clustering provider visible,
  // we need to add our layer to the map

// Custom clustering theme description object.
// Object should implement H.clustering.ITheme interface
  getClusterPresentation: function(cluster) {
    // Get random DataPoint from our cluster
    var randomDataPoint = getRandomDataPoint(cluster),
      // Get a reference to data object that DataPoint holds
      data = randomDataPoint.getData();

    // Create a marker from a random point in the cluster
    var clusterMarker = new H.map.Marker(cluster.getPosition(), {
      icon: new H.map.Icon(data.thumbnail, {
        size: {w: 50, h: 50},
        anchor: {x: 25, y: 25}

      // Set min/max zoom with values from the cluster,
      // otherwise clusters will be shown at all zoom levels:
      min: cluster.getMinZoom(),
      max: cluster.getMaxZoom()

    // Link data from the random point from the cluster to the marker,
    // to make it accessible inside onMarkerClick

    return clusterMarker;
  getNoisePresentation: function (noisePoint) {
    // Get a reference to data object our noise points
    var data = noisePoint.getData(),
      // Create a marker for the noisePoint
      noiseMarker = new H.map.Marker(noisePoint.getPosition(), {
        // Use min zoom from a noise point
        // to show it correctly at certain zoom levels:
        min: noisePoint.getMinZoom(),
        icon: new H.map.Icon(data.thumbnail, {
          size: {w: 20, h: 20},
          anchor: {x: 10, y: 10}

    // Link a data from the point to the marker
    // to make it accessible inside onMarkerClick

    return noiseMarker;

 * Boilerplate map initialization code starts below:
// Helper function for getting a random point from a cluster object
function getRandomDataPoint(cluster) {
  var dataPoints = [];

  // Iterate through all points which fall into the cluster and store references to them

  // Randomly pick an index from [0, dataPoints.length) range
  // Note how we use bitwise OR ("|") operator for that instead of Math.floor
  return dataPoints[Math.random() * dataPoints.length | 0];

 * CLICK/TAP event handler for our markers. That marker can represent either a single photo or
 * a cluster (group of photos)
 * @param {H.mapevents.Event} e The event object
function onMarkerClick(e) {
  // Get position of the "clicked" marker
  var position = e.target.getGeometry(),
    // Get the data associated with that marker
    data = e.target.getData(),
    // Merge default template with the data and get HTML
    bubbleContent = getBubbleContent(data),
    bubble = onMarkerClick.bubble;

  // For all markers create only one bubble, if not created yet
  if (!bubble) {
    bubble = new H.ui.InfoBubble(position, {
      content: bubbleContent
    // Cache the bubble object
    onMarkerClick.bubble = bubble;
  } else {
    // Reuse existing bubble object

  // Move map's center to a clicked marker
  map.setCenter(position, true);

// Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
  apikey: window.apikey,
  useHTTPS: true
var defaultLayers = platform.createDefaultLayers();

// Step 2: initialize a map
var map = new H.Map(document.getElementById('map'), defaultLayers.vector.normal.map, {
  center: new H.geo.Point(50.426467222414374, 6.3054632497803595),
  zoom: 6,
  pixelRatio: window.devicePixelRatio || 1
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());

// Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

// Step 4: create the default UI component, for displaying bubbles
var ui = H.ui.UI.createDefault(map, defaultLayers);

 * Merges given data with default bubble template and returns resulting HTML string
 * @param {Object} data Data holding single picture information
function getBubbleContent(data) {
  return [
', '', '', '', // Author info may be missing data.author ? ['Photo by: ', '', data.author, ''].join(''):'', '
', '', '', '', 'Photos provided by Wikimedia Commons are
under the copyright of their owners.', '
', '
', '
', '
' ].join(''); } // Step 5: request data that will be visualized on a map startClustering(map, ui, getBubbleContent, photos);
<!DOCTYPE html>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes">
    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
    <title>Marker Clustering with Custom Theme</title>
    <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />
    <link rel="stylesheet" type="text/css" href="demo.css" />
    <link rel="stylesheet" type="text/css" href="styles.css" />
    <link rel="stylesheet" type="text/css" href="../template.css" />
    <script type="text/javascript" src='../test-credentials.js'></script>    
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-clustering.js"></script>
    <script type="text/javascript" src="./data/photos.js"></script>
    <style type="text/css">
      .bubble {
          font-size: 11px;
          line-height: 15px;
          color: white;
      .bubble-image {
          width: 300px;
          height: 100px;
          background-size: cover;
          background-position: center;
          display: block;
      .bubble-logo {
          float: left;
          margin-right: 1em;
          margin-bottom: 4px;
      .bubble-footer {
          display: table;
      .bubble-desc {
          display: table-cell;
          vertical-align: middle;
      .bubble a {
          text-decoration: none;
          color: white !important;
      .bubble a:hover {
          text-decoration: underline;
      .bubble hr {
          margin: 5px 0px;
  <script type="text/javascript" src='../js-examples-rendering-helpers/iframe-height.js'></script></head>
  <body id="markers-on-the-map">
    <div class="page-header">
        <h1>Marker Clustering with Custom Theme</h1>
        <p>Cluster multiple markers and customize the theme</p>
    <p>This example displays a map showing geo-tagged clusters of photographs 
      taken from locations around the world. The <a href="http://commons.wikimedia.org/wiki/Main_Page" target="_blank">Wikimedia Commons</a>
      website was used to provide a source of data of geo-tagged images.</p>
    <div id="map"></div>
    <p>Marker clustering requires the presence of the <code>mapsjs-clustering</code> module of the API. The 
      <code>H.clustering.Provider</code> class is used to load in data points and prepare them for clustering. </p>
    <p>Customizing the default look-and-feel of the <code>clusters</code> and <code>noise</code> points is 
      very easy. When instantiating the <code>H.clustering.Provider</code>, a <code>theme</code> object holding two methods - 
      <code>getClusterPresentation()</code> and <code>getNoisePresentation()</code> - is passed as an additional parameter 
      into the constructor. Both <code>theme</code> methods must return an <code>H.map.Object</code> such as a marker or polygon etc. 
      More information can be found in the API Reference.</p>
    <script type="text/javascript" src='demo.js'></script>