You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

layer.py 8.5 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. from ctypes import byref, c_double
  2. from django.contrib.gis.gdal.base import GDALBase
  3. from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
  4. from django.contrib.gis.gdal.error import (
  5. GDALException, OGRIndexError, SRSException,
  6. )
  7. from django.contrib.gis.gdal.feature import Feature
  8. from django.contrib.gis.gdal.field import OGRFieldTypes
  9. from django.contrib.gis.gdal.geometries import OGRGeometry
  10. from django.contrib.gis.gdal.geomtype import OGRGeomType
  11. from django.contrib.gis.gdal.prototypes import (
  12. ds as capi, geom as geom_api, srs as srs_api,
  13. )
  14. from django.contrib.gis.gdal.srs import SpatialReference
  15. from django.utils import six
  16. from django.utils.encoding import force_bytes, force_text
  17. from django.utils.six.moves import range
  18. # For more information, see the OGR C API source code:
  19. # http://www.gdal.org/ogr__api_8h.html
  20. #
  21. # The OGR_L_* routines are relevant here.
  22. class Layer(GDALBase):
  23. "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object."
  24. def __init__(self, layer_ptr, ds):
  25. """
  26. Initializes on an OGR C pointer to the Layer and the `DataSource` object
  27. that owns this layer. The `DataSource` object is required so that a
  28. reference to it is kept with this Layer. This prevents garbage
  29. collection of the `DataSource` while this Layer is still active.
  30. """
  31. if not layer_ptr:
  32. raise GDALException('Cannot create Layer, invalid pointer given')
  33. self.ptr = layer_ptr
  34. self._ds = ds
  35. self._ldefn = capi.get_layer_defn(self._ptr)
  36. # Does the Layer support random reading?
  37. self._random_read = self.test_capability(b'RandomRead')
  38. def __getitem__(self, index):
  39. "Gets the Feature at the specified index."
  40. if isinstance(index, six.integer_types):
  41. # An integer index was given -- we cannot do a check based on the
  42. # number of features because the beginning and ending feature IDs
  43. # are not guaranteed to be 0 and len(layer)-1, respectively.
  44. if index < 0:
  45. raise OGRIndexError('Negative indices are not allowed on OGR Layers.')
  46. return self._make_feature(index)
  47. elif isinstance(index, slice):
  48. # A slice was given
  49. start, stop, stride = index.indices(self.num_feat)
  50. return [self._make_feature(fid) for fid in range(start, stop, stride)]
  51. else:
  52. raise TypeError('Integers and slices may only be used when indexing OGR Layers.')
  53. def __iter__(self):
  54. "Iterates over each Feature in the Layer."
  55. # ResetReading() must be called before iteration is to begin.
  56. capi.reset_reading(self._ptr)
  57. for i in range(self.num_feat):
  58. yield Feature(capi.get_next_feature(self._ptr), self)
  59. def __len__(self):
  60. "The length is the number of features."
  61. return self.num_feat
  62. def __str__(self):
  63. "The string name of the layer."
  64. return self.name
  65. def _make_feature(self, feat_id):
  66. """
  67. Helper routine for __getitem__ that constructs a Feature from the given
  68. Feature ID. If the OGR Layer does not support random-access reading,
  69. then each feature of the layer will be incremented through until the
  70. a Feature is found matching the given feature ID.
  71. """
  72. if self._random_read:
  73. # If the Layer supports random reading, return.
  74. try:
  75. return Feature(capi.get_feature(self.ptr, feat_id), self)
  76. except GDALException:
  77. pass
  78. else:
  79. # Random access isn't supported, have to increment through
  80. # each feature until the given feature ID is encountered.
  81. for feat in self:
  82. if feat.fid == feat_id:
  83. return feat
  84. # Should have returned a Feature, raise an OGRIndexError.
  85. raise OGRIndexError('Invalid feature id: %s.' % feat_id)
  86. # #### Layer properties ####
  87. @property
  88. def extent(self):
  89. "Returns the extent (an Envelope) of this layer."
  90. env = OGREnvelope()
  91. capi.get_extent(self.ptr, byref(env), 1)
  92. return Envelope(env)
  93. @property
  94. def name(self):
  95. "Returns the name of this layer in the Data Source."
  96. name = capi.get_fd_name(self._ldefn)
  97. return force_text(name, self._ds.encoding, strings_only=True)
  98. @property
  99. def num_feat(self, force=1):
  100. "Returns the number of features in the Layer."
  101. return capi.get_feature_count(self.ptr, force)
  102. @property
  103. def num_fields(self):
  104. "Returns the number of fields in the Layer."
  105. return capi.get_field_count(self._ldefn)
  106. @property
  107. def geom_type(self):
  108. "Returns the geometry type (OGRGeomType) of the Layer."
  109. return OGRGeomType(capi.get_fd_geom_type(self._ldefn))
  110. @property
  111. def srs(self):
  112. "Returns the Spatial Reference used in this Layer."
  113. try:
  114. ptr = capi.get_layer_srs(self.ptr)
  115. return SpatialReference(srs_api.clone_srs(ptr))
  116. except SRSException:
  117. return None
  118. @property
  119. def fields(self):
  120. """
  121. Returns a list of string names corresponding to each of the Fields
  122. available in this Layer.
  123. """
  124. return [force_text(capi.get_field_name(capi.get_field_defn(self._ldefn, i)),
  125. self._ds.encoding, strings_only=True)
  126. for i in range(self.num_fields)]
  127. @property
  128. def field_types(self):
  129. """
  130. Returns a list of the types of fields in this Layer. For example,
  131. the list [OFTInteger, OFTReal, OFTString] would be returned for
  132. an OGR layer that had an integer, a floating-point, and string
  133. fields.
  134. """
  135. return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))]
  136. for i in range(self.num_fields)]
  137. @property
  138. def field_widths(self):
  139. "Returns a list of the maximum field widths for the features."
  140. return [capi.get_field_width(capi.get_field_defn(self._ldefn, i))
  141. for i in range(self.num_fields)]
  142. @property
  143. def field_precisions(self):
  144. "Returns the field precisions for the features."
  145. return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i))
  146. for i in range(self.num_fields)]
  147. def _get_spatial_filter(self):
  148. try:
  149. return OGRGeometry(geom_api.clone_geom(capi.get_spatial_filter(self.ptr)))
  150. except GDALException:
  151. return None
  152. def _set_spatial_filter(self, filter):
  153. if isinstance(filter, OGRGeometry):
  154. capi.set_spatial_filter(self.ptr, filter.ptr)
  155. elif isinstance(filter, (tuple, list)):
  156. if not len(filter) == 4:
  157. raise ValueError('Spatial filter list/tuple must have 4 elements.')
  158. # Map c_double onto params -- if a bad type is passed in it
  159. # will be caught here.
  160. xmin, ymin, xmax, ymax = map(c_double, filter)
  161. capi.set_spatial_filter_rect(self.ptr, xmin, ymin, xmax, ymax)
  162. elif filter is None:
  163. capi.set_spatial_filter(self.ptr, None)
  164. else:
  165. raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.')
  166. spatial_filter = property(_get_spatial_filter, _set_spatial_filter)
  167. # #### Layer Methods ####
  168. def get_fields(self, field_name):
  169. """
  170. Returns a list containing the given field name for every Feature
  171. in the Layer.
  172. """
  173. if field_name not in self.fields:
  174. raise GDALException('invalid field name: %s' % field_name)
  175. return [feat.get(field_name) for feat in self]
  176. def get_geoms(self, geos=False):
  177. """
  178. Returns a list containing the OGRGeometry for every Feature in
  179. the Layer.
  180. """
  181. if geos:
  182. from django.contrib.gis.geos import GEOSGeometry
  183. return [GEOSGeometry(feat.geom.wkb) for feat in self]
  184. else:
  185. return [feat.geom for feat in self]
  186. def test_capability(self, capability):
  187. """
  188. Returns a bool indicating whether the this Layer supports the given
  189. capability (a string). Valid capability strings include:
  190. 'RandomRead', 'SequentialWrite', 'RandomWrite', 'FastSpatialFilter',
  191. 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions',
  192. 'DeleteFeature', and 'FastSetNextByIndex'.
  193. """
  194. return bool(capi.test_capability(self.ptr, force_bytes(capability)))