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