Ajoutez des fichiers projet.
This commit is contained in:
250
venv/Lib/site-packages/django/contrib/gis/gdal/raster/band.py
Normal file
250
venv/Lib/site-packages/django/contrib/gis/gdal/raster/band.py
Normal file
@@ -0,0 +1,250 @@
|
||||
from ctypes import byref, c_double, c_int, c_void_p
|
||||
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
from django.contrib.gis.gdal.prototypes import raster as capi
|
||||
from django.contrib.gis.gdal.raster.base import GDALRasterBase
|
||||
from django.contrib.gis.shortcuts import numpy
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from .const import (
|
||||
GDAL_COLOR_TYPES, GDAL_INTEGER_TYPES, GDAL_PIXEL_TYPES, GDAL_TO_CTYPES,
|
||||
)
|
||||
|
||||
|
||||
class GDALBand(GDALRasterBase):
|
||||
"""
|
||||
Wrap a GDAL raster band, needs to be obtained from a GDALRaster object.
|
||||
"""
|
||||
def __init__(self, source, index):
|
||||
self.source = source
|
||||
self._ptr = capi.get_ds_raster_band(source._ptr, index)
|
||||
|
||||
def _flush(self):
|
||||
"""
|
||||
Call the flush method on the Band's parent raster and force a refresh
|
||||
of the statistics attribute when requested the next time.
|
||||
"""
|
||||
self.source._flush()
|
||||
self._stats_refresh = True
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
Return the description string of the band.
|
||||
"""
|
||||
return force_str(capi.get_band_description(self._ptr))
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""
|
||||
Width (X axis) in pixels of the band.
|
||||
"""
|
||||
return capi.get_band_xsize(self._ptr)
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""
|
||||
Height (Y axis) in pixels of the band.
|
||||
"""
|
||||
return capi.get_band_ysize(self._ptr)
|
||||
|
||||
@property
|
||||
def pixel_count(self):
|
||||
"""
|
||||
Return the total number of pixels in this band.
|
||||
"""
|
||||
return self.width * self.height
|
||||
|
||||
_stats_refresh = False
|
||||
|
||||
def statistics(self, refresh=False, approximate=False):
|
||||
"""
|
||||
Compute statistics on the pixel values of this band.
|
||||
|
||||
The return value is a tuple with the following structure:
|
||||
(minimum, maximum, mean, standard deviation).
|
||||
|
||||
If approximate=True, the statistics may be computed based on overviews
|
||||
or a subset of image tiles.
|
||||
|
||||
If refresh=True, the statistics will be computed from the data directly,
|
||||
and the cache will be updated where applicable.
|
||||
|
||||
For empty bands (where all pixel values are nodata), all statistics
|
||||
values are returned as None.
|
||||
|
||||
For raster formats using Persistent Auxiliary Metadata (PAM) services,
|
||||
the statistics might be cached in an auxiliary file.
|
||||
"""
|
||||
# Prepare array with arguments for capi function
|
||||
smin, smax, smean, sstd = c_double(), c_double(), c_double(), c_double()
|
||||
stats_args = [
|
||||
self._ptr, c_int(approximate), byref(smin), byref(smax),
|
||||
byref(smean), byref(sstd), c_void_p(), c_void_p(),
|
||||
]
|
||||
|
||||
if refresh or self._stats_refresh:
|
||||
func = capi.compute_band_statistics
|
||||
else:
|
||||
# Add additional argument to force computation if there is no
|
||||
# existing PAM file to take the values from.
|
||||
force = True
|
||||
stats_args.insert(2, c_int(force))
|
||||
func = capi.get_band_statistics
|
||||
|
||||
# Computation of statistics fails for empty bands.
|
||||
try:
|
||||
func(*stats_args)
|
||||
result = smin.value, smax.value, smean.value, sstd.value
|
||||
except GDALException:
|
||||
result = (None, None, None, None)
|
||||
|
||||
self._stats_refresh = False
|
||||
|
||||
return result
|
||||
|
||||
@property
|
||||
def min(self):
|
||||
"""
|
||||
Return the minimum pixel value for this band.
|
||||
"""
|
||||
return self.statistics()[0]
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
"""
|
||||
Return the maximum pixel value for this band.
|
||||
"""
|
||||
return self.statistics()[1]
|
||||
|
||||
@property
|
||||
def mean(self):
|
||||
"""
|
||||
Return the mean of all pixel values of this band.
|
||||
"""
|
||||
return self.statistics()[2]
|
||||
|
||||
@property
|
||||
def std(self):
|
||||
"""
|
||||
Return the standard deviation of all pixel values of this band.
|
||||
"""
|
||||
return self.statistics()[3]
|
||||
|
||||
@property
|
||||
def nodata_value(self):
|
||||
"""
|
||||
Return the nodata value for this band, or None if it isn't set.
|
||||
"""
|
||||
# Get value and nodata exists flag
|
||||
nodata_exists = c_int()
|
||||
value = capi.get_band_nodata_value(self._ptr, nodata_exists)
|
||||
if not nodata_exists:
|
||||
value = None
|
||||
# If the pixeltype is an integer, convert to int
|
||||
elif self.datatype() in GDAL_INTEGER_TYPES:
|
||||
value = int(value)
|
||||
return value
|
||||
|
||||
@nodata_value.setter
|
||||
def nodata_value(self, value):
|
||||
"""
|
||||
Set the nodata value for this band.
|
||||
"""
|
||||
if value is None:
|
||||
capi.delete_band_nodata_value(self._ptr)
|
||||
elif not isinstance(value, (int, float)):
|
||||
raise ValueError('Nodata value must be numeric or None.')
|
||||
else:
|
||||
capi.set_band_nodata_value(self._ptr, value)
|
||||
self._flush()
|
||||
|
||||
def datatype(self, as_string=False):
|
||||
"""
|
||||
Return the GDAL Pixel Datatype for this band.
|
||||
"""
|
||||
dtype = capi.get_band_datatype(self._ptr)
|
||||
if as_string:
|
||||
dtype = GDAL_PIXEL_TYPES[dtype]
|
||||
return dtype
|
||||
|
||||
def color_interp(self, as_string=False):
|
||||
"""Return the GDAL color interpretation for this band."""
|
||||
color = capi.get_band_color_interp(self._ptr)
|
||||
if as_string:
|
||||
color = GDAL_COLOR_TYPES[color]
|
||||
return color
|
||||
|
||||
def data(self, data=None, offset=None, size=None, shape=None, as_memoryview=False):
|
||||
"""
|
||||
Read or writes pixel values for this band. Blocks of data can
|
||||
be accessed by specifying the width, height and offset of the
|
||||
desired block. The same specification can be used to update
|
||||
parts of a raster by providing an array of values.
|
||||
|
||||
Allowed input data types are bytes, memoryview, list, tuple, and array.
|
||||
"""
|
||||
offset = offset or (0, 0)
|
||||
size = size or (self.width - offset[0], self.height - offset[1])
|
||||
shape = shape or size
|
||||
if any(x <= 0 for x in size):
|
||||
raise ValueError('Offset too big for this raster.')
|
||||
|
||||
if size[0] > self.width or size[1] > self.height:
|
||||
raise ValueError('Size is larger than raster.')
|
||||
|
||||
# Create ctypes type array generator
|
||||
ctypes_array = GDAL_TO_CTYPES[self.datatype()] * (shape[0] * shape[1])
|
||||
|
||||
if data is None:
|
||||
# Set read mode
|
||||
access_flag = 0
|
||||
# Prepare empty ctypes array
|
||||
data_array = ctypes_array()
|
||||
else:
|
||||
# Set write mode
|
||||
access_flag = 1
|
||||
|
||||
# Instantiate ctypes array holding the input data
|
||||
if isinstance(data, (bytes, memoryview)) or (numpy and isinstance(data, numpy.ndarray)):
|
||||
data_array = ctypes_array.from_buffer_copy(data)
|
||||
else:
|
||||
data_array = ctypes_array(*data)
|
||||
|
||||
# Access band
|
||||
capi.band_io(self._ptr, access_flag, offset[0], offset[1],
|
||||
size[0], size[1], byref(data_array), shape[0],
|
||||
shape[1], self.datatype(), 0, 0)
|
||||
|
||||
# Return data as numpy array if possible, otherwise as list
|
||||
if data is None:
|
||||
if as_memoryview:
|
||||
return memoryview(data_array)
|
||||
elif numpy:
|
||||
# reshape() needs a reshape parameter with the height first.
|
||||
return numpy.frombuffer(
|
||||
data_array, dtype=numpy.dtype(data_array)
|
||||
).reshape(tuple(reversed(size)))
|
||||
else:
|
||||
return list(data_array)
|
||||
else:
|
||||
self._flush()
|
||||
|
||||
|
||||
class BandList(list):
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
super().__init__()
|
||||
|
||||
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)
|
@@ -0,0 +1,75 @@
|
||||
from django.contrib.gis.gdal.base import GDALBase
|
||||
from django.contrib.gis.gdal.prototypes import raster as capi
|
||||
|
||||
|
||||
class GDALRasterBase(GDALBase):
|
||||
"""
|
||||
Attributes that exist on both GDALRaster and GDALBand.
|
||||
"""
|
||||
@property
|
||||
def metadata(self):
|
||||
"""
|
||||
Return the metadata for this raster or band. The return value is a
|
||||
nested dictionary, where the first-level key is the metadata domain and
|
||||
the second-level is the metadata item names and values for that domain.
|
||||
"""
|
||||
# The initial metadata domain list contains the default domain.
|
||||
# The default is returned if domain name is None.
|
||||
domain_list = ['DEFAULT']
|
||||
|
||||
# Get additional metadata domains from the raster.
|
||||
meta_list = capi.get_ds_metadata_domain_list(self._ptr)
|
||||
if meta_list:
|
||||
# The number of domains is unknown, so retrieve data until there
|
||||
# are no more values in the ctypes array.
|
||||
counter = 0
|
||||
domain = meta_list[counter]
|
||||
while domain:
|
||||
domain_list.append(domain.decode())
|
||||
counter += 1
|
||||
domain = meta_list[counter]
|
||||
|
||||
# Free domain list array.
|
||||
capi.free_dsl(meta_list)
|
||||
|
||||
# Retrieve metadata values for each domain.
|
||||
result = {}
|
||||
for domain in domain_list:
|
||||
# Get metadata for this domain.
|
||||
data = capi.get_ds_metadata(
|
||||
self._ptr,
|
||||
(None if domain == 'DEFAULT' else domain.encode()),
|
||||
)
|
||||
if not data:
|
||||
continue
|
||||
# The number of metadata items is unknown, so retrieve data until
|
||||
# there are no more values in the ctypes array.
|
||||
domain_meta = {}
|
||||
counter = 0
|
||||
item = data[counter]
|
||||
while item:
|
||||
key, val = item.decode().split('=')
|
||||
domain_meta[key] = val
|
||||
counter += 1
|
||||
item = data[counter]
|
||||
# The default domain values are returned if domain is None.
|
||||
result[domain or 'DEFAULT'] = domain_meta
|
||||
return result
|
||||
|
||||
@metadata.setter
|
||||
def metadata(self, value):
|
||||
"""
|
||||
Set the metadata. Update only the domains that are contained in the
|
||||
value dictionary.
|
||||
"""
|
||||
# Loop through domains.
|
||||
for domain, metadata in value.items():
|
||||
# Set the domain to None for the default, otherwise encode.
|
||||
domain = None if domain == 'DEFAULT' else domain.encode()
|
||||
# Set each metadata entry separately.
|
||||
for meta_name, meta_value in metadata.items():
|
||||
capi.set_ds_metadata_item(
|
||||
self._ptr, meta_name.encode(),
|
||||
meta_value.encode() if meta_value else None,
|
||||
domain,
|
||||
)
|
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
GDAL - Constant definitions
|
||||
"""
|
||||
from ctypes import (
|
||||
c_double, c_float, c_int16, c_int32, c_ubyte, c_uint16, c_uint32,
|
||||
)
|
||||
|
||||
# See https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType
|
||||
GDAL_PIXEL_TYPES = {
|
||||
0: 'GDT_Unknown', # Unknown or unspecified type
|
||||
1: 'GDT_Byte', # Eight bit unsigned integer
|
||||
2: 'GDT_UInt16', # Sixteen bit unsigned integer
|
||||
3: 'GDT_Int16', # Sixteen bit signed integer
|
||||
4: 'GDT_UInt32', # Thirty-two bit unsigned integer
|
||||
5: 'GDT_Int32', # Thirty-two bit signed integer
|
||||
6: 'GDT_Float32', # Thirty-two bit floating point
|
||||
7: 'GDT_Float64', # Sixty-four bit floating point
|
||||
8: 'GDT_CInt16', # Complex Int16
|
||||
9: 'GDT_CInt32', # Complex Int32
|
||||
10: 'GDT_CFloat32', # Complex Float32
|
||||
11: 'GDT_CFloat64', # Complex Float64
|
||||
}
|
||||
|
||||
# A list of gdal datatypes that are integers.
|
||||
GDAL_INTEGER_TYPES = [1, 2, 3, 4, 5]
|
||||
|
||||
# Lookup values to convert GDAL pixel type indices into ctypes objects.
|
||||
# The GDAL band-io works with ctypes arrays to hold data to be written
|
||||
# or to hold the space for data to be read into. The lookup below helps
|
||||
# selecting the right ctypes object for a given gdal pixel type.
|
||||
GDAL_TO_CTYPES = [
|
||||
None, c_ubyte, c_uint16, c_int16, c_uint32, c_int32,
|
||||
c_float, c_double, None, None, None, None
|
||||
]
|
||||
|
||||
# List of resampling algorithms that can be used to warp a GDALRaster.
|
||||
GDAL_RESAMPLE_ALGORITHMS = {
|
||||
'NearestNeighbour': 0,
|
||||
'Bilinear': 1,
|
||||
'Cubic': 2,
|
||||
'CubicSpline': 3,
|
||||
'Lanczos': 4,
|
||||
'Average': 5,
|
||||
'Mode': 6,
|
||||
}
|
||||
|
||||
# See https://gdal.org/api/raster_c_api.html#_CPPv415GDALColorInterp
|
||||
GDAL_COLOR_TYPES = {
|
||||
0: 'GCI_Undefined', # Undefined, default value, i.e. not known
|
||||
1: 'GCI_GrayIndex', # Grayscale
|
||||
2: 'GCI_PaletteIndex', # Paletted
|
||||
3: 'GCI_RedBand', # Red band of RGBA image
|
||||
4: 'GCI_GreenBand', # Green band of RGBA image
|
||||
5: 'GCI_BlueBand', # Blue band of RGBA image
|
||||
6: 'GCI_AlphaBand', # Alpha (0=transparent, 255=opaque)
|
||||
7: 'GCI_HueBand', # Hue band of HLS image
|
||||
8: 'GCI_SaturationBand', # Saturation band of HLS image
|
||||
9: 'GCI_LightnessBand', # Lightness band of HLS image
|
||||
10: 'GCI_CyanBand', # Cyan band of CMYK image
|
||||
11: 'GCI_MagentaBand', # Magenta band of CMYK image
|
||||
12: 'GCI_YellowBand', # Yellow band of CMYK image
|
||||
13: 'GCI_BlackBand', # Black band of CMLY image
|
||||
14: 'GCI_YCbCr_YBand', # Y Luminance
|
||||
15: 'GCI_YCbCr_CbBand', # Cb Chroma
|
||||
16: 'GCI_YCbCr_CrBand', # Cr Chroma, also GCI_Max
|
||||
}
|
||||
|
||||
# GDAL virtual filesystems prefix.
|
||||
VSI_FILESYSTEM_PREFIX = '/vsi'
|
||||
|
||||
# Fixed base path for buffer-based GDAL in-memory files.
|
||||
VSI_MEM_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
|
506
venv/Lib/site-packages/django/contrib/gis/gdal/raster/source.py
Normal file
506
venv/Lib/site-packages/django/contrib/gis/gdal/raster/source.py
Normal file
@@ -0,0 +1,506 @@
|
||||
import json
|
||||
import os
|
||||
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, VSI_DELETE_BUFFER_ON_READ, VSI_FILESYSTEM_PREFIX,
|
||||
VSI_MEM_FILESYSTEM_BASE_PATH, VSI_TAKE_BUFFER_OWNERSHIP,
|
||||
)
|
||||
from django.contrib.gis.gdal.srs import SpatialReference, SRSException
|
||||
from django.contrib.gis.geometry import json_regex
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class TransformPoint(list):
|
||||
indices = {
|
||||
'origin': (0, 3),
|
||||
'scale': (1, 5),
|
||||
'skew': (2, 4),
|
||||
}
|
||||
|
||||
def __init__(self, raster, prop):
|
||||
x = raster.geotransform[self.indices[prop][0]]
|
||||
y = raster.geotransform[self.indices[prop][1]]
|
||||
super().__init__([x, y])
|
||||
self._raster = raster
|
||||
self._prop = prop
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self[0]
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
gtf = self._raster.geotransform
|
||||
gtf[self.indices[self._prop][0]] = value
|
||||
self._raster.geotransform = gtf
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self[1]
|
||||
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
gtf = self._raster.geotransform
|
||||
gtf[self.indices[self._prop][1]] = value
|
||||
self._raster.geotransform = gtf
|
||||
|
||||
|
||||
class GDALRaster(GDALRasterBase):
|
||||
"""
|
||||
Wrap a raster GDAL Data Source object.
|
||||
"""
|
||||
destructor = capi.close_ds
|
||||
|
||||
def __init__(self, ds_input, write=False):
|
||||
self._write = 1 if write else 0
|
||||
Driver.ensure_registered()
|
||||
|
||||
# Preprocess json inputs. This converts json strings to dictionaries,
|
||||
# which are parsed below the same way as direct dictionary inputs.
|
||||
if isinstance(ds_input, str) and json_regex.match(ds_input):
|
||||
ds_input = json.loads(ds_input)
|
||||
|
||||
# If input is a valid file path, try setting file as source.
|
||||
if isinstance(ds_input, str):
|
||||
if (
|
||||
not ds_input.startswith(VSI_FILESYSTEM_PREFIX) and
|
||||
not os.path.exists(ds_input)
|
||||
):
|
||||
raise GDALException(
|
||||
'Unable to read raster source input "%s".' % 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_MEM_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
|
||||
|
||||
# Create driver (in memory by default)
|
||||
driver = Driver(ds_input.get('driver', 'MEM'))
|
||||
|
||||
# For out of memory drivers, check filename argument
|
||||
if driver.name != 'MEM' and 'name' not in ds_input:
|
||||
raise GDALException('Specify name for creation of raster with driver "{}".'.format(driver.name))
|
||||
|
||||
# Check if width and height where specified
|
||||
if 'width' not in ds_input or 'height' not in ds_input:
|
||||
raise GDALException('Specify width and height attributes for JSON or dict input.')
|
||||
|
||||
# Check if srid was specified
|
||||
if 'srid' not in ds_input:
|
||||
raise GDALException('Specify srid for JSON or dict input.')
|
||||
|
||||
# Create null terminated gdal options array.
|
||||
papsz_options = []
|
||||
for key, val in ds_input.get('papsz_options', {}).items():
|
||||
option = '{}={}'.format(key, val)
|
||||
papsz_options.append(option.upper().encode())
|
||||
papsz_options.append(None)
|
||||
|
||||
# Convert papszlist to ctypes array.
|
||||
papsz_options = (c_char_p * len(papsz_options))(*papsz_options)
|
||||
|
||||
# Create GDAL Raster
|
||||
self._ptr = capi.create_ds(
|
||||
driver._ptr,
|
||||
force_bytes(ds_input.get('name', '')),
|
||||
ds_input['width'],
|
||||
ds_input['height'],
|
||||
ds_input.get('nr_of_bands', len(ds_input.get('bands', []))),
|
||||
ds_input.get('datatype', 6),
|
||||
byref(papsz_options),
|
||||
)
|
||||
|
||||
# Set band data if provided
|
||||
for i, band_input in enumerate(ds_input.get('bands', [])):
|
||||
band = self.bands[i]
|
||||
if 'nodata_value' in band_input:
|
||||
band.nodata_value = band_input['nodata_value']
|
||||
# Instantiate band filled with nodata values if only
|
||||
# partial input data has been provided.
|
||||
if band.nodata_value is not None and (
|
||||
'data' not in band_input or
|
||||
'size' in band_input or
|
||||
'shape' in band_input):
|
||||
band.data(data=(band.nodata_value,), shape=(1, 1))
|
||||
# Set band data values from input.
|
||||
band.data(
|
||||
data=band_input.get('data'),
|
||||
size=band_input.get('size'),
|
||||
shape=band_input.get('shape'),
|
||||
offset=band_input.get('offset'),
|
||||
)
|
||||
|
||||
# Set SRID
|
||||
self.srs = ds_input.get('srid')
|
||||
|
||||
# Set additional properties if provided
|
||||
if 'origin' in ds_input:
|
||||
self.origin.x, self.origin.y = ds_input['origin']
|
||||
|
||||
if 'scale' in ds_input:
|
||||
self.scale.x, self.scale.y = ds_input['scale']
|
||||
|
||||
if 'skew' in ds_input:
|
||||
self.skew.x, self.skew.y = ds_input['skew']
|
||||
elif isinstance(ds_input, c_void_p):
|
||||
# Instantiate the object using an existing pointer to a gdal raster.
|
||||
self._ptr = ds_input
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Short-hand representation because WKB may be very large.
|
||||
"""
|
||||
return '<Raster object at %s>' % hex(addressof(self._ptr))
|
||||
|
||||
def _flush(self):
|
||||
"""
|
||||
Flush all data from memory into the source file if it exists.
|
||||
The data that needs flushing are geotransforms, coordinate systems,
|
||||
nodata_values and pixel values. This function will be called
|
||||
automatically wherever it is needed.
|
||||
"""
|
||||
# Raise an Exception if the value is being changed in read mode.
|
||||
if not self._write:
|
||||
raise GDALException('Raster needs to be opened in write mode to change values.')
|
||||
capi.flush_ds(self._ptr)
|
||||
|
||||
@property
|
||||
def vsi_buffer(self):
|
||||
if not (
|
||||
self.is_vsi_based and
|
||||
self.name.startswith(VSI_MEM_FILESYSTEM_BASE_PATH)
|
||||
):
|
||||
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._ptr and self.name.startswith(VSI_FILESYSTEM_PREFIX)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Return the name of this raster. Corresponds to filename
|
||||
for file-based rasters.
|
||||
"""
|
||||
return force_str(capi.get_ds_description(self._ptr))
|
||||
|
||||
@cached_property
|
||||
def driver(self):
|
||||
"""
|
||||
Return the GDAL Driver used for this raster.
|
||||
"""
|
||||
ds_driver = capi.get_ds_driver(self._ptr)
|
||||
return Driver(ds_driver)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""
|
||||
Width (X axis) in pixels.
|
||||
"""
|
||||
return capi.get_ds_xsize(self._ptr)
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""
|
||||
Height (Y axis) in pixels.
|
||||
"""
|
||||
return capi.get_ds_ysize(self._ptr)
|
||||
|
||||
@property
|
||||
def srs(self):
|
||||
"""
|
||||
Return the SpatialReference used in this GDALRaster.
|
||||
"""
|
||||
try:
|
||||
wkt = capi.get_ds_projection_ref(self._ptr)
|
||||
if not wkt:
|
||||
return None
|
||||
return SpatialReference(wkt, srs_type='wkt')
|
||||
except SRSException:
|
||||
return None
|
||||
|
||||
@srs.setter
|
||||
def srs(self, value):
|
||||
"""
|
||||
Set the spatial reference used in this GDALRaster. The input can be
|
||||
a SpatialReference or any parameter accepted by the SpatialReference
|
||||
constructor.
|
||||
"""
|
||||
if isinstance(value, SpatialReference):
|
||||
srs = value
|
||||
elif isinstance(value, (int, str)):
|
||||
srs = SpatialReference(value)
|
||||
else:
|
||||
raise ValueError('Could not create a SpatialReference from input.')
|
||||
capi.set_ds_projection_ref(self._ptr, srs.wkt.encode())
|
||||
self._flush()
|
||||
|
||||
@property
|
||||
def srid(self):
|
||||
"""
|
||||
Shortcut to access the srid of this GDALRaster.
|
||||
"""
|
||||
return self.srs.srid
|
||||
|
||||
@srid.setter
|
||||
def srid(self, value):
|
||||
"""
|
||||
Shortcut to set this GDALRaster's srs from an srid.
|
||||
"""
|
||||
self.srs = value
|
||||
|
||||
@property
|
||||
def geotransform(self):
|
||||
"""
|
||||
Return the geotransform of the data source.
|
||||
Return the default geotransform if it does not exist or has not been
|
||||
set previously. The default is [0.0, 1.0, 0.0, 0.0, 0.0, -1.0].
|
||||
"""
|
||||
# Create empty ctypes double array for data
|
||||
gtf = (c_double * 6)()
|
||||
capi.get_ds_geotransform(self._ptr, byref(gtf))
|
||||
return list(gtf)
|
||||
|
||||
@geotransform.setter
|
||||
def geotransform(self, values):
|
||||
"Set the geotransform for the data source."
|
||||
if len(values) != 6 or not all(isinstance(x, (int, float)) for x in values):
|
||||
raise ValueError('Geotransform must consist of 6 numeric values.')
|
||||
# Create ctypes double array with input and write data
|
||||
values = (c_double * 6)(*values)
|
||||
capi.set_ds_geotransform(self._ptr, byref(values))
|
||||
self._flush()
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
"""
|
||||
Coordinates of the raster origin.
|
||||
"""
|
||||
return TransformPoint(self, 'origin')
|
||||
|
||||
@property
|
||||
def scale(self):
|
||||
"""
|
||||
Pixel scale in units of the raster projection.
|
||||
"""
|
||||
return TransformPoint(self, 'scale')
|
||||
|
||||
@property
|
||||
def skew(self):
|
||||
"""
|
||||
Skew of pixels (rotation parameters).
|
||||
"""
|
||||
return TransformPoint(self, 'skew')
|
||||
|
||||
@property
|
||||
def extent(self):
|
||||
"""
|
||||
Return the extent as a 4-tuple (xmin, ymin, xmax, ymax).
|
||||
"""
|
||||
# Calculate boundary values based on scale and size
|
||||
xval = self.origin.x + self.scale.x * self.width
|
||||
yval = self.origin.y + self.scale.y * self.height
|
||||
# Calculate min and max values
|
||||
xmin = min(xval, self.origin.x)
|
||||
xmax = max(xval, self.origin.x)
|
||||
ymin = min(yval, self.origin.y)
|
||||
ymax = max(yval, self.origin.y)
|
||||
|
||||
return xmin, ymin, xmax, ymax
|
||||
|
||||
@property
|
||||
def bands(self):
|
||||
return BandList(self)
|
||||
|
||||
def warp(self, ds_input, resampling='NearestNeighbour', max_error=0.0):
|
||||
"""
|
||||
Return a warped GDALRaster with the given input characteristics.
|
||||
|
||||
The input is expected to be a dictionary containing the parameters
|
||||
of the target raster. Allowed values are width, height, SRID, origin,
|
||||
scale, skew, datatype, driver, and name (filename).
|
||||
|
||||
By default, the warp functions keeps all parameters equal to the values
|
||||
of the original source raster. For the name of the target raster, the
|
||||
name of the source raster will be used and appended with
|
||||
_copy. + source_driver_name.
|
||||
|
||||
In addition, the resampling algorithm can be specified with the "resampling"
|
||||
input parameter. The default is NearestNeighbor. For a list of all options
|
||||
consult the GDAL_RESAMPLE_ALGORITHMS constant.
|
||||
"""
|
||||
# Get the parameters defining the geotransform, srid, and size of the raster
|
||||
ds_input.setdefault('width', self.width)
|
||||
ds_input.setdefault('height', self.height)
|
||||
ds_input.setdefault('srid', self.srs.srid)
|
||||
ds_input.setdefault('origin', self.origin)
|
||||
ds_input.setdefault('scale', self.scale)
|
||||
ds_input.setdefault('skew', self.skew)
|
||||
# Get the driver, name, and datatype of the target raster
|
||||
ds_input.setdefault('driver', self.driver.name)
|
||||
|
||||
if 'name' not in ds_input:
|
||||
ds_input['name'] = self.name + '_copy.' + self.driver.name
|
||||
|
||||
if 'datatype' not in ds_input:
|
||||
ds_input['datatype'] = self.bands[0].datatype()
|
||||
|
||||
# Instantiate raster bands filled with nodata values.
|
||||
ds_input['bands'] = [{'nodata_value': bnd.nodata_value} for bnd in self.bands]
|
||||
|
||||
# Create target raster
|
||||
target = GDALRaster(ds_input, write=True)
|
||||
|
||||
# Select resampling algorithm
|
||||
algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]
|
||||
|
||||
# Reproject image
|
||||
capi.reproject_image(
|
||||
self._ptr, self.srs.wkt.encode(),
|
||||
target._ptr, target.srs.wkt.encode(),
|
||||
algorithm, 0.0, max_error,
|
||||
c_void_p(), c_void_p(), c_void_p()
|
||||
)
|
||||
|
||||
# Make sure all data is written to file
|
||||
target._flush()
|
||||
|
||||
return target
|
||||
|
||||
def clone(self, name=None):
|
||||
"""Return a clone of this GDALRaster."""
|
||||
if name:
|
||||
clone_name = name
|
||||
elif self.driver.name != 'MEM':
|
||||
clone_name = self.name + '_copy.' + self.driver.name
|
||||
else:
|
||||
clone_name = os.path.join(VSI_MEM_FILESYSTEM_BASE_PATH, str(uuid.uuid4()))
|
||||
return GDALRaster(
|
||||
capi.copy_ds(
|
||||
self.driver._ptr,
|
||||
force_bytes(clone_name),
|
||||
self._ptr,
|
||||
c_int(),
|
||||
c_char_p(),
|
||||
c_void_p(),
|
||||
c_void_p(),
|
||||
),
|
||||
write=self._write,
|
||||
)
|
||||
|
||||
def transform(self, srs, driver=None, name=None, resampling='NearestNeighbour',
|
||||
max_error=0.0):
|
||||
"""
|
||||
Return a copy of this raster reprojected into the given spatial
|
||||
reference system.
|
||||
"""
|
||||
# Convert the resampling algorithm name into an algorithm id
|
||||
algorithm = GDAL_RESAMPLE_ALGORITHMS[resampling]
|
||||
|
||||
if isinstance(srs, SpatialReference):
|
||||
target_srs = srs
|
||||
elif isinstance(srs, (int, str)):
|
||||
target_srs = SpatialReference(srs)
|
||||
else:
|
||||
raise TypeError(
|
||||
'Transform only accepts SpatialReference, string, and integer '
|
||||
'objects.'
|
||||
)
|
||||
|
||||
if target_srs.srid == self.srid and (not driver or driver == self.driver.name):
|
||||
return self.clone(name)
|
||||
# Create warped virtual dataset in the target reference system
|
||||
target = capi.auto_create_warped_vrt(
|
||||
self._ptr, self.srs.wkt.encode(), target_srs.wkt.encode(),
|
||||
algorithm, max_error, c_void_p()
|
||||
)
|
||||
target = GDALRaster(target)
|
||||
|
||||
# Construct the target warp dictionary from the virtual raster
|
||||
data = {
|
||||
'srid': target_srs.srid,
|
||||
'width': target.width,
|
||||
'height': target.height,
|
||||
'origin': [target.origin.x, target.origin.y],
|
||||
'scale': [target.scale.x, target.scale.y],
|
||||
'skew': [target.skew.x, target.skew.y],
|
||||
}
|
||||
|
||||
# Set the driver and filepath if provided
|
||||
if driver:
|
||||
data['driver'] = driver
|
||||
|
||||
if name:
|
||||
data['name'] = name
|
||||
|
||||
# Warp the raster into new srid
|
||||
return self.warp(data, resampling=resampling, max_error=max_error)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""
|
||||
Return information about this raster in a string format equivalent
|
||||
to the output of the gdalinfo command line utility.
|
||||
"""
|
||||
return capi.get_ds_info(self.ptr, None).decode()
|
Reference in New Issue
Block a user