Javascript Map with Popup and Marker Cluster Using OpenLayers – How to Implement

geodjangojavascriptopenlayers

I'm trying to create a map with marker cluster and popup using Openlayers 5.
I'm be able to show a popup on click as you can see below
enter image description here
but when I try to use marker cluster the contents are no longer available.
enter image description here
The popup's contents are handed out using a GeoJson serialized by GeoDjango.

Here the code of working solution:

var source_url = '{% url 'illegal_dump_geojson' %}'

var vectors = new ol.layer.Vector({
  source: new ol.source.Vector({
    url: source_url,
    format: new ol.format.GeoJSON(),
  }),
});

var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');

overlay = new ol.Overlay({
  element: container,
  autoPan: true,
  autoPanAnimation: {
    duration: 250
  }
});

 closer.onclick = function() {
  overlay.setPosition(undefined);
  closer.blur();
  return false;
};

var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectors,
  ],
  view: new ol.View({
    center: [0, 0],
    zoom: 2
  }),
  overlays: [overlay],
});

var feature_onClick;
map.on('click', function(evt) {

    feature_onClick = map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
        return feature;
      });

  if (feature_onClick) {
    overlay.setPosition(evt.coordinate);
     content.innerHTML =
     '<h4 class="text-center"> Report n.'+ feature_onClick.getProperties().pk + '</h4>'
     + '<hr>'
     + '<p class="text-justify">' + feature_onClick.getProperties().description + '</p>'
     + '<p class="">' + feature_onClick.getProperties().volume_extension + ' mc</p>'
     + '<a class="btn btn-outline-warning" href="/geomonitor/illegal-dump/report-'+ feature_onClick.getProperties().pk + '/">Read more..</a>'
     + '<hr>'
     + '<p class="text-right py-0 my-0"><small class="text-muted"> posted by <strong>' + feature_onClick.getProperties().user_name + '</strong></small></p>'
     + '<p class="text-right py-0 my-0"><small class="text-muted"> at <em>' + feature_onClick.getProperties().publishing_date + '</em></small></p>'
     }
});

This is the solution that didn't work:

var source_url = '{% url 'illegal_dump_geojson' %}'

var clusterSource = new ol.source.Cluster({
      source: new ol.source.Vector({
        url: source_url,
        format: new ol.format.GeoJSON(),
      }),
      distance: 100,
  });

var styleCache = {};

var vectors = new ol.layer.Vector({
  source: clusterSource,
  style: function(feature) {
    var size = feature.get('features').length;
    var style = styleCache[size];
    if (!style) {
      style = new ol.style.Style({
        image: new ol.style.Circle({
          radius: 15,
          stroke: new ol.style.Stroke({
            color: 'rgba(200,200,200,1.0)',
            width: 5,
          }),
          fill: new ol.style.Fill({
            color: 'rgba(255,0,0,1.0)'
          })
        }),
        text: new ol.style.Text({
          text: size.toString(),
          fill: new ol.style.Fill({
            color: '#000'
          })
        })
      });
      styleCache[size] = style;
    }
    return style;
  }
});

var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');

overlay = new ol.Overlay({
  element: container,
  autoPan: true,
  autoPanAnimation: {
    duration: 250
  }
});

 closer.onclick = function() {
  overlay.setPosition(undefined);
  closer.blur();
  return false;
};

var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    }),
    vectors,
  ],
  view: new ol.View({
    center: [0, 0],
    zoom: 2
  }),
  overlays: [overlay],
});

var feature_onClick;
map.on('click', function(evt) {

    feature_onClick = map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
        return feature;
      });

  if (feature_onClick) {
    overlay.setPosition(evt.coordinate);
     content.innerHTML =
     '<h4 class="text-center"> Report n.'+ feature_onClick.getProperties().pk + '</h4>'
     + '<hr>'
     + '<p class="text-justify">' + feature_onClick.getProperties().description + '</p>'
     + '<p class="">' + feature_onClick.getProperties().volume_extension + ' mc</p>'
     + '<a class="btn btn-outline-warning" href="/geomonitor/illegal-dump/report-'+ feature_onClick.getProperties().pk + '/">Read more..</a>'
     + '<hr>'
     + '<p class="text-right py-0 my-0"><small class="text-muted"> posted by <strong>' + feature_onClick.getProperties().user_name + '</strong></small></p>'
     + '<p class="text-right py-0 my-0"><small class="text-muted"> at <em>' + feature_onClick.getProperties().publishing_date + '</em></small></p>'
     }
});

.

Best Answer

A cluster is an auto-created and doesn't have those properties, instead it has an array of real features making up the cluster. This would return the first feature in the cluster if you clicked on one:

feature_onClick = map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
    var features = feature.get('features');
    if (features) {
      return features[0];
    }
    return feature;
  });