summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorDaniel Wiesmann <yellowcap@users.noreply.github.com>2017-06-16 17:09:05 +0100
committerTim Graham <timograham@gmail.com>2017-06-16 12:09:05 -0400
commit6f44f714c92d2966dca390ebd3054e5fb0bb0c80 (patch)
treeab7b370f9832acf91fba4e97d20a6229887b1779 /django
parentf6800a081afa27eaee9229ad5ccfd86c6c61496d (diff)
Fixed #28300 -- Allowed GDALRasters to use the vsimem filesystem.
Thanks Tim Graham for the review and edits.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/gis/gdal/prototypes/raster.py8
-rw-r--r--django/contrib/gis/gdal/raster/const.py10
-rw-r--r--django/contrib/gis/gdal/raster/source.py63
3 files changed, 76 insertions, 5 deletions
diff --git a/django/contrib/gis/gdal/prototypes/raster.py b/django/contrib/gis/gdal/prototypes/raster.py
index 7e95cbc9a4..72702ab178 100644
--- a/django/contrib/gis/gdal/prototypes/raster.py
+++ b/django/contrib/gis/gdal/prototypes/raster.py
@@ -2,7 +2,7 @@
This module houses the ctypes function prototypes for GDAL DataSource (raster)
related data structures.
"""
-from ctypes import POINTER, c_char_p, c_double, c_int, c_void_p
+from ctypes import POINTER, c_bool, c_char_p, c_double, c_int, c_void_p
from functools import partial
from django.contrib.gis.gdal.libgdal import GDAL_VERSION, std_call
@@ -102,3 +102,9 @@ auto_create_warped_vrt = voidptr_output(
std_call('GDALAutoCreateWarpedVRT'),
[c_void_p, c_char_p, c_char_p, c_int, c_double, c_void_p]
)
+
+# Create VSI gdal raster files from in-memory buffers.
+# http://gdal.org/cpl__vsi_8h.html
+create_vsi_file_from_mem_buffer = voidptr_output(std_call('VSIFileFromMemBuffer'), [c_char_p, c_void_p, c_int, c_int])
+get_mem_buffer_from_vsi_file = voidptr_output(std_call('VSIGetMemFileBuffer'), [c_char_p, POINTER(c_int), c_bool])
+unlink_vsi_file = int_output(std_call('VSIUnlink'), [c_char_p])
diff --git a/django/contrib/gis/gdal/raster/const.py b/django/contrib/gis/gdal/raster/const.py
index 38f19e294f..8b2656d33f 100644
--- a/django/contrib/gis/gdal/raster/const.py
+++ b/django/contrib/gis/gdal/raster/const.py
@@ -43,3 +43,13 @@ GDAL_RESAMPLE_ALGORITHMS = {
'Average': 5,
'Mode': 6,
}
+
+# Fixed base path for buffer-based GDAL in-memory files.
+VSI_FILESYSTEM_BASE_PATH = '/vsimem/'
+
+# Should the memory file system take ownership of the buffer, freeing it when
+# the file is deleted? (No, GDALRaster.__del__() will delete the buffer.)
+VSI_TAKE_BUFFER_OWNERSHIP = False
+
+# Should a VSI file be removed when retrieving its buffer?
+VSI_DELETE_BUFFER_ON_READ = False
diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py
index e871fe4cf5..358bb44b8f 100644
--- a/django/contrib/gis/gdal/raster/source.py
+++ b/django/contrib/gis/gdal/raster/source.py
@@ -1,13 +1,20 @@
import json
import os
-from ctypes import addressof, byref, c_char_p, c_double, c_void_p
+import sys
+import uuid
+from ctypes import (
+ addressof, byref, c_buffer, c_char_p, c_double, c_int, c_void_p, string_at,
+)
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 BandList
from django.contrib.gis.gdal.raster.base import GDALRasterBase
-from django.contrib.gis.gdal.raster.const import GDAL_RESAMPLE_ALGORITHMS
+from django.contrib.gis.gdal.raster.const import (
+ GDAL_RESAMPLE_ALGORITHMS, VSI_DELETE_BUFFER_ON_READ,
+ VSI_FILESYSTEM_BASE_PATH, VSI_TAKE_BUFFER_OWNERSHIP,
+)
from django.contrib.gis.gdal.srs import SpatialReference, SRSException
from django.contrib.gis.geometry.regex import json_regex
from django.utils.encoding import force_bytes, force_text
@@ -66,13 +73,36 @@ class GDALRaster(GDALRasterBase):
# If input is a valid file path, try setting file as source.
if isinstance(ds_input, str):
- if not os.path.exists(ds_input):
- raise GDALException('Unable to read raster source input "{}"'.format(ds_input))
try:
# GDALOpen will auto-detect the data source type.
self._ptr = capi.open_ds(force_bytes(ds_input), self._write)
except GDALException as err:
raise GDALException('Could not open the datasource at "{}" ({}).'.format(ds_input, err))
+ elif isinstance(ds_input, bytes):
+ # Create a new raster in write mode.
+ self._write = 1
+ # Get size of buffer.
+ size = sys.getsizeof(ds_input)
+ # Pass data to ctypes, keeping a reference to the ctypes object so
+ # that the vsimem file remains available until the GDALRaster is
+ # deleted.
+ self._ds_input = c_buffer(ds_input)
+ # Create random name to reference in vsimem filesystem.
+ vsi_path = os.path.join(VSI_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
+ # Create vsimem file from buffer.
+ capi.create_vsi_file_from_mem_buffer(
+ force_bytes(vsi_path),
+ byref(self._ds_input),
+ size,
+ VSI_TAKE_BUFFER_OWNERSHIP,
+ )
+ # Open the new vsimem file as a GDALRaster.
+ try:
+ self._ptr = capi.open_ds(force_bytes(vsi_path), self._write)
+ except GDALException:
+ # Remove the broken file from the VSI filesystem.
+ capi.unlink_vsi_file(force_bytes(vsi_path))
+ raise GDALException('Failed creating VSI raster from the input buffer.')
elif isinstance(ds_input, dict):
# A new raster needs to be created in write mode
self._write = 1
@@ -151,6 +181,12 @@ class GDALRaster(GDALRasterBase):
else:
raise GDALException('Invalid data source input type: "{}".'.format(type(ds_input)))
+ def __del__(self):
+ if self.is_vsi_based:
+ # Remove the temporary file from the VSI in-memory filesystem.
+ capi.unlink_vsi_file(force_bytes(self.name))
+ super().__del__()
+
def __str__(self):
return self.name
@@ -173,6 +209,25 @@ class GDALRaster(GDALRasterBase):
capi.flush_ds(self._ptr)
@property
+ def vsi_buffer(self):
+ if not self.is_vsi_based:
+ return None
+ # Prepare an integer that will contain the buffer length.
+ out_length = c_int()
+ # Get the data using the vsi file name.
+ dat = capi.get_mem_buffer_from_vsi_file(
+ force_bytes(self.name),
+ byref(out_length),
+ VSI_DELETE_BUFFER_ON_READ,
+ )
+ # Read the full buffer pointer.
+ return string_at(dat, out_length.value)
+
+ @cached_property
+ def is_vsi_based(self):
+ return self.name.startswith(VSI_FILESYSTEM_BASE_PATH)
+
+ @property
def name(self):
"""
Return the name of this raster. Corresponds to filename