"""
This module is for inspecting OGR data sources and generating either
models for GeoDjango and/or mapping dictionaries for use with the
`LayerMapping` utility.
"""
from django.contrib.gis.gdal import DataSource
from django.contrib.gis.gdal.field import (
OFTDate,
OFTDateTime,
OFTInteger,
OFTInteger64,
OFTReal,
OFTString,
OFTTime,
)
def mapping(data_source, geom_name="geom", layer_key=0, multi_geom=False):
"""
Given a DataSource, generate a dictionary that may be used
for invoking the LayerMapping utility.
Keyword Arguments:
`geom_name` => The name of the geometry field to use for the model.
`layer_key` => The key for specifying which layer in the DataSource to use;
defaults to 0 (the first layer). May be an integer index or a string
identifier for the layer.
`multi_geom` => Boolean (default: False) - specify as multigeometry.
"""
if isinstance(data_source, str):
# Instantiating the DataSource from the string.
data_source = DataSource(data_source)
elif isinstance(data_source, DataSource):
pass
else:
raise TypeError(
"Data source parameter must be a string or a DataSource object."
)
# Creating the dictionary.
_mapping = {}
# Generating the field name for each field in the layer.
for field in data_source[layer_key].fields:
mfield = field.lower()
if mfield[-1:] == "_":
mfield += "field"
_mapping[mfield] = field
gtype = data_source[layer_key].geom_type
if multi_geom:
gtype.to_multi()
_mapping[geom_name] = str(gtype).upper()
return _mapping
def ogrinspect(*args, **kwargs):
"""
Given a data source (either a string or a DataSource object) and a string
model name this function will generate a GeoDjango model.
Usage:
>>> from django.contrib.gis.utils import ogrinspect
>>> ogrinspect('/path/to/shapefile.shp','NewModel')
...will print model definition to stout
or put this in a Python script and use to redirect the output to a new
model like:
$ python generate_model.py > myapp/models.py
# generate_model.py
from django.contrib.gis.utils import ogrinspect
shp_file = 'data/mapping_hacks/world_borders.shp'
model_name = 'WorldBorders'
print(ogrinspect(shp_file, model_name, multi_geom=True, srid=4326,
geom_name='shapes', blank=True))
Required Arguments
`datasource` => string or DataSource object to file pointer
`model name` => string of name of new model class to create
Optional Keyword Arguments
`geom_name` => For specifying the model name for the Geometry Field.
Otherwise will default to `geom`
`layer_key` => The key for specifying which layer in the DataSource to use;
defaults to 0 (the first layer). May be an integer index or a string
identifier for the layer.
`srid` => The SRID to use for the Geometry Field. If it can be determined,
the SRID of the datasource is used.
`multi_geom` => Boolean (default: False) - specify as multigeometry.
`name_field` => String - specifies a field name to return for the
__str__() method (which will be generated if specified).
`imports` => Boolean (default: True) - set to False to omit the
`from django.contrib.gis.db import models` code from the
autogenerated models thus avoiding duplicated imports when building
more than one model by batching ogrinspect()
`decimal` => Boolean or sequence (default: False). When set to True
all generated model fields corresponding to the `OFTReal` type will
be `DecimalField` instead of `FloatField`. A sequence of specific
field names to generate as `DecimalField` may also be used.
`blank` => Boolean or sequence (default: False). When set to True all
generated model fields will have `blank=True`. If the user wants to
give specific fields to have blank, then a list/tuple of OGR field
names may be used.
`null` => Boolean (default: False) - When set to True all generated
model fields will have `null=True`. If the user wants to specify
give specific fields to have null, then a list/tuple of OGR field
names may be used.
Note: Call the _ogrinspect() helper to do the heavy lifting.
"""
return "\n".join(_ogrinspect(*args, **kwargs))
def _ogrinspect(
data_source,
model_name,
geom_name="geom",
layer_key=0,
srid=None,
multi_geom=False,
name_field=None,
imports=True,
decimal=False,
blank=False,
null=False,
):
"""
Helper routine for `ogrinspect` that generates GeoDjango models corresponding
to the given data source. See the `ogrinspect` docstring for more details.
"""
# Getting the DataSource
if isinstance(data_source, str):
data_source = DataSource(data_source)
elif isinstance(data_source, DataSource):
pass
else:
raise TypeError(
"Data source parameter must be a string or a DataSource object."
)
# Getting the layer corresponding to the layer key and getting
# a string listing of all OGR fields in the Layer.
layer = data_source[layer_key]
ogr_fields = layer.fields
# Creating lists from the `null`, `blank`, and `decimal`
# keyword arguments.
def process_kwarg(kwarg):
if isinstance(kwarg, (list, tuple)):
return [s.lower() for s in kwarg]
elif kwarg:
return [s.lower() for s in ogr_fields]
else:
return []
null_fields = process_kwarg(null)
blank_fields = process_kwarg(blank)
decimal_fields = process_kwarg(decimal)
# Gets the `null` and `blank` keywords for the given field name.
def get_kwargs_str(field_name):
kwlist = []
if field_name.lower() in null_fields:
kwlist.append("null=True")
if field_name.lower() in blank_fields:
kwlist.append("blank=True")
if kwlist:
return ", " + ", ".join(kwlist)
else:
return ""
# For those wishing to disable the imports.
if imports:
yield "# This is an auto-generated Django model module created by ogrinspect."
yield "from django.contrib.gis.db import models"
yield ""
yield ""
yield "class %s(models.Model):" % model_name
for field_name, width, precision, field_type in zip(
ogr_fields, layer.field_widths, layer.field_precisions, layer.field_types
):
# The model field name.
mfield = field_name.lower()
if mfield[-1:] == "_":
mfield += "field"
# Getting the keyword args string.
kwargs_str = get_kwargs_str(field_name)
if field_type is OFTReal:
# By default OFTReals are mapped to `FloatField`, however, they
# may also be mapped to `DecimalField` if specified in the
# `decimal` keyword.
if field_name.lower() in decimal_fields:
yield (
" %s = models.DecimalField(max_digits=%d, decimal_places=%d%s)"
) % (
mfield,
width,
precision,
kwargs_str,
)
else:
yield " %s = models.FloatField(%s)" % (mfield, kwargs_str[2:])
elif field_type is OFTInteger:
yield " %s = models.IntegerField(%s)" % (mfield, kwargs_str[2:])
elif field_type is OFTInteger64:
yield " %s = models.BigIntegerField(%s)" % (mfield, kwargs_str[2:])
elif field_type is OFTString:
yield " %s = models.CharField(max_length=%s%s)" % (
mfield,
width,
kwargs_str,
)
elif field_type is OFTDate:
yield " %s = models.DateField(%s)" % (mfield, kwargs_str[2:])
elif field_type is OFTDateTime:
yield " %s = models.DateTimeField(%s)" % (mfield, kwargs_str[2:])
elif field_type is OFTTime:
yield " %s = models.TimeField(%s)" % (mfield, kwargs_str[2:])
else:
raise TypeError("Unknown field type %s in %s" % (field_type, mfield))
# TODO: Autodetection of multigeometry types (see #7218).
gtype = layer.geom_type
if multi_geom:
gtype.to_multi()
geom_field = gtype.django
# Setting up the SRID keyword string.
if srid is None:
if layer.srs is None:
srid_str = "srid=-1"
else:
srid = layer.srs.srid
if srid is None:
srid_str = "srid=-1"
elif srid == 4326:
# WGS84 is already the default.
srid_str = ""
else:
srid_str = "srid=%s" % srid
else:
srid_str = "srid=%s" % srid
yield " %s = models.%s(%s)" % (geom_name, geom_field, srid_str)
if name_field:
yield ""
yield " def __str__(self): return self.%s" % name_field