ogrinspect.py 9.0 KB

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