before send to remote
This commit is contained in:
17
env/lib/python3.8/site-packages/django/contrib/gis/utils/__init__.py
vendored
Normal file
17
env/lib/python3.8/site-packages/django/contrib/gis/utils/__init__.py
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
This module contains useful utilities for GeoDjango.
|
||||
"""
|
||||
from django.contrib.gis.utils.ogrinfo import ogrinfo # NOQA
|
||||
from django.contrib.gis.utils.ogrinspect import mapping, ogrinspect # NOQA
|
||||
from django.contrib.gis.utils.srs import add_srs_entry # NOQA
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
try:
|
||||
# LayerMapping requires DJANGO_SETTINGS_MODULE to be set,
|
||||
# and ImproperlyConfigured is raised if that's not the case.
|
||||
from django.contrib.gis.utils.layermapping import ( # NOQA
|
||||
LayerMapError,
|
||||
LayerMapping,
|
||||
)
|
||||
except ImproperlyConfigured:
|
||||
pass
|
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/__init__.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/layermapping.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/layermapping.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/ogrinfo.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/ogrinfo.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/ogrinspect.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/ogrinspect.cpython-38.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/srs.cpython-38.pyc
vendored
Normal file
BIN
env/lib/python3.8/site-packages/django/contrib/gis/utils/__pycache__/srs.cpython-38.pyc
vendored
Normal file
Binary file not shown.
724
env/lib/python3.8/site-packages/django/contrib/gis/utils/layermapping.py
vendored
Normal file
724
env/lib/python3.8/site-packages/django/contrib/gis/utils/layermapping.py
vendored
Normal file
@@ -0,0 +1,724 @@
|
||||
# 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:
|
||||
https://docs.djangoproject.com/en/dev/ref/contrib/gis/layermapping/
|
||||
"""
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from decimal import InvalidOperation as DecimalInvalidOperation
|
||||
from pathlib import Path
|
||||
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.contrib.gis.gdal import (
|
||||
CoordTransform,
|
||||
DataSource,
|
||||
GDALException,
|
||||
OGRGeometry,
|
||||
OGRGeomType,
|
||||
SpatialReference,
|
||||
)
|
||||
from django.contrib.gis.gdal.field import (
|
||||
OFTDate,
|
||||
OFTDateTime,
|
||||
OFTInteger,
|
||||
OFTInteger64,
|
||||
OFTReal,
|
||||
OFTString,
|
||||
OFTTime,
|
||||
)
|
||||
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
||||
from django.db import connections, models, router, transaction
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
|
||||
# LayerMapping exceptions.
|
||||
class LayerMapError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidString(LayerMapError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidDecimal(LayerMapError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidInteger(LayerMapError):
|
||||
pass
|
||||
|
||||
|
||||
class MissingForeignKey(LayerMapError):
|
||||
pass
|
||||
|
||||
|
||||
class LayerMapping:
|
||||
"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.BigAutoField: OFTInteger64,
|
||||
models.SmallAutoField: OFTInteger,
|
||||
models.BooleanField: (OFTInteger, OFTReal, OFTString),
|
||||
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.UUIDField: OFTString,
|
||||
models.BigIntegerField: (OFTInteger, OFTReal, OFTString),
|
||||
models.SmallIntegerField: (OFTInteger, OFTReal, OFTString),
|
||||
models.PositiveBigIntegerField: (OFTInteger, OFTReal, OFTString),
|
||||
models.PositiveIntegerField: (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, (str, Path)):
|
||||
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)
|
||||
connection = connections[self.using]
|
||||
self.spatial_backend = connection.ops
|
||||
|
||||
# Setting the mapping & model attributes.
|
||||
self.mapping = mapping
|
||||
self.model = model
|
||||
|
||||
# Checking the layer -- initialization 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 connection.features.supports_transform:
|
||||
self.geo_field = self.geometry_field()
|
||||
else:
|
||||
transform = False
|
||||
|
||||
# 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):
|
||||
"Check 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):
|
||||
"""
|
||||
Check the Layer metadata and ensure that it's 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 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 GDALException:
|
||||
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.remote_field.model
|
||||
for rel_name, ogr_field in ogr_name.items():
|
||||
idx = check_ogr_fld(ogr_field)
|
||||
try:
|
||||
rel_model._meta.get_field(rel_name)
|
||||
except 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 model_field.__class__ not 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):
|
||||
"Check 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, str)):
|
||||
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):
|
||||
"Check 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 attr not in self.mapping:
|
||||
raise ValueError
|
||||
elif isinstance(unique, str):
|
||||
# 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, 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 GDALException:
|
||||
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`), construct
|
||||
and return the uniqueness keyword arguments -- a subset of the feature
|
||||
kwargs.
|
||||
"""
|
||||
if isinstance(self.unique, str):
|
||||
return {self.unique: kwargs[self.unique]}
|
||||
else:
|
||||
return {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):
|
||||
"""
|
||||
Verify if the OGR Field contents are acceptable to the model field. If
|
||||
they are, return the verified value, otherwise raise an exception.
|
||||
"""
|
||||
if isinstance(ogr_field, OFTString) and isinstance(
|
||||
model_field, (models.CharField, models.TextField)
|
||||
):
|
||||
if self.encoding and ogr_field.value is not None:
|
||||
# The encoding for OGR data sources may be specified here
|
||||
# (e.g., 'cp437' for Census Bureau boundary files).
|
||||
val = force_str(ogr_field.value, self.encoding)
|
||||
else:
|
||||
val = ogr_field.value
|
||||
if (
|
||||
model_field.max_length
|
||||
and val is not None
|
||||
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 DecimalInvalidOperation:
|
||||
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 ValueError:
|
||||
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,
|
||||
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):
|
||||
"""
|
||||
Verify the geometry -- 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):
|
||||
"Return 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 exc:
|
||||
raise LayerMapError(
|
||||
"Could not translate between the data source and model geometry."
|
||||
) from exc
|
||||
|
||||
def geometry_field(self):
|
||||
"Return the GeometryField instance associated with the geographic column."
|
||||
# Use `get_field()` on the model's options so that we
|
||||
# get the correct field instance if there's model inheritance.
|
||||
opts = self.model._meta
|
||||
return opts.get_field(self.geom_field)
|
||||
|
||||
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,
|
||||
):
|
||||
"""
|
||||
Save 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 successfully 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_value = getattr(m, self.geom_field)
|
||||
if geom_value is None:
|
||||
geom = OGRGeometry(kwargs[self.geom_field])
|
||||
else:
|
||||
geom = geom_value.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 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 Exception: # Deliberately catch everything
|
||||
stream.write(
|
||||
"%s\nFailed to save slice: %s\n" % ("=-" * 20, step_slice)
|
||||
)
|
||||
raise
|
||||
else:
|
||||
# Otherwise, just calling the previously defined _save() function.
|
||||
_save()
|
53
env/lib/python3.8/site-packages/django/contrib/gis/utils/ogrinfo.py
vendored
Normal file
53
env/lib/python3.8/site-packages/django/contrib/gis/utils/ogrinfo.py
vendored
Normal file
@@ -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):
|
||||
"""
|
||||
Walk 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)
|
267
env/lib/python3.8/site-packages/django/contrib/gis/utils/ogrinspect.py
vendored
Normal file
267
env/lib/python3.8/site-packages/django/contrib/gis/utils/ogrinspect.py
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
"""
|
||||
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
|
78
env/lib/python3.8/site-packages/django/contrib/gis/utils/srs.py
vendored
Normal file
78
env/lib/python3.8/site-packages/django/contrib/gis/utils/srs.py
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
from django.contrib.gis.gdal import SpatialReference
|
||||
from django.db import DEFAULT_DB_ALIAS, connections
|
||||
|
||||
|
||||
def add_srs_entry(
|
||||
srs, auth_name="EPSG", auth_srid=None, ref_sys_name=None, database=None
|
||||
):
|
||||
"""
|
||||
Take a GDAL SpatialReference system and add 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:
|
||||
|
||||
>>> from django.contrib.gis.utils import add_srs_entry
|
||||
>>> add_srs_entry(3857)
|
||||
|
||||
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, its value
|
||||
is 'default').
|
||||
"""
|
||||
database = database or 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 not connection.features.supports_add_srs_entry:
|
||||
raise Exception("This utility does not support your database backend.")
|
||||
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.
|
||||
srs_field_names = {f.name for f in SpatialRefSys._meta.get_fields()}
|
||||
if "srtext" in srs_field_names:
|
||||
kwargs["srtext"] = srs.wkt
|
||||
if "ref_sys_name" in srs_field_names:
|
||||
# SpatiaLite specific
|
||||
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.
|
||||
SpatialRefSys.objects.using(database).get(srid=srs.srid)
|
||||
except SpatialRefSys.DoesNotExist:
|
||||
SpatialRefSys.objects.using(database).create(**kwargs)
|
Reference in New Issue
Block a user