From d72f8862cb1a39934952e708c3c869be1399846e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 9 Jul 2015 10:52:11 +0200 Subject: [PATCH] Fixed #25072 -- Prevented GDALRaster memory to be uncollectable Setting GDALRaster.bands as a cached property was creating a circular reference with objects having __del__ methods, which means the memory could never be freed. Thanks Daniel Wiesmann for the report and test, and Tim Graham for the review. --- django/contrib/gis/gdal/raster/band.py | 21 +++++++++++++++++++ django/contrib/gis/gdal/raster/source.py | 18 ++++++---------- tests/gis_tests/rasterapp/test_rasterfield.py | 5 +++++ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/django/contrib/gis/gdal/raster/band.py b/django/contrib/gis/gdal/raster/band.py index f1eb50e506..bd273b2985 100644 --- a/django/contrib/gis/gdal/raster/band.py +++ b/django/contrib/gis/gdal/raster/band.py @@ -1,10 +1,12 @@ from ctypes import byref, c_int from django.contrib.gis.gdal.base import GDALBase +from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.prototypes import raster as capi from django.contrib.gis.shortcuts import numpy from django.utils import six from django.utils.encoding import force_text +from django.utils.six.moves import range from .const import GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES @@ -148,3 +150,22 @@ class GDALBand(GDALBase): return list(data_array) else: self.source._flush() + + +class BandList(list): + def __init__(self, source): + self.source = source + list.__init__(self) + + def __iter__(self): + for idx in range(1, len(self) + 1): + yield GDALBand(self.source, idx) + + def __len__(self): + return capi.get_ds_raster_count(self.source._ptr) + + def __getitem__(self, index): + try: + return GDALBand(self.source, index + 1) + except GDALException: + raise GDALException('Unable to get band index %d' % index) diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index e56b442fee..3b4eb183b1 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -6,7 +6,7 @@ from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.prototypes import raster as capi -from django.contrib.gis.gdal.raster.band import GDALBand +from django.contrib.gis.gdal.raster.band import BandList from django.contrib.gis.gdal.raster.const import GDAL_RESAMPLE_ALGORITHMS from django.contrib.gis.gdal.srs import SpatialReference, SRSException from django.contrib.gis.geometry.regex import json_regex @@ -15,7 +15,6 @@ from django.utils.encoding import ( force_bytes, force_text, python_2_unicode_compatible, ) from django.utils.functional import cached_property -from django.utils.six.moves import range class TransformPoint(list): @@ -108,9 +107,10 @@ class GDALRaster(GDALBase): # Set band data if provided for i, band_input in enumerate(ds_input.get('bands', [])): - self.bands[i].data(band_input['data']) + band = self.bands[i] + band.data(band_input['data']) if 'nodata_value' in band_input: - self.bands[i].nodata_value = band_input['nodata_value'] + band.nodata_value = band_input['nodata_value'] # Set SRID self.srs = ds_input.get('srid') @@ -273,15 +273,9 @@ class GDALRaster(GDALBase): return xmin, ymin, xmax, ymax - @cached_property + @property def bands(self): - """ - Returns the bands of this raster as a list of GDALBand instances. - """ - bands = [] - for idx in range(1, capi.get_ds_raster_count(self._ptr) + 1): - bands.append(GDALBand(self, idx)) - return bands + return BandList(self) def warp(self, ds_input, resampling='NearestNeighbour', max_error=0.0): """ diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py index d1c015f499..03691ef0b3 100644 --- a/tests/gis_tests/rasterapp/test_rasterfield.py +++ b/tests/gis_tests/rasterapp/test_rasterfield.py @@ -23,6 +23,11 @@ class RasterFieldTest(TransactionTestCase): r.refresh_from_db() self.assertIsNone(r.rast) + def test_access_band_data_directly_from_queryset(self): + RasterModel.objects.create(rast=JSON_RASTER) + qs = RasterModel.objects.all() + qs[0].rast.bands[0].data() + def test_model_creation(self): """ Test RasterField through a test model.