| 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 |
|---|