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.

ogrinspect.py 8.9 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. """
  2. This module is for inspecting OGR data sources and generating either
  3. models for GeoDjango and/or mapping dictionaries for use with the
  4. `LayerMapping` utility.
  5. """
  6. from django.contrib.gis.gdal import DataSource
  7. from django.contrib.gis.gdal.field import (
  8. OFTDate, OFTDateTime, OFTInteger, OFTInteger64, OFTReal, OFTString,
  9. OFTTime,
  10. )
  11. from django.utils import six
  12. from django.utils.six.moves import zip
  13. def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False):
  14. """
  15. Given a DataSource, generates a dictionary that may be used
  16. for invoking the LayerMapping utility.
  17. Keyword Arguments:
  18. `geom_name` => The name of the geometry field to use for the model.
  19. `layer_key` => The key for specifying which layer in the DataSource to use;
  20. defaults to 0 (the first layer). May be an integer index or a string
  21. identifier for the layer.
  22. `multi_geom` => Boolean (default: False) - specify as multigeometry.
  23. """
  24. if isinstance(data_source, six.string_types):
  25. # Instantiating the DataSource from the string.
  26. data_source = DataSource(data_source)
  27. elif isinstance(data_source, DataSource):
  28. pass
  29. else:
  30. raise TypeError('Data source parameter must be a string or a DataSource object.')
  31. # Creating the dictionary.
  32. _mapping = {}
  33. # Generating the field name for each field in the layer.
  34. for field in data_source[layer_key].fields:
  35. mfield = field.lower()
  36. if mfield[-1:] == '_':
  37. mfield += 'field'
  38. _mapping[mfield] = field
  39. gtype = data_source[layer_key].geom_type
  40. if multi_geom:
  41. gtype.to_multi()
  42. _mapping[geom_name] = str(gtype).upper()
  43. return _mapping
  44. def ogrinspect(*args, **kwargs):
  45. """
  46. Given a data source (either a string or a DataSource object) and a string
  47. model name this function will generate a GeoDjango model.
  48. Usage:
  49. >>> from django.contrib.gis.utils import ogrinspect
  50. >>> ogrinspect('/path/to/shapefile.shp','NewModel')
  51. ...will print model definition to stout
  52. or put this in a python script and use to redirect the output to a new
  53. model like:
  54. $ python generate_model.py > myapp/models.py
  55. # generate_model.py
  56. from django.contrib.gis.utils import ogrinspect
  57. shp_file = 'data/mapping_hacks/world_borders.shp'
  58. model_name = 'WorldBorders'
  59. print(ogrinspect(shp_file, model_name, multi_geom=True, srid=4326,
  60. geom_name='shapes', blank=True))
  61. Required Arguments
  62. `datasource` => string or DataSource object to file pointer
  63. `model name` => string of name of new model class to create
  64. Optional Keyword Arguments
  65. `geom_name` => For specifying the model name for the Geometry Field.
  66. Otherwise will default to `geom`
  67. `layer_key` => The key for specifying which layer in the DataSource to use;
  68. defaults to 0 (the first layer). May be an integer index or a string
  69. identifier for the layer.
  70. `srid` => The SRID to use for the Geometry Field. If it can be determined,
  71. the SRID of the datasource is used.
  72. `multi_geom` => Boolean (default: False) - specify as multigeometry.
  73. `name_field` => String - specifies a field name to return for the
  74. `__unicode__`/`__str__` function (which will be generated if specified).
  75. `imports` => Boolean (default: True) - set to False to omit the
  76. `from django.contrib.gis.db import models` code from the
  77. autogenerated models thus avoiding duplicated imports when building
  78. more than one model by batching ogrinspect()
  79. `decimal` => Boolean or sequence (default: False). When set to True
  80. all generated model fields corresponding to the `OFTReal` type will
  81. be `DecimalField` instead of `FloatField`. A sequence of specific
  82. field names to generate as `DecimalField` may also be used.
  83. `blank` => Boolean or sequence (default: False). When set to True all
  84. generated model fields will have `blank=True`. If the user wants to
  85. give specific fields to have blank, then a list/tuple of OGR field
  86. names may be used.
  87. `null` => Boolean (default: False) - When set to True all generated
  88. model fields will have `null=True`. If the user wants to specify
  89. give specific fields to have null, then a list/tuple of OGR field
  90. names may be used.
  91. Note: This routine calls the _ogrinspect() helper to do the heavy lifting.
  92. """
  93. return '\n'.join(s for s in _ogrinspect(*args, **kwargs))
  94. def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=None,
  95. multi_geom=False, name_field=None, imports=True,
  96. decimal=False, blank=False, null=False):
  97. """
  98. Helper routine for `ogrinspect` that generates GeoDjango models corresponding
  99. to the given data source. See the `ogrinspect` docstring for more details.
  100. """
  101. # Getting the DataSource
  102. if isinstance(data_source, six.string_types):
  103. data_source = DataSource(data_source)
  104. elif isinstance(data_source, DataSource):
  105. pass
  106. else:
  107. raise TypeError('Data source parameter must be a string or a DataSource object.')
  108. # Getting the layer corresponding to the layer key and getting
  109. # a string listing of all OGR fields in the Layer.
  110. layer = data_source[layer_key]
  111. ogr_fields = layer.fields
  112. # Creating lists from the `null`, `blank`, and `decimal`
  113. # keyword arguments.
  114. def process_kwarg(kwarg):
  115. if isinstance(kwarg, (list, tuple)):
  116. return [s.lower() for s in kwarg]
  117. elif kwarg:
  118. return [s.lower() for s in ogr_fields]
  119. else:
  120. return []
  121. null_fields = process_kwarg(null)
  122. blank_fields = process_kwarg(blank)
  123. decimal_fields = process_kwarg(decimal)
  124. # Gets the `null` and `blank` keywords for the given field name.
  125. def get_kwargs_str(field_name):
  126. kwlist = []
  127. if field_name.lower() in null_fields:
  128. kwlist.append('null=True')
  129. if field_name.lower() in blank_fields:
  130. kwlist.append('blank=True')
  131. if kwlist:
  132. return ', ' + ', '.join(kwlist)
  133. else:
  134. return ''
  135. # For those wishing to disable the imports.
  136. if imports:
  137. yield '# This is an auto-generated Django model module created by ogrinspect.'
  138. yield 'from django.contrib.gis.db import models'
  139. yield ''
  140. yield 'class %s(models.Model):' % model_name
  141. for field_name, width, precision, field_type in zip(
  142. ogr_fields, layer.field_widths, layer.field_precisions, layer.field_types):
  143. # The model field name.
  144. mfield = field_name.lower()
  145. if mfield[-1:] == '_':
  146. mfield += 'field'
  147. # Getting the keyword args string.
  148. kwargs_str = get_kwargs_str(field_name)
  149. if field_type is OFTReal:
  150. # By default OFTReals are mapped to `FloatField`, however, they
  151. # may also be mapped to `DecimalField` if specified in the
  152. # `decimal` keyword.
  153. if field_name.lower() in decimal_fields:
  154. yield ' %s = models.DecimalField(max_digits=%d, decimal_places=%d%s)' % (
  155. mfield, width, precision, kwargs_str
  156. )
  157. else:
  158. yield ' %s = models.FloatField(%s)' % (mfield, kwargs_str[2:])
  159. elif field_type is OFTInteger:
  160. yield ' %s = models.IntegerField(%s)' % (mfield, kwargs_str[2:])
  161. elif field_type is OFTInteger64:
  162. yield ' %s = models.BigIntegerField(%s)' % (mfield, kwargs_str[2:])
  163. elif field_type is OFTString:
  164. yield ' %s = models.CharField(max_length=%s%s)' % (mfield, width, kwargs_str)
  165. elif field_type is OFTDate:
  166. yield ' %s = models.DateField(%s)' % (mfield, kwargs_str[2:])
  167. elif field_type is OFTDateTime:
  168. yield ' %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:])
  169. elif field_type is OFTTime:
  170. yield ' %s = models.TimeField(%s)' % (mfield, kwargs_str[2:])
  171. else:
  172. raise TypeError('Unknown field type %s in %s' % (field_type, mfield))
  173. # TODO: Autodetection of multigeometry types (see #7218).
  174. gtype = layer.geom_type
  175. if multi_geom:
  176. gtype.to_multi()
  177. geom_field = gtype.django
  178. # Setting up the SRID keyword string.
  179. if srid is None:
  180. if layer.srs is None:
  181. srid_str = 'srid=-1'
  182. else:
  183. srid = layer.srs.srid
  184. if srid is None:
  185. srid_str = 'srid=-1'
  186. elif srid == 4326:
  187. # WGS84 is already the default.
  188. srid_str = ''
  189. else:
  190. srid_str = 'srid=%s' % srid
  191. else:
  192. srid_str = 'srid=%s' % srid
  193. yield ' %s = models.%s(%s)' % (geom_name, geom_field, srid_str)
  194. if name_field:
  195. yield ''
  196. yield ' def __%s__(self): return self.%s' % (
  197. 'str' if six.PY3 else 'unicode', name_field)