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.utils.six.moves import zip
|
---|
7 | # Requires GDAL to use.
|
---|
8 | from django.contrib.gis.gdal import DataSource
|
---|
9 | from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
|
---|
10 | from django.utils import six
|
---|
11 |
|
---|
12 |
|
---|
13 | def 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 |
|
---|
50 | def 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 |
|
---|
105 | def _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)
|
---|