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.
 
 
 
 

240 lines
8.9 KiB

  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)