diff options
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/gis/utils')
6 files changed, 1028 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/utils/__init__.py new file mode 100644 index 0000000..c6617d2 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/__init__.py @@ -0,0 +1,17 @@ +""" + This module contains useful utilities for GeoDjango. +""" +# Importing the utilities that depend on GDAL, if available. +from django.contrib.gis.gdal import HAS_GDAL +if HAS_GDAL: + from django.contrib.gis.utils.ogrinfo import ogrinfo, sample + from django.contrib.gis.utils.ogrinspect import mapping, ogrinspect + from django.contrib.gis.utils.srs import add_postgis_srs, add_srs_entry + try: + # LayerMapping requires DJANGO_SETTINGS_MODULE to be set, + # so this needs to be in try/except. + from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError + except: + pass + +from django.contrib.gis.utils.wkt import precision_wkt diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/layermapping.py b/lib/python2.7/site-packages/django/contrib/gis/utils/layermapping.py new file mode 100644 index 0000000..a502aa2 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/layermapping.py @@ -0,0 +1,596 @@ +# LayerMapping -- A Django Model/OGR Layer Mapping Utility +""" + The LayerMapping class provides a way to map the contents of OGR + vector files (e.g. SHP files) to Geographic-enabled Django models. + + For more information, please consult the GeoDjango documentation: + http://geodjango.org/docs/layermapping.html +""" +import sys +from decimal import Decimal +from django.core.exceptions import ObjectDoesNotExist +from django.db import connections, router +from django.contrib.gis.db.models import GeometryField +from django.contrib.gis.gdal import (CoordTransform, DataSource, + OGRException, OGRGeometry, OGRGeomType, SpatialReference) +from django.contrib.gis.gdal.field import ( + OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime) +from django.db import models, transaction +from django.utils import six +from django.utils.encoding import force_text + + +# LayerMapping exceptions. +class LayerMapError(Exception): pass +class InvalidString(LayerMapError): pass +class InvalidDecimal(LayerMapError): pass +class InvalidInteger(LayerMapError): pass +class MissingForeignKey(LayerMapError): pass + +class LayerMapping(object): + "A class that maps OGR Layers to GeoDjango Models." + + # Acceptable 'base' types for a multi-geometry type. + MULTI_TYPES = {1 : OGRGeomType('MultiPoint'), + 2 : OGRGeomType('MultiLineString'), + 3 : OGRGeomType('MultiPolygon'), + OGRGeomType('Point25D').num : OGRGeomType('MultiPoint25D'), + OGRGeomType('LineString25D').num : OGRGeomType('MultiLineString25D'), + OGRGeomType('Polygon25D').num : OGRGeomType('MultiPolygon25D'), + } + + # Acceptable Django field types and corresponding acceptable OGR + # counterparts. + FIELD_TYPES = { + models.AutoField : OFTInteger, + models.IntegerField : (OFTInteger, OFTReal, OFTString), + models.FloatField : (OFTInteger, OFTReal), + models.DateField : OFTDate, + models.DateTimeField : OFTDateTime, + models.EmailField : OFTString, + models.TimeField : OFTTime, + models.DecimalField : (OFTInteger, OFTReal), + models.CharField : OFTString, + models.SlugField : OFTString, + models.TextField : OFTString, + models.URLField : OFTString, + models.BigIntegerField : (OFTInteger, OFTReal, OFTString), + models.SmallIntegerField : (OFTInteger, OFTReal, OFTString), + models.PositiveSmallIntegerField : (OFTInteger, OFTReal, OFTString), + } + + def __init__(self, model, data, mapping, layer=0, + source_srs=None, encoding='utf-8', + transaction_mode='commit_on_success', + transform=True, unique=None, using=None): + """ + A LayerMapping object is initialized using the given Model (not an instance), + a DataSource (or string path to an OGR-supported data file), and a mapping + dictionary. See the module level docstring for more details and keyword + argument usage. + """ + # Getting the DataSource and the associated Layer. + if isinstance(data, six.string_types): + self.ds = DataSource(data, encoding=encoding) + else: + self.ds = data + self.layer = self.ds[layer] + + self.using = using if using is not None else router.db_for_write(model) + self.spatial_backend = connections[self.using].ops + + # Setting the mapping & model attributes. + self.mapping = mapping + self.model = model + + # Checking the layer -- intitialization of the object will fail if + # things don't check out before hand. + self.check_layer() + + # Getting the geometry column associated with the model (an + # exception will be raised if there is no geometry column). + if self.spatial_backend.mysql: + transform = False + else: + self.geo_field = self.geometry_field() + + # Checking the source spatial reference system, and getting + # the coordinate transformation object (unless the `transform` + # keyword is set to False) + if transform: + self.source_srs = self.check_srs(source_srs) + self.transform = self.coord_transform() + else: + self.transform = transform + + # Setting the encoding for OFTString fields, if specified. + if encoding: + # Making sure the encoding exists, if not a LookupError + # exception will be thrown. + from codecs import lookup + lookup(encoding) + self.encoding = encoding + else: + self.encoding = None + + if unique: + self.check_unique(unique) + transaction_mode = 'autocommit' # Has to be set to autocommit. + self.unique = unique + else: + self.unique = None + + # Setting the transaction decorator with the function in the + # transaction modes dictionary. + self.transaction_mode = transaction_mode + if transaction_mode == 'autocommit': + self.transaction_decorator = None + elif transaction_mode == 'commit_on_success': + self.transaction_decorator = transaction.atomic + else: + raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode) + + #### Checking routines used during initialization #### + def check_fid_range(self, fid_range): + "This checks the `fid_range` keyword." + if fid_range: + if isinstance(fid_range, (tuple, list)): + return slice(*fid_range) + elif isinstance(fid_range, slice): + return fid_range + else: + raise TypeError + else: + return None + + def check_layer(self): + """ + This checks the Layer metadata, and ensures that it is compatible + with the mapping information and model. Unlike previous revisions, + there is no need to increment through each feature in the Layer. + """ + # The geometry field of the model is set here. + # TODO: Support more than one geometry field / model. However, this + # depends on the GDAL Driver in use. + self.geom_field = False + self.fields = {} + + # Getting lists of the field names and the field types available in + # the OGR Layer. + ogr_fields = self.layer.fields + ogr_field_types = self.layer.field_types + + # Function for determining if the OGR mapping field is in the Layer. + def check_ogr_fld(ogr_map_fld): + try: + idx = ogr_fields.index(ogr_map_fld) + except ValueError: + raise LayerMapError('Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld) + return idx + + # No need to increment through each feature in the model, simply check + # the Layer metadata against what was given in the mapping dictionary. + for field_name, ogr_name in self.mapping.items(): + # Ensuring that a corresponding field exists in the model + # for the given field name in the mapping. + try: + model_field = self.model._meta.get_field(field_name) + except models.fields.FieldDoesNotExist: + raise LayerMapError('Given mapping field "%s" not in given Model fields.' % field_name) + + # Getting the string name for the Django field class (e.g., 'PointField'). + fld_name = model_field.__class__.__name__ + + if isinstance(model_field, GeometryField): + if self.geom_field: + raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') + + # Getting the coordinate dimension of the geometry field. + coord_dim = model_field.dim + + try: + if coord_dim == 3: + gtype = OGRGeomType(ogr_name + '25D') + else: + gtype = OGRGeomType(ogr_name) + except OGRException: + raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) + + # Making sure that the OGR Layer's Geometry is compatible. + ltype = self.layer.geom_type + if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)): + raise LayerMapError('Invalid mapping geometry; model has %s%s, ' + 'layer geometry type is %s.' % + (fld_name, '(dim=3)' if coord_dim == 3 else '', ltype)) + + # Setting the `geom_field` attribute w/the name of the model field + # that is a Geometry. Also setting the coordinate dimension + # attribute. + self.geom_field = field_name + self.coord_dim = coord_dim + fields_val = model_field + elif isinstance(model_field, models.ForeignKey): + if isinstance(ogr_name, dict): + # Is every given related model mapping field in the Layer? + rel_model = model_field.rel.to + for rel_name, ogr_field in ogr_name.items(): + idx = check_ogr_fld(ogr_field) + try: + rel_field = rel_model._meta.get_field(rel_name) + except models.fields.FieldDoesNotExist: + raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' % + (rel_name, rel_model.__class__.__name__)) + fields_val = rel_model + else: + raise TypeError('ForeignKey mapping must be of dictionary type.') + else: + # Is the model field type supported by LayerMapping? + if not model_field.__class__ in self.FIELD_TYPES: + raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % fld_name) + + # Is the OGR field in the Layer? + idx = check_ogr_fld(ogr_name) + ogr_field = ogr_field_types[idx] + + # Can the OGR field type be mapped to the Django field type? + if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]): + raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' % + (ogr_field, ogr_field.__name__, fld_name)) + fields_val = model_field + + self.fields[field_name] = fields_val + + def check_srs(self, source_srs): + "Checks the compatibility of the given spatial reference object." + + if isinstance(source_srs, SpatialReference): + sr = source_srs + elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()): + sr = source_srs.srs + elif isinstance(source_srs, (int, six.string_types)): + sr = SpatialReference(source_srs) + else: + # Otherwise just pulling the SpatialReference from the layer + sr = self.layer.srs + + if not sr: + raise LayerMapError('No source reference system defined.') + else: + return sr + + def check_unique(self, unique): + "Checks the `unique` keyword parameter -- may be a sequence or string." + if isinstance(unique, (list, tuple)): + # List of fields to determine uniqueness with + for attr in unique: + if not attr in self.mapping: raise ValueError + elif isinstance(unique, six.string_types): + # Only a single field passed in. + if unique not in self.mapping: raise ValueError + else: + raise TypeError('Unique keyword argument must be set with a tuple, list, or string.') + + #### Keyword argument retrieval routines #### + def feature_kwargs(self, feat): + """ + Given an OGR Feature, this will return a dictionary of keyword arguments + for constructing the mapped model. + """ + # The keyword arguments for model construction. + kwargs = {} + + # Incrementing through each model field and OGR field in the + # dictionary mapping. + for field_name, ogr_name in self.mapping.items(): + model_field = self.fields[field_name] + + if isinstance(model_field, GeometryField): + # Verify OGR geometry. + try: + val = self.verify_geom(feat.geom, model_field) + except OGRException: + raise LayerMapError('Could not retrieve geometry from feature.') + elif isinstance(model_field, models.base.ModelBase): + # The related _model_, not a field was passed in -- indicating + # another mapping for the related Model. + val = self.verify_fk(feat, model_field, ogr_name) + else: + # Otherwise, verify OGR Field type. + val = self.verify_ogr_field(feat[ogr_name], model_field) + + # Setting the keyword arguments for the field name with the + # value obtained above. + kwargs[field_name] = val + + return kwargs + + def unique_kwargs(self, kwargs): + """ + Given the feature keyword arguments (from `feature_kwargs`) this routine + will construct and return the uniqueness keyword arguments -- a subset + of the feature kwargs. + """ + if isinstance(self.unique, six.string_types): + return {self.unique : kwargs[self.unique]} + else: + return dict((fld, kwargs[fld]) for fld in self.unique) + + #### Verification routines used in constructing model keyword arguments. #### + def verify_ogr_field(self, ogr_field, model_field): + """ + Verifies if the OGR Field contents are acceptable to the Django + model field. If they are, the verified value is returned, + otherwise the proper exception is raised. + """ + if (isinstance(ogr_field, OFTString) and + isinstance(model_field, (models.CharField, models.TextField))): + if self.encoding: + # The encoding for OGR data sources may be specified here + # (e.g., 'cp437' for Census Bureau boundary files). + val = force_text(ogr_field.value, self.encoding) + else: + val = ogr_field.value + if model_field.max_length and len(val) > model_field.max_length: + raise InvalidString('%s model field maximum string length is %s, given %s characters.' % + (model_field.name, model_field.max_length, len(val))) + elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField): + try: + # Creating an instance of the Decimal value to use. + d = Decimal(str(ogr_field.value)) + except: + raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field.value) + + # Getting the decimal value as a tuple. + dtup = d.as_tuple() + digits = dtup[1] + d_idx = dtup[2] # index where the decimal is + + # Maximum amount of precision, or digits to the left of the decimal. + max_prec = model_field.max_digits - model_field.decimal_places + + # Getting the digits to the left of the decimal place for the + # given decimal. + if d_idx < 0: + n_prec = len(digits[:d_idx]) + else: + n_prec = len(digits) + d_idx + + # If we have more than the maximum digits allowed, then throw an + # InvalidDecimal exception. + if n_prec > max_prec: + raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' % + (model_field.max_digits, model_field.decimal_places, max_prec)) + val = d + elif isinstance(ogr_field, (OFTReal, OFTString)) and isinstance(model_field, models.IntegerField): + # Attempt to convert any OFTReal and OFTString value to an OFTInteger. + try: + val = int(ogr_field.value) + except: + raise InvalidInteger('Could not construct integer from: %s' % ogr_field.value) + else: + val = ogr_field.value + return val + + def verify_fk(self, feat, rel_model, rel_mapping): + """ + Given an OGR Feature, the related model and its dictionary mapping, + this routine will retrieve the related model for the ForeignKey + mapping. + """ + # TODO: It is expensive to retrieve a model for every record -- + # explore if an efficient mechanism exists for caching related + # ForeignKey models. + + # Constructing and verifying the related model keyword arguments. + fk_kwargs = {} + for field_name, ogr_name in rel_mapping.items(): + fk_kwargs[field_name] = self.verify_ogr_field(feat[ogr_name], rel_model._meta.get_field(field_name)) + + # Attempting to retrieve and return the related model. + try: + return rel_model.objects.using(self.using).get(**fk_kwargs) + except ObjectDoesNotExist: + raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs)) + + def verify_geom(self, geom, model_field): + """ + Verifies the geometry -- will construct and return a GeometryCollection + if necessary (for example if the model field is MultiPolygonField while + the mapped shapefile only contains Polygons). + """ + # Downgrade a 3D geom to a 2D one, if necessary. + if self.coord_dim != geom.coord_dim: + geom.coord_dim = self.coord_dim + + if self.make_multi(geom.geom_type, model_field): + # Constructing a multi-geometry type to contain the single geometry + multi_type = self.MULTI_TYPES[geom.geom_type.num] + g = OGRGeometry(multi_type) + g.add(geom) + else: + g = geom + + # Transforming the geometry with our Coordinate Transformation object, + # but only if the class variable `transform` is set w/a CoordTransform + # object. + if self.transform: g.transform(self.transform) + + # Returning the WKT of the geometry. + return g.wkt + + #### Other model methods #### + def coord_transform(self): + "Returns the coordinate transformation object." + SpatialRefSys = self.spatial_backend.spatial_ref_sys() + try: + # Getting the target spatial reference system + target_srs = SpatialRefSys.objects.using(self.using).get(srid=self.geo_field.srid).srs + + # Creating the CoordTransform object + return CoordTransform(self.source_srs, target_srs) + except Exception as msg: + new_msg = 'Could not translate between the data source and model geometry: %s' % msg + six.reraise(LayerMapError, LayerMapError(new_msg), sys.exc_info()[2]) + + def geometry_field(self): + "Returns the GeometryField instance associated with the geographic column." + # Use the `get_field_by_name` on the model's options so that we + # get the correct field instance if there's model inheritance. + opts = self.model._meta + fld, model, direct, m2m = opts.get_field_by_name(self.geom_field) + return fld + + def make_multi(self, geom_type, model_field): + """ + Given the OGRGeomType for a geometry and its associated GeometryField, + determine whether the geometry should be turned into a GeometryCollection. + """ + return (geom_type.num in self.MULTI_TYPES and + model_field.__class__.__name__ == 'Multi%s' % geom_type.django) + + def save(self, verbose=False, fid_range=False, step=False, + progress=False, silent=False, stream=sys.stdout, strict=False): + """ + Saves the contents from the OGR DataSource Layer into the database + according to the mapping dictionary given at initialization. + + Keyword Parameters: + verbose: + If set, information will be printed subsequent to each model save + executed on the database. + + fid_range: + May be set with a slice or tuple of (begin, end) feature ID's to map + from the data source. In other words, this keyword enables the user + to selectively import a subset range of features in the geographic + data source. + + step: + If set with an integer, transactions will occur at every step + interval. For example, if step=1000, a commit would occur after + the 1,000th feature, the 2,000th feature etc. + + progress: + When this keyword is set, status information will be printed giving + the number of features processed and sucessfully saved. By default, + progress information will pe printed every 1000 features processed, + however, this default may be overridden by setting this keyword with an + integer for the desired interval. + + stream: + Status information will be written to this file handle. Defaults to + using `sys.stdout`, but any object with a `write` method is supported. + + silent: + By default, non-fatal error notifications are printed to stdout, but + this keyword may be set to disable these notifications. + + strict: + Execution of the model mapping will cease upon the first error + encountered. The default behavior is to attempt to continue. + """ + # Getting the default Feature ID range. + default_range = self.check_fid_range(fid_range) + + # Setting the progress interval, if requested. + if progress: + if progress is True or not isinstance(progress, int): + progress_interval = 1000 + else: + progress_interval = progress + + def _save(feat_range=default_range, num_feat=0, num_saved=0): + if feat_range: + layer_iter = self.layer[feat_range] + else: + layer_iter = self.layer + + for feat in layer_iter: + num_feat += 1 + # Getting the keyword arguments + try: + kwargs = self.feature_kwargs(feat) + except LayerMapError as msg: + # Something borked the validation + if strict: raise + elif not silent: + stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg)) + else: + # Constructing the model using the keyword args + is_update = False + if self.unique: + # If we want unique models on a particular field, handle the + # geometry appropriately. + try: + # Getting the keyword arguments and retrieving + # the unique model. + u_kwargs = self.unique_kwargs(kwargs) + m = self.model.objects.using(self.using).get(**u_kwargs) + is_update = True + + # Getting the geometry (in OGR form), creating + # one from the kwargs WKT, adding in additional + # geometries, and update the attribute with the + # just-updated geometry WKT. + geom = getattr(m, self.geom_field).ogr + new = OGRGeometry(kwargs[self.geom_field]) + for g in new: geom.add(g) + setattr(m, self.geom_field, geom.wkt) + except ObjectDoesNotExist: + # No unique model exists yet, create. + m = self.model(**kwargs) + else: + m = self.model(**kwargs) + + try: + # Attempting to save. + m.save(using=self.using) + num_saved += 1 + if verbose: stream.write('%s: %s\n' % ('Updated' if is_update else 'Saved', m)) + except SystemExit: + raise + except Exception as msg: + if strict: + # Bailing out if the `strict` keyword is set. + if not silent: + stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid) + stream.write('%s\n' % kwargs) + raise + elif not silent: + stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg)) + + # Printing progress information, if requested. + if progress and num_feat % progress_interval == 0: + stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved)) + + # Only used for status output purposes -- incremental saving uses the + # values returned here. + return num_saved, num_feat + + if self.transaction_decorator is not None: + _save = self.transaction_decorator(_save) + + nfeat = self.layer.num_feat + if step and isinstance(step, int) and step < nfeat: + # Incremental saving is requested at the given interval (step) + if default_range: + raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.') + beg, num_feat, num_saved = (0, 0, 0) + indices = range(step, nfeat, step) + n_i = len(indices) + + for i, end in enumerate(indices): + # Constructing the slice to use for this step; the last slice is + # special (e.g, [100:] instead of [90:100]). + if i+1 == n_i: step_slice = slice(beg, None) + else: step_slice = slice(beg, end) + + try: + num_feat, num_saved = _save(step_slice, num_feat, num_saved) + beg = end + except: + stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice)) + raise + else: + # Otherwise, just calling the previously defined _save() function. + _save() diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinfo.py b/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinfo.py new file mode 100644 index 0000000..d9c3e09 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinfo.py @@ -0,0 +1,53 @@ +""" +This module includes some utility functions for inspecting the layout +of a GDAL data source -- the functionality is analogous to the output +produced by the `ogrinfo` utility. +""" + +from django.contrib.gis.gdal import DataSource +from django.contrib.gis.gdal.geometries import GEO_CLASSES + +def ogrinfo(data_source, num_features=10): + """ + Walks the available layers in the supplied `data_source`, displaying + the fields for the first `num_features` features. + """ + + # Checking the parameters. + if isinstance(data_source, str): + data_source = DataSource(data_source) + elif isinstance(data_source, DataSource): + pass + else: + raise Exception('Data source parameter must be a string or a DataSource object.') + + for i, layer in enumerate(data_source): + print("data source : %s" % data_source.name) + print("==== layer %s" % i) + print(" shape type: %s" % GEO_CLASSES[layer.geom_type.num].__name__) + print(" # features: %s" % len(layer)) + print(" srs: %s" % layer.srs) + extent_tup = layer.extent.tuple + print(" extent: %s - %s" % (extent_tup[0:2], extent_tup[2:4])) + print("Displaying the first %s features ====" % num_features) + + width = max(*map(len,layer.fields)) + fmt = " %%%ss: %%s" % width + for j, feature in enumerate(layer[:num_features]): + print("=== Feature %s" % j) + for fld_name in layer.fields: + type_name = feature[fld_name].type_name + output = fmt % (fld_name, type_name) + val = feature.get(fld_name) + if val: + if isinstance(val, str): + val_fmt = ' ("%s")' + else: + val_fmt = ' (%s)' + output += val_fmt % val + else: + output += ' (None)' + print(output) + +# For backwards compatibility. +sample = ogrinfo diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinspect.py b/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinspect.py new file mode 100644 index 0000000..b7cfafd --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/ogrinspect.py @@ -0,0 +1,225 @@ +""" +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.utils.six.moves import zip +# Requires GDAL to use. +from django.contrib.gis.gdal import DataSource +from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime +from django.utils import six + +def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False): + """ + Given a DataSource, generates 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, six.string_types): + # 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 and gtype.num in (1, 2, 3): prefix = 'MULTI' + else: prefix = '' + _mapping[geom_name] = prefix + 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 + `__unicode__`/`__str__` function (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: This routine calls the _ogrinspect() helper to do the heavy lifting. + """ + return '\n'.join(s for s in _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, six.string_types): + 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 '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 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 and gtype.num in (1, 2, 3): + geom_field = 'Multi%s' % gtype.django + else: + 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) + yield ' objects = models.GeoManager()' + + if name_field: + yield '' + yield ' def __%s__(self): return self.%s' % ( + 'str' if six.PY3 else 'unicode', name_field) diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/srs.py b/lib/python2.7/site-packages/django/contrib/gis/utils/srs.py new file mode 100644 index 0000000..fe2f291 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/srs.py @@ -0,0 +1,80 @@ +from django.contrib.gis.gdal import SpatialReference + +def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None, + database=None): + """ + This function takes a GDAL SpatialReference system and adds its information + to the `spatial_ref_sys` table of the spatial backend. Doing this enables + database-level spatial transformations for the backend. Thus, this utility + is useful for adding spatial reference systems not included by default with + the backend -- for example, the so-called "Google Maps Mercator Projection" + is excluded in PostGIS 1.3 and below, and the following adds it to the + `spatial_ref_sys` table: + + >>> from django.contrib.gis.utils import add_srs_entry + >>> add_srs_entry(900913) + + Keyword Arguments: + auth_name: + This keyword may be customized with the value of the `auth_name` field. + Defaults to 'EPSG'. + + auth_srid: + This keyword may be customized with the value of the `auth_srid` field. + Defaults to the SRID determined by GDAL. + + ref_sys_name: + For SpatiaLite users only, sets the value of the `ref_sys_name` field. + Defaults to the name determined by GDAL. + + database: + The name of the database connection to use; the default is the value + of `django.db.DEFAULT_DB_ALIAS` (at the time of this writing, it's value + is 'default'). + """ + from django.db import connections, DEFAULT_DB_ALIAS + if not database: + database = DEFAULT_DB_ALIAS + connection = connections[database] + + if not hasattr(connection.ops, 'spatial_version'): + raise Exception('The `add_srs_entry` utility only works ' + 'with spatial backends.') + if connection.ops.oracle or connection.ops.mysql: + raise Exception('This utility does not support the ' + 'Oracle or MySQL spatial backends.') + SpatialRefSys = connection.ops.spatial_ref_sys() + + # If argument is not a `SpatialReference` instance, use it as parameter + # to construct a `SpatialReference` instance. + if not isinstance(srs, SpatialReference): + srs = SpatialReference(srs) + + if srs.srid is None: + raise Exception('Spatial reference requires an SRID to be ' + 'compatible with the spatial backend.') + + # Initializing the keyword arguments dictionary for both PostGIS + # and SpatiaLite. + kwargs = {'srid' : srs.srid, + 'auth_name' : auth_name, + 'auth_srid' : auth_srid or srs.srid, + 'proj4text' : srs.proj4, + } + + # Backend-specific fields for the SpatialRefSys model. + if connection.ops.postgis: + kwargs['srtext'] = srs.wkt + if connection.ops.spatialite: + kwargs['ref_sys_name'] = ref_sys_name or srs.name + + # Creating the spatial_ref_sys model. + try: + # Try getting via SRID only, because using all kwargs may + # differ from exact wkt/proj in database. + sr = SpatialRefSys.objects.using(database).get(srid=srs.srid) + except SpatialRefSys.DoesNotExist: + sr = SpatialRefSys.objects.using(database).create(**kwargs) + +# Alias is for backwards-compatibility purposes. +add_postgis_srs = add_srs_entry diff --git a/lib/python2.7/site-packages/django/contrib/gis/utils/wkt.py b/lib/python2.7/site-packages/django/contrib/gis/utils/wkt.py new file mode 100644 index 0000000..d60eed3 --- /dev/null +++ b/lib/python2.7/site-packages/django/contrib/gis/utils/wkt.py @@ -0,0 +1,57 @@ +""" + Utilities for manipulating Geometry WKT. +""" + +from django.utils import six + +def precision_wkt(geom, prec): + """ + Returns WKT text of the geometry according to the given precision (an + integer or a string). If the precision is an integer, then the decimal + places of coordinates WKT will be truncated to that number: + + >>> pnt = Point(5, 23) + >>> pnt.wkt + 'POINT (5.0000000000000000 23.0000000000000000)' + >>> precision(geom, 1) + 'POINT (5.0 23.0)' + + If the precision is a string, it must be valid Python format string + (e.g., '%20.7f') -- thus, you should know what you're doing. + """ + if isinstance(prec, int): + num_fmt = '%%.%df' % prec + elif isinstance(prec, six.string_types): + num_fmt = prec + else: + raise TypeError + + # TODO: Support 3D geometries. + coord_fmt = ' '.join([num_fmt, num_fmt]) + + def formatted_coords(coords): + return ','.join([coord_fmt % c[:2] for c in coords]) + + def formatted_poly(poly): + return ','.join(['(%s)' % formatted_coords(r) for r in poly]) + + def formatted_geom(g): + gtype = str(g.geom_type).upper() + yield '%s(' % gtype + if gtype == 'POINT': + yield formatted_coords((g.coords,)) + elif gtype in ('LINESTRING', 'LINEARRING'): + yield formatted_coords(g.coords) + elif gtype in ('POLYGON', 'MULTILINESTRING'): + yield formatted_poly(g) + elif gtype == 'MULTIPOINT': + yield formatted_coords(g.coords) + elif gtype == 'MULTIPOLYGON': + yield ','.join(['(%s)' % formatted_poly(p) for p in g]) + elif gtype == 'GEOMETRYCOLLECTION': + yield ','.join([''.join([wkt for wkt in formatted_geom(child)]) for child in g]) + else: + raise TypeError + yield ')' + + return ''.join([wkt for wkt in formatted_geom(geom)]) |