DroidOS
DroidOS

Reputation: 8890

How to customize HERE Maps Clusters

In my current Hybrid app I am in the process of switching from using Leaflet and its Clusterer plugin to using the HERE Maps v3 JavaScript API. The HERE Maps documentation can be rather dense at times for someone used to the clarity of Leaflet documentation. Nevertheless the process of using HERE is fairly straightforward. However, there is one Leaflet feature that I really miss:

In Leaflet when you add markers to a cluster it is possible to assign custom map pins to each marker. Further, you can quite easily customize the pin used to represent the cluster itself. In HERE the documentation indicates the following

 var dataPoints = [];
 //create an array for the clustered marker datapoints

 dataPoints.push(new H.clustering.DataPoint(43.25539364396839, -79.07150530321474));
 dataPoints.push(new H.clustering.DataPoint(43.255434408174246, -79.07175552759227));    
 dataPoints.push(new H.clustering.DataPoint(43.25557588373579, -79.07203209137799));
 dataPoints.push(new H.clustering.DataPoint(43.25567419706804, -79.07218354297491));  
 //populate that array

 var clusteredDataProvider = new H.clustering.Provider(dataPoints);
 //create a cluster data provider and assign it the freshly created data points

 var layer = new H.map.layer.ObjectLayer(clusteredDataProvider);
  //create a new layer that uses this provider

 map.addLayer(layer);
 //inject this layer into the map

This works. However, it leaves me with three unanswered questions

Upvotes: 2

Views: 2365

Answers (1)

user3505695
user3505695

Reputation:

Clusters and noise points are represented on the map with markers. Unless otherwise configured, the clustering provider uses the default bitmap markers theme with weight information to display clusters and noise points on the map. You can define your own custom theme and pass it to the provider as the theme property.

Read please this docu on https://developer.here.com/documentation/maps/3.1.22.0/dev_guide/topics/clustering.html

A custom theme is defined in an object that implements the interface H.clustering.ITheme as demonstrated by the code below.

    /**
 * 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
  map.addLayer(layer);
}

// Custom clustering theme description object.
// Object should implement H.clustering.ITheme interface
var CUSTOM_THEME = {
  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
    clusterMarker.setData(data);

    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
    noiseMarker.setData(data);

    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
  cluster.forEachDataPoint(dataPoints.push.bind(dataPoints));

  // 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
    });
    ui.addBubble(bubble);
    // Cache the bubble object
    onMarkerClick.bubble = bubble;
  } else {
    // Reuse existing bubble object
    bubble.setPosition(position);
    bubble.setContent(bubbleContent);
    bubble.open();
  }

  // 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
});
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 [
    '<div class="bubble">',
      '<a class="bubble-image" ',
        'style="background-image: url(', data.fullurl, ')" ',
        'href="', data.url, '" target="_blank">',
      '</a>',
      '<span>',
        // Author info may be missing
        data.author ? ['Photo by: ', '<a href="//commons.wikimedia.org/wiki/User:',
          encodeURIComponent(data.author), '" target="_blank">',
          data.author, '</a>'].join(''):'',
        '<hr/>',
        '<a class="bubble-footer" href="//commons.wikimedia.org/" target="_blank">',
          '<img class="bubble-logo" src="data/wikimedia-logo.png" width="20" height="20" />',
          '<span class="bubble-desc">',
          'Photos provided by Wikimedia Commons are <br/>under the copyright of their owners.',
          '</span>',
        '</a>',
      '</span>',
    '</div>'
  ].join('');
}

// Step 5: request data that will be visualized on a map
startClustering(map, ui, getBubbleContent, photos);

This worked example try please on JsFiddle: https://jsfiddle.net/gh/get/jquery/2.1.0/heremaps/maps-api-for-javascript-examples/tree/master/custom-cluster-theme

The data for this example loaded from https://heremaps.github.io/maps-api-for-javascript-examples/custom-cluster-theme/data/photos.js

How the Cluster icon to "explode" when tapped you can see sources of this example https://tcs.ext.here.com/examples/v3.1/cluster_marker_spider . Note please on loaded additional js module "ClusterMarkerSpider-..."

How zoom-in to "exploded" of the clustered map pins - note please on "forEachDataPoint" (in Spider example) there are created all Markers which you can simple push to some H.map.Group and after zoom-in to this group by its bounding box.

Upvotes: 2

Related Questions