Commit d72f8862 authored by Claude Paroz's avatar Claude Paroz
Browse files

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.
parent 0b02ce54
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
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)
+6 −12
Original line number Diff line number Diff line
@@ -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):
        """
+5 −0
Original line number Diff line number Diff line
@@ -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.