Repository: ResonantGeoData/ResonantGeoData

Issue:
Heat map to show all data in the catalog
We should add a new heatmap page to show a heat map of all of the data in the catalog.

It would be nice to also show this heatmap during the search, but I'm not sure how best to handle that since we do want the footprints of the paginated search results to show up too


Discussion:
We did a heatmap in a Girder 3 plugin (https://github.com/girder/girder-geo-heatmap).  If memory serves, for each geospatial entry, it computed the average of the boundary points and used that point on the heatmap.  It was slow because it fetched all of the data via the client (no server-side request to get the points).
Thanks, David! this is really useful!

I think I am also going to include other broad stats about the data catalog (such at temporal ranges, number of each type of entry, etc) on one page under the `/stats/` path with this heatmap

Resolving patch:
diff --git a/rgd/geodata/urls.py b/rgd/geodata/urls.py
--- a/rgd/geodata/urls.py
+++ b/rgd/geodata/urls.py
@@ -6,6 +6,11 @@
     # Pages
     path(r'', views.SpatialEntriesListView.as_view(), name='index'),
     path(r'geodata/raster/', views.RasterMetaEntriesListView.as_view(), name='raster-search'),
+    path(
+        'geodata/statistics',
+        views.StatisticsView.as_view(),
+        name='statistics',
+    ),
     path(
         'geodata/spatial_entries/<int:pk>/',
         views.spatial_entry_redirect_view,
diff --git a/rgd/geodata/views.py b/rgd/geodata/views.py
--- a/rgd/geodata/views.py
+++ b/rgd/geodata/views.py
@@ -1,6 +1,7 @@
 import json
 
 from django.contrib.gis.db.models import Collect, Extent
+from django.db.models import Max, Min
 from django.shortcuts import redirect
 from django.views import generic
 from django.views.generic import DetailView
@@ -89,6 +90,44 @@ class SpatialEntriesListView(_SpatialListView):
     template_name = 'geodata/spatial_entries.html'
 
 
+class StatisticsView(generic.ListView):
+    paginate_by = None
+    model = SpatialEntry
+    context_object_name = 'spatial_entries'
+    template_name = 'geodata/statistics.html'
+
+    def get_queryset(self):
+        queryset = self.model.objects.all()
+        return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')
+
+    def get_context_data(self, *args, **kwargs):
+        context = super().get_context_data(*args, **kwargs)
+        q = self.get_queryset()
+        context['count'] = q.count()
+        context['coordinates'] = json.dumps([o.footprint.centroid.json for o in self.object_list])
+        context['raster_count'] = q.filter(rastermetaentry__isnull=False).count()
+        instrumentation = (
+            q.filter(instrumentation__isnull=False).values_list('instrumentation').distinct()
+        )
+        context['instrumentation_count'] = instrumentation.count()
+        dates = q.filter(acquisition_date__isnull=False).aggregate(
+            Min('acquisition_date'),
+            Max('acquisition_date'),
+        )
+        context['acquisition_date__min'] = dates['acquisition_date__min']
+        context['acquisition_date__max'] = dates['acquisition_date__max']
+        extent = q.aggregate(Extent('outline'))
+        context['extents'] = json.dumps(
+            {
+                'xmin': extent['outline__extent'][0],
+                'ymin': extent['outline__extent'][1],
+                'xmax': extent['outline__extent'][2],
+                'ymax': extent['outline__extent'][3],
+            }
+        )
+        return context
+
+
 class RasterMetaEntriesListView(_SpatialListView):
     model = RasterMetaEntry
     filter = RasterMetaEntryFilter


Test patch: