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