Ticket #24207: ogrinspect.py

File ogrinspect.py, 9.4 KB (added by Michael Diener, 10 years ago)

new ogrinspect.py code

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