source.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. from ctypes import addressof, byref, c_double
  2. import os
  3. from django.contrib.gis.gdal.base import GDALBase
  4. from django.contrib.gis.gdal.driver import Driver
  5. from django.contrib.gis.gdal.error import GDALException
  6. from django.contrib.gis.gdal.prototypes import raster as capi
  7. from django.contrib.gis.gdal.raster.band import GDALBand
  8. from django.contrib.gis.gdal.srs import SpatialReference, SRSException
  9. from django.utils import six
  10. from django.utils.six.moves import range
  11. from django.utils.encoding import (force_bytes, force_text,
  12. python_2_unicode_compatible)
  13. from django.utils.functional import cached_property
  14. class TransformPoint(list):
  15. indices = {
  16. 'origin': (0, 3),
  17. 'scale': (1, 5),
  18. 'skew': (2, 4),
  19. }
  20. def __init__(self, raster, prop):
  21. x = raster.geotransform[self.indices[prop][0]]
  22. y = raster.geotransform[self.indices[prop][1]]
  23. list.__init__(self, [x, y])
  24. self._raster = raster
  25. self._prop = prop
  26. @property
  27. def x(self):
  28. return self[0]
  29. @property
  30. def y(self):
  31. return self[1]
  32. @python_2_unicode_compatible
  33. class GDALRaster(GDALBase):
  34. """
  35. Wraps a raster GDAL Data Source object.
  36. """
  37. def __init__(self, ds_input, write=False):
  38. self._write = 1 if write else 0
  39. Driver.ensure_registered()
  40. # If input is a valid file path, try setting file as source.
  41. if isinstance(ds_input, six.string_types):
  42. if os.path.exists(ds_input):
  43. try:
  44. # GDALOpen will auto-detect the data source type.
  45. self.ptr = capi.open_ds(force_bytes(ds_input), self._write)
  46. except GDALException as err:
  47. raise GDALException('Could not open the datasource at "{}" ({}).'.format(
  48. ds_input, err))
  49. else:
  50. raise GDALException('Unable to read raster source input "{}"'.format(ds_input))
  51. else:
  52. raise GDALException('Invalid data source input type: "{}".'.format(type(ds_input)))
  53. def __del__(self):
  54. if self._ptr and capi:
  55. capi.close_ds(self._ptr)
  56. def __str__(self):
  57. return self.name
  58. def __repr__(self):
  59. """
  60. Short-hand representation because WKB may be very large.
  61. """
  62. return '<Raster object at %s>' % hex(addressof(self.ptr))
  63. @property
  64. def name(self):
  65. return force_text(capi.get_ds_description(self.ptr))
  66. @cached_property
  67. def driver(self):
  68. ds_driver = capi.get_ds_driver(self.ptr)
  69. return Driver(ds_driver)
  70. @property
  71. def width(self):
  72. """
  73. Width (X axis) in pixels.
  74. """
  75. return capi.get_ds_xsize(self.ptr)
  76. @property
  77. def height(self):
  78. """
  79. Height (Y axis) in pixels.
  80. """
  81. return capi.get_ds_ysize(self.ptr)
  82. @property
  83. def srs(self):
  84. """
  85. Returns the Spatial Reference used in this GDALRaster.
  86. """
  87. try:
  88. wkt = capi.get_ds_projection_ref(self.ptr)
  89. return SpatialReference(wkt, srs_type='wkt')
  90. except SRSException:
  91. return None
  92. @cached_property
  93. def geotransform(self):
  94. """
  95. Returns the geotransform of the data source.
  96. Returns the default geotransform if it does not exist or has not been
  97. set previously. The default is (0.0, 1.0, 0.0, 0.0, 0.0, -1.0).
  98. """
  99. # Create empty ctypes double array for data
  100. gtf = (c_double * 6)()
  101. capi.get_ds_geotransform(self.ptr, byref(gtf))
  102. return tuple(gtf)
  103. @property
  104. def origin(self):
  105. return TransformPoint(self, 'origin')
  106. @property
  107. def scale(self):
  108. return TransformPoint(self, 'scale')
  109. @property
  110. def skew(self):
  111. return TransformPoint(self, 'skew')
  112. @property
  113. def extent(self):
  114. """
  115. Returns the extent as a 4-tuple (xmin, ymin, xmax, ymax).
  116. """
  117. # Calculate boundary values based on scale and size
  118. xval = self.origin.x + self.scale.x * self.width
  119. yval = self.origin.y + self.scale.y * self.height
  120. # Calculate min and max values
  121. xmin = min(xval, self.origin.x)
  122. xmax = max(xval, self.origin.x)
  123. ymin = min(yval, self.origin.y)
  124. ymax = max(yval, self.origin.y)
  125. return xmin, ymin, xmax, ymax
  126. @cached_property
  127. def bands(self):
  128. bands = []
  129. for idx in range(1, capi.get_ds_raster_count(self.ptr) + 1):
  130. bands.append(GDALBand(self, idx))
  131. return bands