We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rgd/geodata/views.py`
Content:
```
1 import json
2 
3 from django.contrib.gis.db.models import Collect, Extent
4 from django.shortcuts import redirect
5 from django.views import generic
6 from django.views.generic import DetailView
7 from rest_framework.reverse import reverse
8 
9 from rgd.geodata import permissions
10 
11 from .filters import RasterMetaEntryFilter, SpatialEntryFilter
12 from .models.common import SpatialEntry
13 from .models.fmv.base import FMVEntry
14 from .models.geometry import GeometryEntry
15 from .models.imagery import RasterMetaEntry
16 from .models.threed import PointCloudEntry, PointCloudMetaEntry
17 
18 
19 class PermissionDetailView(DetailView):
20     def get_object(self):
21         obj = super().get_object()
22         permissions.check_read_perm(self.request.user, obj)
23         return obj
24 
25 
26 def query_params(params):
27     query = params.copy()
28 
29     if query.get('page'):
30         del query['page']
31 
32     return '&' + query.urlencode() if query.urlencode() else ''
33 
34 
35 class _SpatialListView(generic.ListView):
36     paginate_by = 15
37 
38     def get_queryset(self):
39         filterset = self.filter(data=self.request.GET)
40         assert filterset.is_valid()
41         queryset = filterset.filter_queryset(self.model.objects.all())
42         return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')
43 
44     def _get_extent_summary(self, object_list):
45         ids = [o.spatial_id for o in object_list]
46         queryset = self.model.objects.filter(spatial_id__in=ids)
47         summary = queryset.aggregate(
48             Collect('outline'),
49             Extent('outline'),
50         )
51         extents = {
52             'count': queryset.count(),
53         }
54         if queryset.count():
55             extents.update(
56                 {
57                     'collect': json.loads(summary['outline__collect'].geojson),
58                     'convex_hull': json.loads(summary['outline__collect'].convex_hull.geojson),
59                     'extent': {
60                         'xmin': summary['outline__extent'][0],
61                         'ymin': summary['outline__extent'][1],
62                         'xmax': summary['outline__extent'][2],
63                         'ymax': summary['outline__extent'][3],
64                     },
65                 }
66             )
67         return extents
68 
69     def get_context_data(self, *args, **kwargs):
70         # Pagination happens here
71         context = super().get_context_data(*args, **kwargs)
72         summary = self._get_extent_summary(context['object_list'])
73         context['extents'] = json.dumps(summary)
74         # Have a smaller dict of meta fields to parse for menu bar
75         # This keeps us from parsing long GeoJSON fields twice
76         meta = {
77             'count': self.get_queryset().count(),  # This is the amount in the full results
78         }
79         context['extents_meta'] = json.dumps(meta)
80         context['search_params'] = json.dumps(self.request.GET)
81         context['query_params'] = query_params(self.request.GET)
82         return context
83 
84 
85 class SpatialEntriesListView(_SpatialListView):
86     model = SpatialEntry
87     filter = SpatialEntryFilter
88     context_object_name = 'spatial_entries'
89     template_name = 'geodata/spatial_entries.html'
90 
91 
92 class RasterMetaEntriesListView(_SpatialListView):
93     model = RasterMetaEntry
94     filter = RasterMetaEntryFilter
95     context_object_name = 'spatial_entries'
96     template_name = 'geodata/raster_entries.html'
97 
98 
99 class _SpatialDetailView(PermissionDetailView):
100     def get_object(self):
101         obj = super().get_object()
102         permissions.check_read_perm(self.request.user, obj)
103         return obj
104 
105     def _get_extent(self):
106         extent = {
107             'count': 0,
108         }
109         if self.object.footprint:
110             extent.update(
111                 {
112                     'count': 1,
113                     'collect': self.object.footprint.json,
114                     'outline': self.object.outline.json,
115                     'extent': {
116                         'xmin': self.object.footprint.extent[0],
117                         'ymin': self.object.footprint.extent[1],
118                         'xmax': self.object.footprint.extent[2],
119                         'ymax': self.object.footprint.extent[3],
120                     },
121                 }
122             )
123         return extent
124 
125     def get_context_data(self, *args, **kwargs):
126         context = super().get_context_data(*args, **kwargs)
127         context['extents'] = json.dumps(self._get_extent())
128         return context
129 
130 
131 class RasterEntryDetailView(_SpatialDetailView):
132     model = RasterMetaEntry
133 
134 
135 class FMVEntryDetailView(_SpatialDetailView):
136     model = FMVEntry
137 
138     def _get_extent(self):
139         extent = super()._get_extent()
140         if self.object.ground_union is not None:
141             # All or none of these will be set, only check one
142             extent['collect'] = self.object.ground_union.json
143             extent['ground_frames'] = self.object.ground_frames.json
144             extent['frame_numbers'] = self.object._blob_to_array(self.object.frame_numbers)
145         return extent
146 
147     def get_context_data(self, *args, **kwargs):
148         context = super().get_context_data(*args, **kwargs)
149         context['frame_rate'] = json.dumps(self.object.fmv_file.frame_rate)
150         return context
151 
152 
153 class GeometryEntryDetailView(_SpatialDetailView):
154     model = GeometryEntry
155 
156     def _get_extent(self):
157         extent = super()._get_extent()
158         extent['data'] = self.object.data.json
159         return extent
160 
161 
162 def spatial_entry_redirect_view(request, pk):
163     spat = SpatialEntry.objects.get(pk=pk)
164     sub = spat.subentry
165     if isinstance(sub, RasterMetaEntry):
166         name = 'raster-entry-detail'
167     elif isinstance(sub, GeometryEntry):
168         name = 'geometry-entry-detail'
169     elif isinstance(sub, FMVEntry):
170         name = 'fmv-entry-detail'
171     elif isinstance(sub, PointCloudMetaEntry):
172         name = 'point-cloud-entry-detail'
173         sub = sub.parent_point_cloud
174     else:
175         raise ValueError()
176     return redirect(reverse(name, kwargs={'pk': sub.pk}))
177 
178 
179 class PointCloudEntryDetailView(PermissionDetailView):
180     model = PointCloudEntry
181
```

Path: `rgd/geodata/urls.py`
Content:
```
1 from django.urls import path
2 
3 from . import api, views
4 
5 urlpatterns = [
6     # Pages
7     path(r'', views.SpatialEntriesListView.as_view(), name='index'),
8     path(r'geodata/raster/', views.RasterMetaEntriesListView.as_view(), name='raster-search'),
9     path(
10         'geodata/spatial_entries/<int:pk>/',
11         views.spatial_entry_redirect_view,
12         name='spatial-entry-detail',
13     ),
14     path(
15         'geodata/raster/<int:pk>/',
16         views.RasterEntryDetailView.as_view(),
17         name='raster-entry-detail',
18     ),
19     path(
20         'geodata/fmv/<int:pk>/',
21         views.FMVEntryDetailView.as_view(),
22         name='fmv-entry-detail',
23     ),
24     path(
25         'geodata/geometry/<int:pk>/',
26         views.GeometryEntryDetailView.as_view(),
27         name='geometry-entry-detail',
28     ),
29     path(
30         'geodata/point_cloud/<int:pk>/',
31         views.PointCloudEntryDetailView.as_view(),
32         name='point-cloud-entry-detail',
33     ),
34     #############
35     # Search
36     path('api/geosearch', api.search.SearchSpatialEntryView.as_view()),
37     path('api/geosearch/raster', api.search.SearchRasterMetaEntrySTACView.as_view()),
38     #############
39     # Other
40     path(
41         'api/geodata/status/<model>/<int:pk>',
42         api.download.get_status,
43         name='get-status',
44     ),
45     path(
46         'api/geodata/common/spatial_entry/<int:spatial_id>',
47         api.get.GetSpatialEntry.as_view(),
48         name='spatial-entry',
49     ),
50     path(
51         'api/geodata/common/spatial_entry/<int:spatial_id>/footprint',
52         api.get.GetSpatialEntryFootprint.as_view(),
53         name='spatial-entry-footprint',
54     ),
55     path(
56         'api/geodata/common/checksum_file/<int:pk>',
57         api.get.GetChecksumFile.as_view(),
58         name='checksum-file',
59     ),
60     path(
61         'api/geodata/common/checksum_file/<int:pk>/data',
62         api.download.download_checksum_file,
63         name='checksum-file-data',
64     ),
65     path(
66         'api/geodata/geometry/<int:pk>',
67         api.get.GetGeometryEntry.as_view(),
68         name='geometry-entry',
69     ),
70     path(
71         'api/geodata/geometry/<int:pk>/data',
72         api.get.GetGeometryEntryData.as_view(),
73         name='geometry-entry-data',
74     ),
75     path(
76         'api/geodata/imagery/<int:pk>',
77         api.get.GetImageEntry.as_view(),
78         name='image-entry',
79     ),
80     path(
81         'api/geodata/imagery/<int:pk>/data',
82         api.download.download_image_entry_file,
83         name='image-entry-data',
84     ),
85     path(
86         'api/geodata/imagery/image_set/<int:pk>',
87         api.get.GetImageSet.as_view(),
88         name='image-set',
89     ),
90     path(
91         'api/geodata/imagery/raster/<int:pk>',
92         api.get.GetRasterMetaEntry.as_view(),
93         name='raster-meta-entry',
94     ),
95     path(
96         'api/geodata/imagery/raster/<int:pk>/stac',
97         api.get.GetRasterMetaEntrySTAC.as_view(),
98         name='raster-meta-entry-stac',
99     ),
100     path(
101         'api/geodata/fmv/<int:pk>',
102         api.get.GetFMVEntry.as_view(),
103         name='fmv-entry',
104     ),
105     path(
106         'api/geodata/fmv/<int:pk>/data',
107         api.get.GetFMVDataEntry.as_view(),
108         name='fmv-entry-data',
109     ),
110     path(
111         'api/geodata/point_cloud/<int:pk>',
112         api.get.GetPointCloudEntry.as_view(),
113         name='point-cloud-entry',
114     ),
115     path(
116         'api/geodata/point_cloud/<int:pk>/base64',
117         api.get.GetPointCloudEntryData.as_view(),
118         name='point-cloud-entry-data',
119     ),
120     #############
121     # Geoprocessing
122     path(
123         'api/geoprocess/imagery/<int:pk>/tiles',
124         api.tiles.TileMetadataView.as_view(),
125         name='image-tile-metadata',
126     ),
127     path(
128         'api/geoprocess/imagery/<int:pk>/tiles/<int:z>/<int:x>/<int:y>.png',
129         api.tiles.TileView.as_view(),
130         name='image-tiles',
131     ),
132     path(
133         'api/geoprocess/imagery/<int:pk>/thumbnail',
134         api.tiles.TileThumnailView.as_view(),
135         name='image-thumbnail',
136     ),
137     path('api/geoprocess/imagery/cog', api.post.CreateConvertedImageFile.as_view()),
138     path(
139         'api/geoprocess/imagery/cog/<int:pk>',
140         api.get.GetConvertedImageStatus.as_view(),
141         name='cog',
142     ),
143     path(
144         'api/geoprocess/imagery/cog/<int:pk>/data',
145         api.download.download_cog_file,
146         name='cog-data',
147     ),
148     path(
149         'api/geoprocess/imagery/subsample',
150         api.post.CreateSubsampledImage.as_view(),
151     ),
152     path(
153         'api/geoprocess/imagery/subsample/<int:pk>',
154         api.get.GetSubsampledImage.as_view(),
155         name='subsampled',
156     ),
157     path(
158         'api/geoprocess/imagery/subsample/<int:pk>/status',
159         api.download.get_status_subsampled_image,
160         name='subsampled-status',
161     ),
162 ]
163
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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

{"golden_diff": "diff --git a/rgd/geodata/urls.py b/rgd/geodata/urls.py\n--- a/rgd/geodata/urls.py\n+++ b/rgd/geodata/urls.py\n@@ -6,6 +6,11 @@\n     # Pages\n     path(r'', views.SpatialEntriesListView.as_view(), name='index'),\n     path(r'geodata/raster/', views.RasterMetaEntriesListView.as_view(), name='raster-search'),\n+    path(\n+        'geodata/statistics',\n+        views.StatisticsView.as_view(),\n+        name='statistics',\n+    ),\n     path(\n         'geodata/spatial_entries/<int:pk>/',\n         views.spatial_entry_redirect_view,\ndiff --git a/rgd/geodata/views.py b/rgd/geodata/views.py\n--- a/rgd/geodata/views.py\n+++ b/rgd/geodata/views.py\n@@ -1,6 +1,7 @@\n import json\n \n from django.contrib.gis.db.models import Collect, Extent\n+from django.db.models import Max, Min\n from django.shortcuts import redirect\n from django.views import generic\n from django.views.generic import DetailView\n@@ -89,6 +90,44 @@\n     template_name = 'geodata/spatial_entries.html'\n \n \n+class StatisticsView(generic.ListView):\n+    paginate_by = None\n+    model = SpatialEntry\n+    context_object_name = 'spatial_entries'\n+    template_name = 'geodata/statistics.html'\n+\n+    def get_queryset(self):\n+        queryset = self.model.objects.all()\n+        return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')\n+\n+    def get_context_data(self, *args, **kwargs):\n+        context = super().get_context_data(*args, **kwargs)\n+        q = self.get_queryset()\n+        context['count'] = q.count()\n+        context['coordinates'] = json.dumps([o.footprint.centroid.json for o in self.object_list])\n+        context['raster_count'] = q.filter(rastermetaentry__isnull=False).count()\n+        instrumentation = (\n+            q.filter(instrumentation__isnull=False).values_list('instrumentation').distinct()\n+        )\n+        context['instrumentation_count'] = instrumentation.count()\n+        dates = q.filter(acquisition_date__isnull=False).aggregate(\n+            Min('acquisition_date'),\n+            Max('acquisition_date'),\n+        )\n+        context['acquisition_date__min'] = dates['acquisition_date__min']\n+        context['acquisition_date__max'] = dates['acquisition_date__max']\n+        extent = q.aggregate(Extent('outline'))\n+        context['extents'] = json.dumps(\n+            {\n+                'xmin': extent['outline__extent'][0],\n+                'ymin': extent['outline__extent'][1],\n+                'xmax': extent['outline__extent'][2],\n+                'ymax': extent['outline__extent'][3],\n+            }\n+        )\n+        return context\n+\n+\n class RasterMetaEntriesListView(_SpatialListView):\n     model = RasterMetaEntry\n     filter = RasterMetaEntryFilter\n", "issue": "Heat map to show all data in the catalog\nWe should add a new heatmap page to show a heat map of all of the data in the catalog.\r\n\r\nIt 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\n", "before_files": [{"content": "import json\n\nfrom django.contrib.gis.db.models import Collect, Extent\nfrom django.shortcuts import redirect\nfrom django.views import generic\nfrom django.views.generic import DetailView\nfrom rest_framework.reverse import reverse\n\nfrom rgd.geodata import permissions\n\nfrom .filters import RasterMetaEntryFilter, SpatialEntryFilter\nfrom .models.common import SpatialEntry\nfrom .models.fmv.base import FMVEntry\nfrom .models.geometry import GeometryEntry\nfrom .models.imagery import RasterMetaEntry\nfrom .models.threed import PointCloudEntry, PointCloudMetaEntry\n\n\nclass PermissionDetailView(DetailView):\n    def get_object(self):\n        obj = super().get_object()\n        permissions.check_read_perm(self.request.user, obj)\n        return obj\n\n\ndef query_params(params):\n    query = params.copy()\n\n    if query.get('page'):\n        del query['page']\n\n    return '&' + query.urlencode() if query.urlencode() else ''\n\n\nclass _SpatialListView(generic.ListView):\n    paginate_by = 15\n\n    def get_queryset(self):\n        filterset = self.filter(data=self.request.GET)\n        assert filterset.is_valid()\n        queryset = filterset.filter_queryset(self.model.objects.all())\n        return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')\n\n    def _get_extent_summary(self, object_list):\n        ids = [o.spatial_id for o in object_list]\n        queryset = self.model.objects.filter(spatial_id__in=ids)\n        summary = queryset.aggregate(\n            Collect('outline'),\n            Extent('outline'),\n        )\n        extents = {\n            'count': queryset.count(),\n        }\n        if queryset.count():\n            extents.update(\n                {\n                    'collect': json.loads(summary['outline__collect'].geojson),\n                    'convex_hull': json.loads(summary['outline__collect'].convex_hull.geojson),\n                    'extent': {\n                        'xmin': summary['outline__extent'][0],\n                        'ymin': summary['outline__extent'][1],\n                        'xmax': summary['outline__extent'][2],\n                        'ymax': summary['outline__extent'][3],\n                    },\n                }\n            )\n        return extents\n\n    def get_context_data(self, *args, **kwargs):\n        # Pagination happens here\n        context = super().get_context_data(*args, **kwargs)\n        summary = self._get_extent_summary(context['object_list'])\n        context['extents'] = json.dumps(summary)\n        # Have a smaller dict of meta fields to parse for menu bar\n        # This keeps us from parsing long GeoJSON fields twice\n        meta = {\n            'count': self.get_queryset().count(),  # This is the amount in the full results\n        }\n        context['extents_meta'] = json.dumps(meta)\n        context['search_params'] = json.dumps(self.request.GET)\n        context['query_params'] = query_params(self.request.GET)\n        return context\n\n\nclass SpatialEntriesListView(_SpatialListView):\n    model = SpatialEntry\n    filter = SpatialEntryFilter\n    context_object_name = 'spatial_entries'\n    template_name = 'geodata/spatial_entries.html'\n\n\nclass RasterMetaEntriesListView(_SpatialListView):\n    model = RasterMetaEntry\n    filter = RasterMetaEntryFilter\n    context_object_name = 'spatial_entries'\n    template_name = 'geodata/raster_entries.html'\n\n\nclass _SpatialDetailView(PermissionDetailView):\n    def get_object(self):\n        obj = super().get_object()\n        permissions.check_read_perm(self.request.user, obj)\n        return obj\n\n    def _get_extent(self):\n        extent = {\n            'count': 0,\n        }\n        if self.object.footprint:\n            extent.update(\n                {\n                    'count': 1,\n                    'collect': self.object.footprint.json,\n                    'outline': self.object.outline.json,\n                    'extent': {\n                        'xmin': self.object.footprint.extent[0],\n                        'ymin': self.object.footprint.extent[1],\n                        'xmax': self.object.footprint.extent[2],\n                        'ymax': self.object.footprint.extent[3],\n                    },\n                }\n            )\n        return extent\n\n    def get_context_data(self, *args, **kwargs):\n        context = super().get_context_data(*args, **kwargs)\n        context['extents'] = json.dumps(self._get_extent())\n        return context\n\n\nclass RasterEntryDetailView(_SpatialDetailView):\n    model = RasterMetaEntry\n\n\nclass FMVEntryDetailView(_SpatialDetailView):\n    model = FMVEntry\n\n    def _get_extent(self):\n        extent = super()._get_extent()\n        if self.object.ground_union is not None:\n            # All or none of these will be set, only check one\n            extent['collect'] = self.object.ground_union.json\n            extent['ground_frames'] = self.object.ground_frames.json\n            extent['frame_numbers'] = self.object._blob_to_array(self.object.frame_numbers)\n        return extent\n\n    def get_context_data(self, *args, **kwargs):\n        context = super().get_context_data(*args, **kwargs)\n        context['frame_rate'] = json.dumps(self.object.fmv_file.frame_rate)\n        return context\n\n\nclass GeometryEntryDetailView(_SpatialDetailView):\n    model = GeometryEntry\n\n    def _get_extent(self):\n        extent = super()._get_extent()\n        extent['data'] = self.object.data.json\n        return extent\n\n\ndef spatial_entry_redirect_view(request, pk):\n    spat = SpatialEntry.objects.get(pk=pk)\n    sub = spat.subentry\n    if isinstance(sub, RasterMetaEntry):\n        name = 'raster-entry-detail'\n    elif isinstance(sub, GeometryEntry):\n        name = 'geometry-entry-detail'\n    elif isinstance(sub, FMVEntry):\n        name = 'fmv-entry-detail'\n    elif isinstance(sub, PointCloudMetaEntry):\n        name = 'point-cloud-entry-detail'\n        sub = sub.parent_point_cloud\n    else:\n        raise ValueError()\n    return redirect(reverse(name, kwargs={'pk': sub.pk}))\n\n\nclass PointCloudEntryDetailView(PermissionDetailView):\n    model = PointCloudEntry\n", "path": "rgd/geodata/views.py"}, {"content": "from django.urls import path\n\nfrom . import api, views\n\nurlpatterns = [\n    # Pages\n    path(r'', views.SpatialEntriesListView.as_view(), name='index'),\n    path(r'geodata/raster/', views.RasterMetaEntriesListView.as_view(), name='raster-search'),\n    path(\n        'geodata/spatial_entries/<int:pk>/',\n        views.spatial_entry_redirect_view,\n        name='spatial-entry-detail',\n    ),\n    path(\n        'geodata/raster/<int:pk>/',\n        views.RasterEntryDetailView.as_view(),\n        name='raster-entry-detail',\n    ),\n    path(\n        'geodata/fmv/<int:pk>/',\n        views.FMVEntryDetailView.as_view(),\n        name='fmv-entry-detail',\n    ),\n    path(\n        'geodata/geometry/<int:pk>/',\n        views.GeometryEntryDetailView.as_view(),\n        name='geometry-entry-detail',\n    ),\n    path(\n        'geodata/point_cloud/<int:pk>/',\n        views.PointCloudEntryDetailView.as_view(),\n        name='point-cloud-entry-detail',\n    ),\n    #############\n    # Search\n    path('api/geosearch', api.search.SearchSpatialEntryView.as_view()),\n    path('api/geosearch/raster', api.search.SearchRasterMetaEntrySTACView.as_view()),\n    #############\n    # Other\n    path(\n        'api/geodata/status/<model>/<int:pk>',\n        api.download.get_status,\n        name='get-status',\n    ),\n    path(\n        'api/geodata/common/spatial_entry/<int:spatial_id>',\n        api.get.GetSpatialEntry.as_view(),\n        name='spatial-entry',\n    ),\n    path(\n        'api/geodata/common/spatial_entry/<int:spatial_id>/footprint',\n        api.get.GetSpatialEntryFootprint.as_view(),\n        name='spatial-entry-footprint',\n    ),\n    path(\n        'api/geodata/common/checksum_file/<int:pk>',\n        api.get.GetChecksumFile.as_view(),\n        name='checksum-file',\n    ),\n    path(\n        'api/geodata/common/checksum_file/<int:pk>/data',\n        api.download.download_checksum_file,\n        name='checksum-file-data',\n    ),\n    path(\n        'api/geodata/geometry/<int:pk>',\n        api.get.GetGeometryEntry.as_view(),\n        name='geometry-entry',\n    ),\n    path(\n        'api/geodata/geometry/<int:pk>/data',\n        api.get.GetGeometryEntryData.as_view(),\n        name='geometry-entry-data',\n    ),\n    path(\n        'api/geodata/imagery/<int:pk>',\n        api.get.GetImageEntry.as_view(),\n        name='image-entry',\n    ),\n    path(\n        'api/geodata/imagery/<int:pk>/data',\n        api.download.download_image_entry_file,\n        name='image-entry-data',\n    ),\n    path(\n        'api/geodata/imagery/image_set/<int:pk>',\n        api.get.GetImageSet.as_view(),\n        name='image-set',\n    ),\n    path(\n        'api/geodata/imagery/raster/<int:pk>',\n        api.get.GetRasterMetaEntry.as_view(),\n        name='raster-meta-entry',\n    ),\n    path(\n        'api/geodata/imagery/raster/<int:pk>/stac',\n        api.get.GetRasterMetaEntrySTAC.as_view(),\n        name='raster-meta-entry-stac',\n    ),\n    path(\n        'api/geodata/fmv/<int:pk>',\n        api.get.GetFMVEntry.as_view(),\n        name='fmv-entry',\n    ),\n    path(\n        'api/geodata/fmv/<int:pk>/data',\n        api.get.GetFMVDataEntry.as_view(),\n        name='fmv-entry-data',\n    ),\n    path(\n        'api/geodata/point_cloud/<int:pk>',\n        api.get.GetPointCloudEntry.as_view(),\n        name='point-cloud-entry',\n    ),\n    path(\n        'api/geodata/point_cloud/<int:pk>/base64',\n        api.get.GetPointCloudEntryData.as_view(),\n        name='point-cloud-entry-data',\n    ),\n    #############\n    # Geoprocessing\n    path(\n        'api/geoprocess/imagery/<int:pk>/tiles',\n        api.tiles.TileMetadataView.as_view(),\n        name='image-tile-metadata',\n    ),\n    path(\n        'api/geoprocess/imagery/<int:pk>/tiles/<int:z>/<int:x>/<int:y>.png',\n        api.tiles.TileView.as_view(),\n        name='image-tiles',\n    ),\n    path(\n        'api/geoprocess/imagery/<int:pk>/thumbnail',\n        api.tiles.TileThumnailView.as_view(),\n        name='image-thumbnail',\n    ),\n    path('api/geoprocess/imagery/cog', api.post.CreateConvertedImageFile.as_view()),\n    path(\n        'api/geoprocess/imagery/cog/<int:pk>',\n        api.get.GetConvertedImageStatus.as_view(),\n        name='cog',\n    ),\n    path(\n        'api/geoprocess/imagery/cog/<int:pk>/data',\n        api.download.download_cog_file,\n        name='cog-data',\n    ),\n    path(\n        'api/geoprocess/imagery/subsample',\n        api.post.CreateSubsampledImage.as_view(),\n    ),\n    path(\n        'api/geoprocess/imagery/subsample/<int:pk>',\n        api.get.GetSubsampledImage.as_view(),\n        name='subsampled',\n    ),\n    path(\n        'api/geoprocess/imagery/subsample/<int:pk>/status',\n        api.download.get_status_subsampled_image,\n        name='subsampled-status',\n    ),\n]\n", "path": "rgd/geodata/urls.py"}], "after_files": [{"content": "import json\n\nfrom django.contrib.gis.db.models import Collect, Extent\nfrom django.db.models import Max, Min\nfrom django.shortcuts import redirect\nfrom django.views import generic\nfrom django.views.generic import DetailView\nfrom rest_framework.reverse import reverse\n\nfrom rgd.geodata import permissions\n\nfrom .filters import RasterMetaEntryFilter, SpatialEntryFilter\nfrom .models.common import SpatialEntry\nfrom .models.fmv.base import FMVEntry\nfrom .models.geometry import GeometryEntry\nfrom .models.imagery import RasterMetaEntry\nfrom .models.threed import PointCloudEntry, PointCloudMetaEntry\n\n\nclass PermissionDetailView(DetailView):\n    def get_object(self):\n        obj = super().get_object()\n        permissions.check_read_perm(self.request.user, obj)\n        return obj\n\n\ndef query_params(params):\n    query = params.copy()\n\n    if query.get('page'):\n        del query['page']\n\n    return '&' + query.urlencode() if query.urlencode() else ''\n\n\nclass _SpatialListView(generic.ListView):\n    paginate_by = 15\n\n    def get_queryset(self):\n        filterset = self.filter(data=self.request.GET)\n        assert filterset.is_valid()\n        queryset = filterset.filter_queryset(self.model.objects.all())\n        return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')\n\n    def _get_extent_summary(self, object_list):\n        ids = [o.spatial_id for o in object_list]\n        queryset = self.model.objects.filter(spatial_id__in=ids)\n        summary = queryset.aggregate(\n            Collect('outline'),\n            Extent('outline'),\n        )\n        extents = {\n            'count': queryset.count(),\n        }\n        if queryset.count():\n            extents.update(\n                {\n                    'collect': json.loads(summary['outline__collect'].geojson),\n                    'convex_hull': json.loads(summary['outline__collect'].convex_hull.geojson),\n                    'extent': {\n                        'xmin': summary['outline__extent'][0],\n                        'ymin': summary['outline__extent'][1],\n                        'xmax': summary['outline__extent'][2],\n                        'ymax': summary['outline__extent'][3],\n                    },\n                }\n            )\n        return extents\n\n    def get_context_data(self, *args, **kwargs):\n        # Pagination happens here\n        context = super().get_context_data(*args, **kwargs)\n        summary = self._get_extent_summary(context['object_list'])\n        context['extents'] = json.dumps(summary)\n        # Have a smaller dict of meta fields to parse for menu bar\n        # This keeps us from parsing long GeoJSON fields twice\n        meta = {\n            'count': self.get_queryset().count(),  # This is the amount in the full results\n        }\n        context['extents_meta'] = json.dumps(meta)\n        context['search_params'] = json.dumps(self.request.GET)\n        context['query_params'] = query_params(self.request.GET)\n        return context\n\n\nclass SpatialEntriesListView(_SpatialListView):\n    model = SpatialEntry\n    filter = SpatialEntryFilter\n    context_object_name = 'spatial_entries'\n    template_name = 'geodata/spatial_entries.html'\n\n\nclass StatisticsView(generic.ListView):\n    paginate_by = None\n    model = SpatialEntry\n    context_object_name = 'spatial_entries'\n    template_name = 'geodata/statistics.html'\n\n    def get_queryset(self):\n        queryset = self.model.objects.all()\n        return permissions.filter_read_perm(self.request.user, queryset).order_by('spatial_id')\n\n    def get_context_data(self, *args, **kwargs):\n        context = super().get_context_data(*args, **kwargs)\n        q = self.get_queryset()\n        context['count'] = q.count()\n        context['coordinates'] = json.dumps([o.footprint.centroid.json for o in self.object_list])\n        context['raster_count'] = q.filter(rastermetaentry__isnull=False).count()\n        instrumentation = (\n            q.filter(instrumentation__isnull=False).values_list('instrumentation').distinct()\n        )\n        context['instrumentation_count'] = instrumentation.count()\n        dates = q.filter(acquisition_date__isnull=False).aggregate(\n            Min('acquisition_date'),\n            Max('acquisition_date'),\n        )\n        context['acquisition_date__min'] = dates['acquisition_date__min']\n        context['acquisition_date__max'] = dates['acquisition_date__max']\n        extent = q.aggregate(Extent('outline'))\n        context['extents'] = json.dumps(\n            {\n                'xmin': extent['outline__extent'][0],\n                'ymin': extent['outline__extent'][1],\n                'xmax': extent['outline__extent'][2],\n                'ymax': extent['outline__extent'][3],\n            }\n        )\n        return context\n\n\nclass RasterMetaEntriesListView(_SpatialListView):\n    model = RasterMetaEntry\n    filter = RasterMetaEntryFilter\n    context_object_name = 'spatial_entries'\n    template_name = 'geodata/raster_entries.html'\n\n\nclass _SpatialDetailView(PermissionDetailView):\n    def get_object(self):\n        obj = super().get_object()\n        permissions.check_read_perm(self.request.user, obj)\n        return obj\n\n    def _get_extent(self):\n        extent = {\n            'count': 0,\n        }\n        if self.object.footprint:\n            extent.update(\n                {\n                    'count': 1,\n                    'collect': self.object.footprint.json,\n                    'outline': self.object.outline.json,\n                    'extent': {\n                        'xmin': self.object.footprint.extent[0],\n                        'ymin': self.object.footprint.extent[1],\n                        'xmax': self.object.footprint.extent[2],\n                        'ymax': self.object.footprint.extent[3],\n                    },\n                }\n            )\n        return extent\n\n    def get_context_data(self, *args, **kwargs):\n        context = super().get_context_data(*args, **kwargs)\n        context['extents'] = json.dumps(self._get_extent())\n        return context\n\n\nclass RasterEntryDetailView(_SpatialDetailView):\n    model = RasterMetaEntry\n\n\nclass FMVEntryDetailView(_SpatialDetailView):\n    model = FMVEntry\n\n    def _get_extent(self):\n        extent = super()._get_extent()\n        if self.object.ground_union is not None:\n            # All or none of these will be set, only check one\n            extent['collect'] = self.object.ground_union.json\n            extent['ground_frames'] = self.object.ground_frames.json\n            extent['frame_numbers'] = self.object._blob_to_array(self.object.frame_numbers)\n        return extent\n\n    def get_context_data(self, *args, **kwargs):\n        context = super().get_context_data(*args, **kwargs)\n        context['frame_rate'] = json.dumps(self.object.fmv_file.frame_rate)\n        return context\n\n\nclass GeometryEntryDetailView(_SpatialDetailView):\n    model = GeometryEntry\n\n    def _get_extent(self):\n        extent = super()._get_extent()\n        extent['data'] = self.object.data.json\n        return extent\n\n\ndef spatial_entry_redirect_view(request, pk):\n    spat = SpatialEntry.objects.get(pk=pk)\n    sub = spat.subentry\n    if isinstance(sub, RasterMetaEntry):\n        name = 'raster-entry-detail'\n    elif isinstance(sub, GeometryEntry):\n        name = 'geometry-entry-detail'\n    elif isinstance(sub, FMVEntry):\n        name = 'fmv-entry-detail'\n    elif isinstance(sub, PointCloudMetaEntry):\n        name = 'point-cloud-entry-detail'\n        sub = sub.parent_point_cloud\n    else:\n        raise ValueError()\n    return redirect(reverse(name, kwargs={'pk': sub.pk}))\n\n\nclass PointCloudEntryDetailView(PermissionDetailView):\n    model = PointCloudEntry\n", "path": "rgd/geodata/views.py"}, {"content": "from django.urls import path\n\nfrom . import api, views\n\nurlpatterns = [\n    # Pages\n    path(r'', views.SpatialEntriesListView.as_view(), name='index'),\n    path(r'geodata/raster/', views.RasterMetaEntriesListView.as_view(), name='raster-search'),\n    path(\n        'geodata/statistics',\n        views.StatisticsView.as_view(),\n        name='statistics',\n    ),\n    path(\n        'geodata/spatial_entries/<int:pk>/',\n        views.spatial_entry_redirect_view,\n        name='spatial-entry-detail',\n    ),\n    path(\n        'geodata/raster/<int:pk>/',\n        views.RasterEntryDetailView.as_view(),\n        name='raster-entry-detail',\n    ),\n    path(\n        'geodata/fmv/<int:pk>/',\n        views.FMVEntryDetailView.as_view(),\n        name='fmv-entry-detail',\n    ),\n    path(\n        'geodata/geometry/<int:pk>/',\n        views.GeometryEntryDetailView.as_view(),\n        name='geometry-entry-detail',\n    ),\n    path(\n        'geodata/point_cloud/<int:pk>/',\n        views.PointCloudEntryDetailView.as_view(),\n        name='point-cloud-entry-detail',\n    ),\n    #############\n    # Search\n    path('api/geosearch', api.search.SearchSpatialEntryView.as_view()),\n    path('api/geosearch/raster', api.search.SearchRasterMetaEntrySTACView.as_view()),\n    #############\n    # Other\n    path(\n        'api/geodata/status/<model>/<int:pk>',\n        api.download.get_status,\n        name='get-status',\n    ),\n    path(\n        'api/geodata/common/spatial_entry/<int:spatial_id>',\n        api.get.GetSpatialEntry.as_view(),\n        name='spatial-entry',\n    ),\n    path(\n        'api/geodata/common/spatial_entry/<int:spatial_id>/footprint',\n        api.get.GetSpatialEntryFootprint.as_view(),\n        name='spatial-entry-footprint',\n    ),\n    path(\n        'api/geodata/common/checksum_file/<int:pk>',\n        api.get.GetChecksumFile.as_view(),\n        name='checksum-file',\n    ),\n    path(\n        'api/geodata/common/checksum_file/<int:pk>/data',\n        api.download.download_checksum_file,\n        name='checksum-file-data',\n    ),\n    path(\n        'api/geodata/geometry/<int:pk>',\n        api.get.GetGeometryEntry.as_view(),\n        name='geometry-entry',\n    ),\n    path(\n        'api/geodata/geometry/<int:pk>/data',\n        api.get.GetGeometryEntryData.as_view(),\n        name='geometry-entry-data',\n    ),\n    path(\n        'api/geodata/imagery/<int:pk>',\n        api.get.GetImageEntry.as_view(),\n        name='image-entry',\n    ),\n    path(\n        'api/geodata/imagery/<int:pk>/data',\n        api.download.download_image_entry_file,\n        name='image-entry-data',\n    ),\n    path(\n        'api/geodata/imagery/image_set/<int:pk>',\n        api.get.GetImageSet.as_view(),\n        name='image-set',\n    ),\n    path(\n        'api/geodata/imagery/raster/<int:pk>',\n        api.get.GetRasterMetaEntry.as_view(),\n        name='raster-meta-entry',\n    ),\n    path(\n        'api/geodata/imagery/raster/<int:pk>/stac',\n        api.get.GetRasterMetaEntrySTAC.as_view(),\n        name='raster-meta-entry-stac',\n    ),\n    path(\n        'api/geodata/fmv/<int:pk>',\n        api.get.GetFMVEntry.as_view(),\n        name='fmv-entry',\n    ),\n    path(\n        'api/geodata/fmv/<int:pk>/data',\n        api.get.GetFMVDataEntry.as_view(),\n        name='fmv-entry-data',\n    ),\n    path(\n        'api/geodata/point_cloud/<int:pk>',\n        api.get.GetPointCloudEntry.as_view(),\n        name='point-cloud-entry',\n    ),\n    path(\n        'api/geodata/point_cloud/<int:pk>/base64',\n        api.get.GetPointCloudEntryData.as_view(),\n        name='point-cloud-entry-data',\n    ),\n    #############\n    # Geoprocessing\n    path(\n        'api/geoprocess/imagery/<int:pk>/tiles',\n        api.tiles.TileMetadataView.as_view(),\n        name='image-tile-metadata',\n    ),\n    path(\n        'api/geoprocess/imagery/<int:pk>/tiles/<int:z>/<int:x>/<int:y>.png',\n        api.tiles.TileView.as_view(),\n        name='image-tiles',\n    ),\n    path(\n        'api/geoprocess/imagery/<int:pk>/thumbnail',\n        api.tiles.TileThumnailView.as_view(),\n        name='image-thumbnail',\n    ),\n    path('api/geoprocess/imagery/cog', api.post.CreateConvertedImageFile.as_view()),\n    path(\n        'api/geoprocess/imagery/cog/<int:pk>',\n        api.get.GetConvertedImageStatus.as_view(),\n        name='cog',\n    ),\n    path(\n        'api/geoprocess/imagery/cog/<int:pk>/data',\n        api.download.download_cog_file,\n        name='cog-data',\n    ),\n    path(\n        'api/geoprocess/imagery/subsample',\n        api.post.CreateSubsampledImage.as_view(),\n    ),\n    path(\n        'api/geoprocess/imagery/subsample/<int:pk>',\n        api.get.GetSubsampledImage.as_view(),\n        name='subsampled',\n    ),\n    path(\n        'api/geoprocess/imagery/subsample/<int:pk>/status',\n        api.download.get_status_subsampled_image,\n        name='subsampled-status',\n    ),\n]\n", "path": "rgd/geodata/urls.py"}]}