mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
gis: LayerMapping
: Improved the internals (i.e., checking every feature in OGR Layer is no longer needed, removed unnecessary class constants); added real support ForeignKey
model fields; added field_types
property to Layer
; fixed county shapefile because of Harris County, Georgia.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6992 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7e322b5908
commit
b11172e4e7
@ -5,6 +5,7 @@ from ctypes import byref
|
|||||||
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
|
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
|
||||||
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
|
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
|
||||||
from django.contrib.gis.gdal.feature import Feature
|
from django.contrib.gis.gdal.feature import Feature
|
||||||
|
from django.contrib.gis.gdal.field import FIELD_CLASSES
|
||||||
from django.contrib.gis.gdal.geometries import OGRGeomType
|
from django.contrib.gis.gdal.geometries import OGRGeomType
|
||||||
from django.contrib.gis.gdal.srs import SpatialReference
|
from django.contrib.gis.gdal.srs import SpatialReference
|
||||||
|
|
||||||
@ -12,8 +13,8 @@ from django.contrib.gis.gdal.srs import SpatialReference
|
|||||||
from django.contrib.gis.gdal.prototypes.ds import \
|
from django.contrib.gis.gdal.prototypes.ds import \
|
||||||
get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \
|
get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \
|
||||||
get_field_count, get_field_defn, get_field_name, get_field_precision, \
|
get_field_count, get_field_defn, get_field_name, get_field_precision, \
|
||||||
get_field_width, get_layer_defn, get_layer_srs, get_next_feature, \
|
get_field_width, get_field_type, get_layer_defn, get_layer_srs, \
|
||||||
reset_reading
|
get_next_feature, reset_reading
|
||||||
from django.contrib.gis.gdal.prototypes.srs import clone_srs
|
from django.contrib.gis.gdal.prototypes.srs import clone_srs
|
||||||
|
|
||||||
# For more information, see the OGR C API source code:
|
# For more information, see the OGR C API source code:
|
||||||
@ -107,10 +108,24 @@ class Layer(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def fields(self):
|
def fields(self):
|
||||||
"Returns a list of the fields available in this Layer."
|
"""
|
||||||
|
Returns a list of string names corresponding to each of the Fields
|
||||||
|
available in this Layer.
|
||||||
|
"""
|
||||||
return [get_field_name(get_field_defn(self._ldefn, i))
|
return [get_field_name(get_field_defn(self._ldefn, i))
|
||||||
for i in xrange(self.num_fields) ]
|
for i in xrange(self.num_fields) ]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def field_types(self):
|
||||||
|
"""
|
||||||
|
Returns a list of the types of fields in this Layer. For example,
|
||||||
|
the list [OFTInteger, OFTReal, OFTString] would be returned for
|
||||||
|
an OGR layer that had an integer, a floating-point, and string
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
return [FIELD_CLASSES[get_field_type(get_field_defn(self._ldefn, i))]
|
||||||
|
for i in xrange(self.num_fields)]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def field_widths(self):
|
def field_widths(self):
|
||||||
"Returns a list of the maximum field widths for the features."
|
"Returns a list of the maximum field widths for the features."
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,7 +1,12 @@
|
|||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
|
|
||||||
|
class State(models.Model):
|
||||||
|
name = models.CharField(max_length=20)
|
||||||
|
objects = models.GeoManager()
|
||||||
|
|
||||||
class County(models.Model):
|
class County(models.Model):
|
||||||
name = models.CharField(max_length=25)
|
name = models.CharField(max_length=25)
|
||||||
|
state = models.ForeignKey(State)
|
||||||
mpoly = models.MultiPolygonField(srid=4269) # Multipolygon in NAD83
|
mpoly = models.MultiPolygonField(srid=4269) # Multipolygon in NAD83
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
@ -26,6 +31,7 @@ class Interstate(models.Model):
|
|||||||
|
|
||||||
# Mapping dictionaries for the models above.
|
# Mapping dictionaries for the models above.
|
||||||
co_mapping = {'name' : 'Name',
|
co_mapping = {'name' : 'Name',
|
||||||
|
'state' : {'name' : 'State'}, # ForeignKey's use another mapping dictionary for the _related_ Model (State in this case).
|
||||||
'mpoly' : 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS.
|
'mpoly' : 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import os, unittest
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from models import City, County, CountyFeat, Interstate, city_mapping, co_mapping, cofeat_mapping, inter_mapping
|
from models import City, County, CountyFeat, Interstate, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
|
||||||
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal
|
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
|
||||||
from django.contrib.gis.gdal import DataSource
|
from django.contrib.gis.gdal import DataSource
|
||||||
|
|
||||||
shp_path = os.path.dirname(__file__)
|
shp_path = os.path.dirname(__file__)
|
||||||
@ -111,8 +111,8 @@ class LayerMapTest(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(p1[0], p2[0], 6)
|
self.assertAlmostEqual(p1[0], p2[0], 6)
|
||||||
self.assertAlmostEqual(p1[1], p2[1], 6)
|
self.assertAlmostEqual(p1[1], p2[1], 6)
|
||||||
|
|
||||||
def test04_layermap_unique_multigeometry(self):
|
def test04_layermap_unique_multigeometry_fk(self):
|
||||||
"Testing the `unique`, and `transform` keywords and geometry collection conversion."
|
"Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings."
|
||||||
# All the following should work.
|
# All the following should work.
|
||||||
try:
|
try:
|
||||||
# Telling LayerMapping that we want no transformations performed on the data.
|
# Telling LayerMapping that we want no transformations performed on the data.
|
||||||
@ -135,6 +135,23 @@ class LayerMapTest(unittest.TestCase):
|
|||||||
# No source reference system defined in the shapefile, should raise an error.
|
# No source reference system defined in the shapefile, should raise an error.
|
||||||
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
|
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
|
||||||
|
|
||||||
|
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary
|
||||||
|
# mapping for the model the ForeignKey points to.
|
||||||
|
bad_fk_map1 = copy(co_mapping); bad_fk_map1['state'] = 'name'
|
||||||
|
bad_fk_map2 = copy(co_mapping); bad_fk_map2['state'] = {'nombre' : 'State'}
|
||||||
|
self.assertRaises(TypeError, LayerMapping, County, co_shp, bad_fk_map1, transform=False)
|
||||||
|
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, bad_fk_map2, transform=False)
|
||||||
|
|
||||||
|
# There exist no State models for the ForeignKey mapping to work -- should raise
|
||||||
|
# a MissingForeignKey exception (this error would be ignored if the `strict`
|
||||||
|
# keyword is not set).
|
||||||
|
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True, strict=True)
|
||||||
|
self.assertRaises(MissingForeignKey, lm.save)
|
||||||
|
|
||||||
|
# Now creating the state models so the ForeignKey mapping may work.
|
||||||
|
co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
|
||||||
|
co.save(), hi.save(), tx.save()
|
||||||
|
|
||||||
# If a mapping is specified as a collection, all OGR fields that
|
# If a mapping is specified as a collection, all OGR fields that
|
||||||
# are not collections will be converted into them. For example,
|
# are not collections will be converted into them. For example,
|
||||||
# a Point column would be converted to MultiPoint. Other things being done
|
# a Point column would be converted to MultiPoint. Other things being done
|
||||||
@ -148,21 +165,26 @@ class LayerMapTest(unittest.TestCase):
|
|||||||
# appended to the geometry collection of the unique model. Thus,
|
# appended to the geometry collection of the unique model. Thus,
|
||||||
# all of the various islands in Honolulu county will be in in one
|
# all of the various islands in Honolulu county will be in in one
|
||||||
# database record with a MULTIPOLYGON type.
|
# database record with a MULTIPOLYGON type.
|
||||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True)
|
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name', silent=True, strict=True)
|
||||||
lm.save()
|
lm.save()
|
||||||
|
|
||||||
# A reference that doesn't use the unique keyword; a new database record will
|
# A reference that doesn't use the unique keyword; a new database record will
|
||||||
# created for each polygon.
|
# created for each polygon.
|
||||||
lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False, silent=True)
|
lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False, silent=True, strict=True)
|
||||||
lm.save()
|
lm.save()
|
||||||
|
|
||||||
# Dictionary to hold what's expected in the shapefile.
|
# Dictionary to hold what's expected in the shapefile.
|
||||||
exp = {'names' : ('Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo'),
|
names = ('Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo')
|
||||||
'num' : (1, 2, 2, 19, 1), # Number of polygons for each.
|
nums = (1, 2, 1, 19, 1) # Number of polygons for each.
|
||||||
}
|
states = ('Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado')
|
||||||
for name, n in zip(exp['names'], exp['num']):
|
|
||||||
c = County.objects.get(name=name) # Should only be one record.
|
for name, n, st in zip(names, nums, states):
|
||||||
|
# Should only be one record b/c of `unique` keyword.
|
||||||
|
c = County.objects.get(name=name)
|
||||||
self.assertEqual(n, len(c.mpoly))
|
self.assertEqual(n, len(c.mpoly))
|
||||||
|
self.assertEqual(st, c.state.name) # Checking ForeignKey mapping.
|
||||||
|
|
||||||
|
# Multiple records because `unique` was not set.
|
||||||
qs = CountyFeat.objects.filter(name=name)
|
qs = CountyFeat.objects.filter(name=name)
|
||||||
self.assertEqual(n, qs.count())
|
self.assertEqual(n, qs.count())
|
||||||
|
|
||||||
|
@ -45,10 +45,13 @@
|
|||||||
encoding parameters.
|
encoding parameters.
|
||||||
|
|
||||||
check:
|
check:
|
||||||
By default, LayerMapping increments through each feature in the
|
Due to optimizations, this keyword argument is deprecated and will
|
||||||
layer to ensure that it is compatible with the given model and
|
be removed in future revisions.
|
||||||
mapping. Setting this keyword to False, disables this action,
|
|
||||||
which will speed up execution time for very large files.
|
pipe:
|
||||||
|
Status information will be written to this file handle. Defaults
|
||||||
|
to using `sys.stdout`, but any object with a `write` method is
|
||||||
|
supported.
|
||||||
|
|
||||||
silent:
|
silent:
|
||||||
By default, non-fatal error notifications are printed to stdout; this
|
By default, non-fatal error notifications are printed to stdout; this
|
||||||
@ -56,7 +59,8 @@
|
|||||||
|
|
||||||
strict:
|
strict:
|
||||||
Setting this keyword to True will instruct the save() method to
|
Setting this keyword to True will instruct the save() method to
|
||||||
cease execution on the first error encountered.
|
cease execution on the first error encountered. The default behavior
|
||||||
|
is to attempt to continue even if errors are encountered.
|
||||||
|
|
||||||
transaction_mode:
|
transaction_mode:
|
||||||
May be 'commit_on_success' (default) or 'autocommit'.
|
May be 'commit_on_success' (default) or 'autocommit'.
|
||||||
@ -121,64 +125,48 @@ Example:
|
|||||||
the layer, use the `source_srs` keyword with a SpatialReference object to
|
the layer, use the `source_srs` keyword with a SpatialReference object to
|
||||||
specify one.
|
specify one.
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import connection, transaction
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
from django.db.models.fields.related import ForeignKey
|
|
||||||
from django.contrib.gis.db.backend import SPATIAL_BACKEND
|
from django.contrib.gis.db.backend import SPATIAL_BACKEND
|
||||||
from django.contrib.gis.gdal import CoordTransform, DataSource, \
|
from django.contrib.gis.gdal import CoordTransform, DataSource, \
|
||||||
OGRException, OGRGeometry, OGRGeomType, SpatialReference
|
OGRException, OGRGeometry, OGRGeomType, SpatialReference
|
||||||
from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
|
from django.contrib.gis.gdal.field import \
|
||||||
|
OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
|
||||||
from django.contrib.gis.models import GeometryColumns, SpatialRefSys
|
from django.contrib.gis.models import GeometryColumns, SpatialRefSys
|
||||||
|
from django.db import models, transaction
|
||||||
|
|
||||||
# LayerMapping exceptions.
|
# LayerMapping exceptions.
|
||||||
class LayerMapError(Exception): pass
|
class LayerMapError(Exception): pass
|
||||||
class InvalidString(LayerMapError): pass
|
class InvalidString(LayerMapError): pass
|
||||||
class InvalidDecimal(LayerMapError): pass
|
class InvalidDecimal(LayerMapError): pass
|
||||||
|
class MissingForeignKey(LayerMapError): pass
|
||||||
|
|
||||||
class LayerMapping(object):
|
class LayerMapping(object):
|
||||||
"A class that maps OGR Layers to GeoDjango Models."
|
"A class that maps OGR Layers to GeoDjango Models."
|
||||||
|
|
||||||
# A mapping of given geometry types to their OGR integer type.
|
|
||||||
OGC_TYPES = {'POINT' : OGRGeomType('Point'),
|
|
||||||
'LINESTRING' : OGRGeomType('LineString'),
|
|
||||||
'POLYGON' : OGRGeomType('Polygon'),
|
|
||||||
'MULTIPOINT' : OGRGeomType('MultiPoint'),
|
|
||||||
'MULTILINESTRING' : OGRGeomType('MultiLineString'),
|
|
||||||
'MULTIPOLYGON' : OGRGeomType('MultiPolygon'),
|
|
||||||
'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# The django.contrib.gis model types.
|
|
||||||
GIS_FIELDS = {'PointField' : 'POINT',
|
|
||||||
'LineStringField': 'LINESTRING',
|
|
||||||
'PolygonField': 'POLYGON',
|
|
||||||
'MultiPointField' : 'MULTIPOINT',
|
|
||||||
'MultiLineStringField' : 'MULTILINESTRING',
|
|
||||||
'MultiPolygonField' : 'MULTIPOLYGON',
|
|
||||||
'GeometryCollectionField' : 'GEOMETRYCOLLECTION',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Acceptable 'base' types for a multi-geometry type.
|
# Acceptable 'base' types for a multi-geometry type.
|
||||||
MULTI_TYPES = {'POINT' : OGRGeomType('MultiPoint'),
|
MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
|
||||||
'LINESTRING' : OGRGeomType('MultiLineString'),
|
2 : OGRGeomType('MultiLineString'),
|
||||||
'POLYGON' : OGRGeomType('MultiPolygon'),
|
3 : OGRGeomType('MultiPolygon'),
|
||||||
}
|
}
|
||||||
|
|
||||||
# The acceptable Django field types that map to OGR fields.
|
# Acceptable Django field types and corresponding acceptable OGR
|
||||||
|
# counterparts.
|
||||||
FIELD_TYPES = {
|
FIELD_TYPES = {
|
||||||
'AutoField' : OFTInteger,
|
models.AutoField : OFTInteger,
|
||||||
'IntegerField' : OFTInteger,
|
models.IntegerField : (OFTInteger, OFTReal),
|
||||||
'FloatField' : OFTReal,
|
models.FloatField : (OFTInteger, OFTReal),
|
||||||
'DateField' : OFTDate,
|
models.DateField : OFTDate,
|
||||||
'DateTimeField' : OFTDateTime,
|
models.DateTimeField : OFTDateTime,
|
||||||
'TimeField' : OFTTime,
|
models.TimeField : OFTTime,
|
||||||
'DecimalField' : OFTReal,
|
models.DecimalField : (OFTInteger, OFTReal),
|
||||||
'CharField' : OFTString,
|
models.CharField : OFTString,
|
||||||
'TextField' : OFTString,
|
models.TextField : OFTString,
|
||||||
'SmallIntegerField' : OFTInteger,
|
models.SmallIntegerField : (OFTInteger, OFTReal),
|
||||||
'PositiveSmallIntegerField' : OFTInteger,
|
models.PositiveSmallIntegerField : (OFTInteger, OFTReal),
|
||||||
}
|
}
|
||||||
|
|
||||||
# The acceptable transaction modes.
|
# The acceptable transaction modes.
|
||||||
@ -187,17 +175,17 @@ class LayerMapping(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, model, data, mapping, layer=0,
|
def __init__(self, model, data, mapping, layer=0,
|
||||||
source_srs=None, encoding=None, check=True,
|
source_srs=None, encoding=None, check=True, pipe=sys.stdout,
|
||||||
progress=False, interval=1000, strict=False, silent=False,
|
progress=False, interval=1000, strict=False, silent=False,
|
||||||
transaction_mode='commit_on_success', transform=True,
|
transaction_mode='commit_on_success', transform=True,
|
||||||
unique=False):
|
unique=False):
|
||||||
"Takes the Django model, the data source, and the mapping (dictionary)"
|
"""
|
||||||
|
A LayerMapping object is initialized using the given Model (not an instance),
|
||||||
# Getting the field names and types from the model
|
a DataSource (or string path to an OGR-supported data file), and a mapping
|
||||||
self.fields = dict((f.name, self.map_foreign_key(f)) for f in model._meta.fields)
|
dictionary. See the module level docstring for more details and keyword
|
||||||
self.field_classes = dict((f.name, f) for f in model._meta.fields)
|
argument usage.
|
||||||
|
"""
|
||||||
# Getting the DataSource and its Layer
|
# Getting the DataSource and the associated Layer.
|
||||||
if isinstance(data, basestring):
|
if isinstance(data, basestring):
|
||||||
self.ds = DataSource(data)
|
self.ds = DataSource(data)
|
||||||
else:
|
else:
|
||||||
@ -223,14 +211,16 @@ class LayerMapping(object):
|
|||||||
self.transform = transform
|
self.transform = transform
|
||||||
|
|
||||||
# Checking the layer -- intitialization of the object will fail if
|
# Checking the layer -- intitialization of the object will fail if
|
||||||
# things don't check out before hand. This may be time-consuming,
|
# things don't check out before hand.
|
||||||
# and disabled by setting the `check` keyword to False.
|
self.check_layer()
|
||||||
if check: self.check_layer()
|
|
||||||
|
|
||||||
# The silent, strict, progress, and interval flags.
|
# The strict flag -- if it is set, exceptions will be propagated up.
|
||||||
self.silent = silent
|
|
||||||
self.strict = strict
|
self.strict = strict
|
||||||
|
|
||||||
|
# Setting the keyword arguments related to status printing.
|
||||||
|
self.silent = silent
|
||||||
self.progress = progress
|
self.progress = progress
|
||||||
|
self.pipe = pipe
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
|
|
||||||
# Setting the encoding for OFTString fields, if specified.
|
# Setting the encoding for OFTString fields, if specified.
|
||||||
@ -258,62 +248,91 @@ class LayerMapping(object):
|
|||||||
else:
|
else:
|
||||||
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
|
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
|
||||||
|
|
||||||
def check_feature(self, feat):
|
#### Checking routines used during initialization ####
|
||||||
"Checks the OGR feature against the model fields and mapping."
|
|
||||||
HAS_GEO = False
|
|
||||||
|
|
||||||
# Incrementing through each model_field & ogr_field in the given mapping.
|
|
||||||
for model_field, ogr_field in self.mapping.items():
|
|
||||||
# Making sure the given mapping model field is in the given model fields.
|
|
||||||
if model_field in self.fields:
|
|
||||||
model_type = self.fields[model_field]
|
|
||||||
elif model_field[:-3] in self.fields: #foreign key
|
|
||||||
model_type = self.fields[model_field[:-3]]
|
|
||||||
else:
|
|
||||||
raise LayerMapError('Given mapping field "%s" not in given Model fields!' % model_field)
|
|
||||||
|
|
||||||
### Handling if we get a geometry in the Field ###
|
|
||||||
if ogr_field in self.OGC_TYPES:
|
|
||||||
# At this time, no more than one geographic field per model =(
|
|
||||||
if HAS_GEO:
|
|
||||||
raise LayerMapError('More than one geographic field in mapping not allowed (yet).')
|
|
||||||
else:
|
|
||||||
HAS_GEO = ogr_field
|
|
||||||
|
|
||||||
# Making sure this geometry field type is a valid Django GIS field.
|
|
||||||
if not model_type in self.GIS_FIELDS:
|
|
||||||
raise LayerMapError('Unknown Django GIS field type "%s"' % model_type)
|
|
||||||
|
|
||||||
# Getting the OGRGeometry, it's type (an integer) and it's name (a string)
|
|
||||||
geom = feat.geom
|
|
||||||
gtype = geom.geom_type
|
|
||||||
gname = geom.geom_name
|
|
||||||
|
|
||||||
if self.make_multi(gname, model_type):
|
|
||||||
# Do we have to 'upsample' into a Geometry Collection?
|
|
||||||
pass
|
|
||||||
elif gtype == self.OGC_TYPES[self.GIS_FIELDS[model_type]]:
|
|
||||||
# The geometry type otherwise was expected
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s' % (model_type, gtype))
|
|
||||||
### Handling other fields ###
|
|
||||||
else:
|
|
||||||
# Making sure the model field is supported.
|
|
||||||
if not model_type in self.FIELD_TYPES:
|
|
||||||
raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % model_type)
|
|
||||||
|
|
||||||
# Otherwise, we've got an OGR Field. Making sure that an
|
|
||||||
# index exists for the mapping OGR field.
|
|
||||||
try:
|
|
||||||
fi = feat.index(ogr_field)
|
|
||||||
except:
|
|
||||||
raise LayerMapError('Given mapping OGR field "%s" not in given OGR layer feature!' % ogr_field)
|
|
||||||
|
|
||||||
def check_layer(self):
|
def check_layer(self):
|
||||||
"Checks every feature in this object's layer."
|
"""
|
||||||
for feat in self.layer:
|
This checks the Layer metadata, and ensures that it is compatible
|
||||||
self.check_feature(feat)
|
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.
|
||||||
|
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.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
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 (gtype == ltype or self.make_multi(ltype, model_field)):
|
||||||
|
raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype))
|
||||||
|
|
||||||
|
# Setting the `geom_field` attribute w/the name of the model field
|
||||||
|
# that is a Geometry.
|
||||||
|
self.geom_field = field_name
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Can the OGR field type be mapped to the Django field type?
|
||||||
|
if not issubclass(ogr_field_types[idx], self.FIELD_TYPES[model_field.__class__]):
|
||||||
|
raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' %
|
||||||
|
(ogr_field, ogr_field_types[idx].__name__, fld_name))
|
||||||
|
fields_val = model_field
|
||||||
|
|
||||||
|
self.fields[field_name] = fields_val
|
||||||
|
|
||||||
def check_srs(self, source_srs):
|
def check_srs(self, source_srs):
|
||||||
"Checks the compatibility of the given spatial reference object."
|
"Checks the compatibility of the given spatial reference object."
|
||||||
@ -321,7 +340,7 @@ class LayerMapping(object):
|
|||||||
sr = source_srs
|
sr = source_srs
|
||||||
elif isinstance(source_srs, SpatialRefSys):
|
elif isinstance(source_srs, SpatialRefSys):
|
||||||
sr = source_srs.srs
|
sr = source_srs.srs
|
||||||
elif isinstance(source_srs, (int, str)):
|
elif isinstance(source_srs, (int, basestring)):
|
||||||
sr = SpatialReference(source_srs)
|
sr = SpatialReference(source_srs)
|
||||||
else:
|
else:
|
||||||
# Otherwise just pulling the SpatialReference from the layer
|
# Otherwise just pulling the SpatialReference from the layer
|
||||||
@ -334,14 +353,6 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
def check_unique(self, unique):
|
def check_unique(self, unique):
|
||||||
"Checks the `unique` keyword parameter -- may be a sequence or string."
|
"Checks the `unique` keyword parameter -- may be a sequence or string."
|
||||||
# Getting the geometry field; only the first encountered GeometryField
|
|
||||||
# will be used.
|
|
||||||
self.geom_field = False
|
|
||||||
for model_field, ogr_fld in self.mapping.items():
|
|
||||||
if ogr_fld in self.OGC_TYPES:
|
|
||||||
self.geom_field = model_field
|
|
||||||
break
|
|
||||||
|
|
||||||
if isinstance(unique, (list, tuple)):
|
if isinstance(unique, (list, tuple)):
|
||||||
# List of fields to determine uniqueness with
|
# List of fields to determine uniqueness with
|
||||||
for attr in unique:
|
for attr in unique:
|
||||||
@ -352,20 +363,14 @@ class LayerMapping(object):
|
|||||||
else:
|
else:
|
||||||
raise TypeError('Unique keyword argument must be set with a tuple, list, or string.')
|
raise TypeError('Unique keyword argument must be set with a tuple, list, or string.')
|
||||||
|
|
||||||
def coord_transform(self):
|
#### Keyword argument retrieval routines ####
|
||||||
"Returns the coordinate transformation object."
|
|
||||||
try:
|
|
||||||
# Getting the target spatial reference system
|
|
||||||
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
|
|
||||||
|
|
||||||
# Creating the CoordTransform object
|
|
||||||
return CoordTransform(self.source_srs, target_srs)
|
|
||||||
except Exception, msg:
|
|
||||||
raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
|
|
||||||
|
|
||||||
def feature_kwargs(self, feat):
|
def feature_kwargs(self, feat):
|
||||||
"Returns the keyword arguments needed for saving a feature."
|
"""
|
||||||
|
Given an OGR Feature, this will return a dictionary of keyword arguments
|
||||||
|
for constructing the mapped model. Also returned is the `all_prepped`
|
||||||
|
flag, which is used to signal that a model corresponding to a ForeignKey
|
||||||
|
mapping does not exist.
|
||||||
|
"""
|
||||||
# The keyword arguments for model construction.
|
# The keyword arguments for model construction.
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
@ -375,37 +380,24 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
# Incrementing through each model field and OGR field in the
|
# Incrementing through each model field and OGR field in the
|
||||||
# dictionary mapping.
|
# dictionary mapping.
|
||||||
for model_field, ogr_field in self.mapping.items():
|
for field_name, ogr_name in self.mapping.items():
|
||||||
is_fk = False
|
model_field = self.fields[field_name]
|
||||||
try:
|
|
||||||
model_type = self.fields[model_field]
|
|
||||||
except KeyError: #foreign key
|
|
||||||
# The -3 index is b/c foreign keys are appended w/'_id'.
|
|
||||||
model_type = self.fields[model_field[:-3]]
|
|
||||||
is_fk = True
|
|
||||||
|
|
||||||
if ogr_field in self.OGC_TYPES:
|
if isinstance(model_field, GeometryField):
|
||||||
# Verify OGR geometry.
|
# Verify OGR geometry.
|
||||||
val = self.verify_geom(feat.geom, model_type)
|
val = self.verify_geom(feat.geom, model_field)
|
||||||
|
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)
|
||||||
|
if not val: all_prepped = False
|
||||||
else:
|
else:
|
||||||
# Otherwise, verify OGR Field type.
|
# Otherwise, verify OGR Field type.
|
||||||
val = self.verify_field(feat[ogr_field], model_field)
|
val = self.verify_ogr_field(feat[ogr_name], model_field)
|
||||||
|
|
||||||
if is_fk:
|
# Setting the keyword arguments for the field name with the
|
||||||
# Handling if foreign key.
|
# value obtained above.
|
||||||
rel_obj = None
|
kwargs[field_name] = val
|
||||||
field_name = model_field[:-3]
|
|
||||||
try:
|
|
||||||
# FIXME: refactor to efficiently fetch FKs.
|
|
||||||
# Requires significant re-work. :-/
|
|
||||||
rel = self.model._meta.get_field(field_name).rel
|
|
||||||
rel_obj = rel.to._default_manager.get(**{('%s__exact' % rel.field_name):val})
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
all_prepped = False
|
|
||||||
|
|
||||||
kwargs[model_field[:-3]] = rel_obj
|
|
||||||
else:
|
|
||||||
kwargs[model_field] = val
|
|
||||||
|
|
||||||
return kwargs, all_prepped
|
return kwargs, all_prepped
|
||||||
|
|
||||||
@ -420,29 +412,29 @@ class LayerMapping(object):
|
|||||||
else:
|
else:
|
||||||
return dict((fld, kwargs[fld]) for fld in self.unique)
|
return dict((fld, kwargs[fld]) for fld in self.unique)
|
||||||
|
|
||||||
def verify_field(self, fld, model_field):
|
#### 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
|
Verifies if the OGR Field contents are acceptable to the Django
|
||||||
model field. If they are, the verified value is returned,
|
model field. If they are, the verified value is returned,
|
||||||
otherwise the proper exception is raised.
|
otherwise the proper exception is raised.
|
||||||
"""
|
"""
|
||||||
field_class = self.field_classes[model_field]
|
if isinstance(ogr_field, OFTString):
|
||||||
if isinstance(fld, OFTString):
|
|
||||||
if self.encoding:
|
if self.encoding:
|
||||||
# The encoding for OGR data sources may be specified here
|
# The encoding for OGR data sources may be specified here
|
||||||
# (e.g., 'cp437' for Census Bureau boundary files).
|
# (e.g., 'cp437' for Census Bureau boundary files).
|
||||||
val = unicode(fld.value, self.encoding)
|
val = unicode(ogr_field.value, self.encoding)
|
||||||
else:
|
else:
|
||||||
val = fld.value
|
val = ogr_field.value
|
||||||
if len(val) > field_class.max_length:
|
if len(val) > model_field.max_length:
|
||||||
raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
|
raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
|
||||||
(model_field, field_class.max_length, len(val)))
|
(model_field.name, model_field.max_length, len(val)))
|
||||||
elif isinstance(fld, OFTReal):
|
elif isinstance(ogr_field, OFTReal):
|
||||||
try:
|
try:
|
||||||
# Creating an instance of the Decimal value to use.
|
# Creating an instance of the Decimal value to use.
|
||||||
d = Decimal(str(fld.value))
|
d = Decimal(str(ogr_field.value))
|
||||||
except:
|
except:
|
||||||
raise InvalidDecimal('Could not construct decimal from: %s' % fld)
|
raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field)
|
||||||
|
|
||||||
# Getting the decimal value as a tuple.
|
# Getting the decimal value as a tuple.
|
||||||
dtup = d.as_tuple()
|
dtup = d.as_tuple()
|
||||||
@ -450,7 +442,7 @@ class LayerMapping(object):
|
|||||||
d_idx = dtup[2] # index where the decimal is
|
d_idx = dtup[2] # index where the decimal is
|
||||||
|
|
||||||
# Maximum amount of precision, or digits to the left of the decimal.
|
# Maximum amount of precision, or digits to the left of the decimal.
|
||||||
max_prec = field_class.max_digits - field_class.decimal_places
|
max_prec = model_field.max_digits - model_field.decimal_places
|
||||||
|
|
||||||
# Getting the digits to the left of the decimal place for the
|
# Getting the digits to the left of the decimal place for the
|
||||||
# given decimal.
|
# given decimal.
|
||||||
@ -463,17 +455,43 @@ class LayerMapping(object):
|
|||||||
# InvalidDecimal exception.
|
# InvalidDecimal exception.
|
||||||
if n_prec > max_prec:
|
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.' %
|
raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' %
|
||||||
(field_class.max_digits, field_class.decimal_places, max_prec))
|
(model_field.max_digits, model_field.decimal_places, max_prec))
|
||||||
val = d
|
val = d
|
||||||
else:
|
else:
|
||||||
val = fld.value
|
val = ogr_field.value
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def verify_geom(self, geom, model_type):
|
def verify_fk(self, feat, rel_model, rel_mapping):
|
||||||
"Verifies the geometry."
|
"""
|
||||||
if self.make_multi(geom.geom_name, model_type):
|
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.get(**fk_kwargs)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
if self.strict: raise MissingForeignKey('No %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs))
|
||||||
|
else: return None
|
||||||
|
|
||||||
|
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).
|
||||||
|
"""
|
||||||
|
if self.make_multi(geom.geom_type, model_field):
|
||||||
# Constructing a multi-geometry type to contain the single geometry
|
# Constructing a multi-geometry type to contain the single geometry
|
||||||
multi_type = self.MULTI_TYPES[geom.geom_name]
|
multi_type = self.MULTI_TYPES[geom.geom_type.num]
|
||||||
g = OGRGeometry(multi_type)
|
g = OGRGeometry(multi_type)
|
||||||
g.add(geom)
|
g.add(geom)
|
||||||
else:
|
else:
|
||||||
@ -487,6 +505,18 @@ class LayerMapping(object):
|
|||||||
# Returning the WKT of the geometry.
|
# Returning the WKT of the geometry.
|
||||||
return g.wkt
|
return g.wkt
|
||||||
|
|
||||||
|
#### Other model methods ####
|
||||||
|
def coord_transform(self):
|
||||||
|
"Returns the coordinate transformation object."
|
||||||
|
try:
|
||||||
|
# Getting the target spatial reference system
|
||||||
|
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
|
||||||
|
|
||||||
|
# Creating the CoordTransform object
|
||||||
|
return CoordTransform(self.source_srs, target_srs)
|
||||||
|
except Exception, msg:
|
||||||
|
raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
|
||||||
|
|
||||||
def geometry_column(self):
|
def geometry_column(self):
|
||||||
"Returns the GeometryColumn model associated with the geographic column."
|
"Returns the GeometryColumn model associated with the geographic column."
|
||||||
# Getting the GeometryColumn object.
|
# Getting the GeometryColumn object.
|
||||||
@ -498,24 +528,23 @@ class LayerMapping(object):
|
|||||||
except Exception, msg:
|
except Exception, msg:
|
||||||
raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg)
|
raise LayerMapError('Geometry column does not exist for model. (did you run syncdb?):\n %s' % msg)
|
||||||
|
|
||||||
def make_multi(self, geom_name, model_type):
|
def make_multi(self, geom_type, model_field):
|
||||||
"Determines whether the geometry should be made into a GeometryCollection."
|
"""
|
||||||
return (geom_name in self.MULTI_TYPES) and (model_type.startswith('Multi'))
|
Given the OGRGeomType for a geometry and its associated GeometryField,
|
||||||
|
determine whether the geometry should be turned into a GeometryCollection.
|
||||||
def map_foreign_key(self, django_field):
|
"""
|
||||||
"Handles fields within foreign keys for the given field."
|
return (geom_type.num in self.MULTI_TYPES and
|
||||||
if not django_field.__class__ is ForeignKey:
|
model_field.__class__.__name__ == 'Multi%s' % geom_type.django)
|
||||||
# Returning the field's class name.
|
|
||||||
return django_field.__class__.__name__
|
|
||||||
else:
|
|
||||||
# Otherwise, getting the type of the related field's
|
|
||||||
# from the Foreign key.
|
|
||||||
rf = django_field.rel.get_related_field()
|
|
||||||
return rf.get_internal_type()
|
|
||||||
|
|
||||||
def save(self, verbose=False):
|
def save(self, verbose=False):
|
||||||
"Runs the layer mapping on the given SHP file, and saves to the database."
|
"""
|
||||||
|
Saves the contents from the OGR DataSource Layer into the database
|
||||||
|
according to the mapping dictionary given at initialization. If
|
||||||
|
the `verbose` keyword is set, information will be printed subsequent
|
||||||
|
to each model save executed on the database.
|
||||||
|
"""
|
||||||
|
# Defining the 'real' save method, utilizing the transaction
|
||||||
|
# decorator created during initialization.
|
||||||
@self.transaction_decorator
|
@self.transaction_decorator
|
||||||
def _save():
|
def _save():
|
||||||
num_feat = 0
|
num_feat = 0
|
||||||
@ -530,7 +559,7 @@ class LayerMapping(object):
|
|||||||
# Something borked the validation
|
# Something borked the validation
|
||||||
if self.strict: raise
|
if self.strict: raise
|
||||||
elif not self.silent:
|
elif not self.silent:
|
||||||
print 'Ignoring Feature ID %s because: %s' % (feat.fid, msg)
|
self.pipe.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg))
|
||||||
else:
|
else:
|
||||||
# Constructing the model using the keyword args
|
# Constructing the model using the keyword args
|
||||||
if all_prepped:
|
if all_prepped:
|
||||||
@ -561,7 +590,7 @@ class LayerMapping(object):
|
|||||||
# Attempting to save.
|
# Attempting to save.
|
||||||
m.save()
|
m.save()
|
||||||
num_saved += 1
|
num_saved += 1
|
||||||
if verbose: print 'Saved: %s' % m
|
if verbose: self.pipe.write('Saved: %s\n' % m)
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except Exception, msg:
|
except Exception, msg:
|
||||||
@ -572,17 +601,18 @@ class LayerMapping(object):
|
|||||||
if self.strict:
|
if self.strict:
|
||||||
# Bailing out if the `strict` keyword is set.
|
# Bailing out if the `strict` keyword is set.
|
||||||
if not self.silent:
|
if not self.silent:
|
||||||
print 'Failed to save the feature (id: %s) into the model with the keyword arguments:' % feat.fid
|
self.pipe.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid)
|
||||||
print kwargs
|
self.pipe.write('%s\n' % kwargs)
|
||||||
raise
|
raise
|
||||||
elif not self.silent:
|
elif not self.silent:
|
||||||
print 'Failed to save %s:\n %s\nContinuing' % (kwargs, msg)
|
self.pipe.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg))
|
||||||
else:
|
else:
|
||||||
print 'Skipping %s due to missing relation.' % kwargs
|
if not self.silent: self.pipe.write('Skipping due to missing relation:\n%s\n' % kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Printing progress information, if requested.
|
# Printing progress information, if requested.
|
||||||
if self.progress and num_feat % self.interval == 0:
|
if self.progress and num_feat % self.interval == 0:
|
||||||
print 'Processed %d features, saved %d ...' % (num_feat, num_saved)
|
self.pipe.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved))
|
||||||
|
|
||||||
# Calling our defined function, which will use the specified
|
# Calling our defined function, which will use the specified
|
||||||
# trasaction mode.
|
# trasaction mode.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user