1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +00:00

gis: LayerMapping refactor

(1) Moved all routines into LayerMapping class (for easier subclassing) and modularized the routines.
 (2) OFTString and OFTReal OGR fields are verified w/the Django fields prior to insertion, thus avoiding invalidating a large transaction.
 (3) Added keyword options for specifying the transaction mode, not performing transformations, and status printing.
 (4) Created unit tests.
Other Changes:
 Updated `ogrinfo` for GDAL refactor and fixed an iterating bug; simplified a few lines in `geoapp` model tests.


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6687 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-11-17 21:57:12 +00:00
parent ef32f913a0
commit e88ce426b6
15 changed files with 542 additions and 271 deletions

View File

@ -2,7 +2,13 @@ import sys
from copy import copy from copy import copy
from unittest import TestSuite, TextTestRunner from unittest import TestSuite, TextTestRunner
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
try:
from django.contrib.gis.tests.utils import mysql from django.contrib.gis.tests.utils import mysql
except:
mysql = False
# Tests that require use of a spatial database (e.g., creation of models)
test_models = ['geoapp']
# Tests that do not require setting up and tearing down a spatial database. # Tests that do not require setting up and tearing down a spatial database.
test_suite_names = [ test_suite_names = [
@ -10,6 +16,7 @@ test_suite_names = [
'test_measure', 'test_measure',
] ]
if HAS_GDAL: if HAS_GDAL:
test_models += ['layermap']
test_suite_names += [ test_suite_names += [
'test_gdal_driver', 'test_gdal_driver',
'test_gdal_ds', 'test_gdal_ds',
@ -21,8 +28,6 @@ if HAS_GDAL:
else: else:
print >>sys.stderr, "GDAL not available - no GDAL tests will be run." print >>sys.stderr, "GDAL not available - no GDAL tests will be run."
test_models = ['geoapp']
def suite(): def suite():
"Builds a test suite for the GIS package." "Builds a test suite for the GIS package."
s = TestSuite() s = TestSuite()
@ -62,7 +67,7 @@ def run_tests(module_list, verbosity=1, interactive=True):
`pg_config --sharedir` (and defaults to /usr/local/share if that fails). `pg_config --sharedir` (and defaults to /usr/local/share if that fails).
This behavior is overridden if `POSTGIS_SQL_PATH` is in your settings. This behavior is overridden if `POSTGIS_SQL_PATH` is in your settings.
Windows users should use the POSTGIS_SQL_PATH because the output Windows users should set POSTGIS_SQL_PATH manually because the output
of `pg_config` uses paths like 'C:/PROGRA~1/POSTGR~1/..'. of `pg_config` uses paths like 'C:/PROGRA~1/POSTGR~1/..'.
Finally, the tests may be run by invoking `./manage.py test`. Finally, the tests may be run by invoking `./manage.py test`.
@ -93,9 +98,10 @@ def run_tests(module_list, verbosity=1, interactive=True):
tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]), test_module_name) tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]), test_module_name)
test_suite.addTest(tsuite.suite()) test_suite.addTest(tsuite.suite())
# Resetting the loaded flag to take into account what we appended to the INSTALLED_APPS # Resetting the loaded flag to take into account what we appended to
# (since this routine is invoked through django/core/management, it caches the apps, # the INSTALLED_APPS (since this routine is invoked through
# this ensures that syncdb will see our appended models) # django/core/management, it caches the apps; this ensures that syncdb
# will see our appended models)
from django.db.models import loading from django.db.models import loading
loading._loaded = False loading._loaded = False

View File

@ -77,7 +77,7 @@ class GeoModelTest(unittest.TestCase):
inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40)) inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
# Creating a State object using a built Polygon # Creating a State object using a built Polygon
ply = Polygon(shell.clone(), inner.clone()) ply = Polygon(shell, inner)
nullstate = State(name='NullState', poly=ply) nullstate = State(name='NullState', poly=ply)
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
nullstate.save() nullstate.save()
@ -94,12 +94,12 @@ class GeoModelTest(unittest.TestCase):
# Changing the interior ring on the poly attribute. # Changing the interior ring on the poly attribute.
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30)) new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
nullstate.poly[1] = new_inner.clone() ns.poly[1] = new_inner
ply[1] = new_inner ply[1] = new_inner
self.assertEqual(4326, nullstate.poly.srid) self.assertEqual(4326, ns.poly.srid)
nullstate.save() ns.save()
self.assertEqual(ply, State.objects.get(name='NullState').poly) self.assertEqual(ply, State.objects.get(name='NullState').poly)
nullstate.delete() ns.delete()
@no_oracle # Oracle does not support KML. @no_oracle # Oracle does not support KML.
def test03a_kml(self): def test03a_kml(self):

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1,29 @@
from django.contrib.gis.db import models
class City(models.Model):
name = models.CharField(max_length=25)
population = models.IntegerField()
density = models.DecimalField(max_digits=7, decimal_places=1)
date = models.DateField()
point = models.PointField()
objects = models.GeoManager()
class Interstate(models.Model):
name = models.CharField(max_length=20)
length = models.DecimalField(max_digits=7, decimal_places=2)
path = models.LineStringField()
objects = models.GeoManager()
# Mapping dictionary for the City model.
city_mapping = {'name' : 'Name',
'population' : 'Population',
'density' : 'Density',
'date' : 'Created',
'point' : 'POINT',
}
# Mapping dictionary for the Interstate model.
inter_mapping = {'name' : 'Name',
'length' : 'Length',
'path' : 'LINESTRING',
}

View File

@ -0,0 +1,106 @@
import os, unittest
from copy import copy
from datetime import date
from decimal import Decimal
from models import City, Interstate, city_mapping, inter_mapping
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal
from django.contrib.gis.gdal import DataSource
shp_path = os.path.dirname(__file__)
city_shp = os.path.join(shp_path, 'cities/cities.shp')
inter_shp = os.path.join(shp_path, 'interstates/interstates.shp')
class LayerMapTest(unittest.TestCase):
def test01_init(self):
"Testing LayerMapping initialization."
# Model field that does not exist.
bad1 = copy(city_mapping)
bad1['foobar'] = 'FooField'
# Shapefile field that does not exist.
bad2 = copy(city_mapping)
bad2['name'] = 'Nombre'
# Nonexistent geographic field type.
bad3 = copy(city_mapping)
bad3['point'] = 'CURVE'
# Incrementing through the bad mapping dictionaries and
# ensuring that a LayerMapError is raised.
for bad_map in (bad1, bad2, bad3):
try:
lm = LayerMapping(City, city_shp, bad_map)
except LayerMapError:
pass
else:
self.fail('Expected a LayerMapError.')
# A LookupError should be thrown for bogus encodings.
try:
lm = LayerMapping(City, city_shp, city_mapping, encoding='foobar')
except LookupError:
pass
else:
self.fail('Expected a LookupError')
def test02_simple_layermap(self):
"Test LayerMapping import of a simple point shapefile."
# Setting up for the LayerMapping.
lm = LayerMapping(City, city_shp, city_mapping)
lm.save()
# There should be three cities in the shape file.
self.assertEqual(3, City.objects.count())
# Opening up the shapefile, and verifying the values in each
# of the features made it to the model.
ds = DataSource(city_shp)
layer = ds[0]
for feat in layer:
city = City.objects.get(name=feat['Name'].value)
self.assertEqual(feat['Population'].value, city.population)
self.assertEqual(Decimal(str(feat['Density'])), city.density)
self.assertEqual(feat['Created'].value, city.date)
# Comparing the geometries.
pnt1, pnt2 = feat.geom, city.point
self.assertAlmostEqual(pnt1.x, pnt2.x, 6)
self.assertAlmostEqual(pnt1.y, pnt2.y, 6)
def test03_layermap_strict(self):
"Testing the `strict` keyword, and import of a LineString shapefile."
# When the `strict` keyword is set an error encountered will force
# the importation to stop.
try:
lm = LayerMapping(Interstate, inter_shp, inter_mapping,
strict=True, silent=True)
lm.save()
except InvalidDecimal:
pass
else:
self.fail('Should have failed on strict import with invalid decimal values.')
# This LayerMapping should work b/c `strict` is not set.
lm = LayerMapping(Interstate, inter_shp, inter_mapping, silent=True)
lm.save()
# Only one interstate should have imported correctly.
self.assertEqual(1, Interstate.objects.count())
# Verifying the values in the single feature w/the model.
ds = DataSource(inter_shp)
feat = ds[0][0]
istate = Interstate.objects.get(name=feat['Name'].value)
self.assertEqual(Decimal(str(feat['Length'])), istate.length)
for p1, p2 in zip(feat.geom, istate.path):
self.assertAlmostEqual(p1[0], p2[0], 6)
self.assertAlmostEqual(p1[1], p2[1], 6)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(LayerMapTest))
return s

View File

@ -6,13 +6,11 @@ The LayerMapping class provides a way to map the contents of OGR
This grew out of my personal needs, specifically the code repetition This grew out of my personal needs, specifically the code repetition
that went into pulling geometries and fields out of an OGR layer, that went into pulling geometries and fields out of an OGR layer,
converting to another coordinate system (e.g. WGS84), and then inserting converting to another coordinate system (e.g. WGS84), and then inserting
into a Geographic Django model. into a GeoDjango model.
This utility is still in early stages of development, so its usage This utility is still in early stages of development, so its usage
is subject to change -- please report any bugs. is subject to change -- please report any bugs.
TODO: Unit tests and documentation.
Requirements: OGR C Library (from GDAL) required. Requirements: OGR C Library (from GDAL) required.
Usage: Usage:
@ -46,6 +44,26 @@ Keyword Args:
For example, 'latin-1', 'utf-8', and 'cp437' are all valid For example, 'latin-1', 'utf-8', and 'cp437' are all valid
encoding parameters. encoding parameters.
check:
By default, LayerMapping increments through each feature in the
layer to ensure that it is compatible with the given model and
mapping. Setting this keyword to False, disables this action,
which will speed up execution time for very large files.
silent:
By default, non-fatal error notifications are printed to stdout; this
keyword may be set in order to disable these notifications.
strict:
Setting this keyword to True will instruct the save() method to
cease execution on the first error encountered.
transaction_mode:
May be 'commit_on_success' (default) or 'autocommit'.
transform:
Setting this to False will disable all coordinate transformations.
Example: Example:
1. You need a GDAL-supported data source, like a shapefile. 1. You need a GDAL-supported data source, like a shapefile.
@ -94,22 +112,29 @@ Example:
source spatial reference system (WGS84) to the spatial reference system of source spatial reference system (WGS84) to the spatial reference system of
the GeoDjango model (NAD83). If no spatial reference system is defined for the GeoDjango model (NAD83). If no spatial reference system is defined for
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. Further, data is selectively imported from the given data source specify one.
fields into the model fields.
""" """
from types import StringType, TupleType from datetime import date, datetime
from datetime import datetime from decimal import Decimal
from django.contrib.gis.db.backend import SPATIAL_BACKEND
from django.contrib.gis.gdal import \
OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
DataSource, OGRException
from django.contrib.gis.gdal.field import Field, OFTInteger, OFTReal, OFTString, OFTDateTime
from django.contrib.gis.models import GeometryColumns, SpatialRefSys
from django.db import connection, transaction
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import connection, transaction
from django.db.models.fields.related import ForeignKey
from django.contrib.gis.db.backend import SPATIAL_BACKEND
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.contrib.gis.models import GeometryColumns, SpatialRefSys
# LayerMapping exceptions.
class LayerMapError(Exception): pass
class InvalidString(LayerMapError): pass
class InvalidDecimal(LayerMapError): pass
class LayerMapping(object):
"A class that maps OGR Layers to GeoDjango Models."
# A mapping of given geometry types to their OGR integer type. # A mapping of given geometry types to their OGR integer type.
ogc_types = {'POINT' : OGRGeomType('Point'), OGC_TYPES = {'POINT' : OGRGeomType('Point'),
'LINESTRING' : OGRGeomType('LineString'), 'LINESTRING' : OGRGeomType('LineString'),
'POLYGON' : OGRGeomType('Polygon'), 'POLYGON' : OGRGeomType('Polygon'),
'MULTIPOINT' : OGRGeomType('MultiPoint'), 'MULTIPOINT' : OGRGeomType('MultiPoint'),
@ -119,134 +144,51 @@ ogc_types = {'POINT' : OGRGeomType('Point'),
} }
# The django.contrib.gis model types. # The django.contrib.gis model types.
gis_fields = {'PointField' : 'POINT', GIS_FIELDS = {'PointField' : 'POINT',
'LineStringField': 'LINESTRING', 'LineStringField': 'LINESTRING',
'PolygonField': 'POLYGON', 'PolygonField': 'POLYGON',
'MultiPointField' : 'MULTIPOINT', 'MultiPointField' : 'MULTIPOINT',
'MultiLineStringField' : 'MULTILINESTRING', 'MultiLineStringField' : 'MULTILINESTRING',
'MultiPolygonField' : 'MULTIPOLYGON', '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 = {'POINT' : OGRGeomType('MultiPoint'),
'LINESTRING' : OGRGeomType('MultiLineString'), 'LINESTRING' : OGRGeomType('MultiLineString'),
'POLYGON' : OGRGeomType('MultiPolygon'), 'POLYGON' : OGRGeomType('MultiPolygon'),
} }
def map_foreign_key(django_field):
from django.db.models.fields.related import ForeignKey
if not django_field.__class__ is ForeignKey:
return django_field.__class__.__name__
rf=django_field.rel.get_related_field()
return rf.get_internal_type()
# The acceptable Django field types that map to OGR fields. # The acceptable Django field types that map to OGR fields.
field_types = { FIELD_TYPES = {
'AutoField' : OFTInteger, 'AutoField' : OFTInteger,
'IntegerField' : OFTInteger, 'IntegerField' : OFTInteger,
'FloatField' : OFTReal, 'FloatField' : OFTReal,
'DateField' : OFTDate,
'DateTimeField' : OFTDateTime, 'DateTimeField' : OFTDateTime,
'TimeField' : OFTTime,
'DecimalField' : OFTReal, 'DecimalField' : OFTReal,
'CharField' : OFTString, 'CharField' : OFTString,
'TextField' : OFTString,
'SmallIntegerField' : OFTInteger, 'SmallIntegerField' : OFTInteger,
'PositiveSmallIntegerField' : OFTInteger, 'PositiveSmallIntegerField' : OFTInteger,
} }
def make_multi(geom_name, model_type): # The acceptable transaction modes.
"Determines whether the geometry should be made into a GeometryCollection." TRANSACTION_MODES = {'autocommit' : transaction.autocommit,
if (geom_name in multi_types) and (model_type.startswith('Multi')): 'commit_on_success' : transaction.commit_on_success,
return True }
else:
return False
def check_feature(feat, model_fields, mapping): def __init__(self, model, data, mapping, layer=0,
"Checks the OGR layer feature." source_srs=None, encoding=None, check=True,
progress=False, interval=1000, strict=False, silent=False,
HAS_GEO = False transaction_mode='commit_on_success', transform=True):
# Incrementing through each model_field & ogr_field in the given mapping.
for model_field, ogr_field in mapping.items():
# Making sure the given mapping model field is in the given model fields.
if model_field in model_fields:
model_type = model_fields[model_field]
elif model_field[:-3] in model_fields: #foreign key
model_type = model_fields[model_field[:-3]]
else:
raise Exception('Given mapping field "%s" not in given Model fields!' % model_field)
### Handling if we get a geometry in the Field ###
if ogr_field in ogc_types:
# At this time, no more than one geographic field per model =(
if HAS_GEO:
raise Exception('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 gis_fields:
raise Exception('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 make_multi(gname, model_type):
# Do we have to 'upsample' into a Geometry Collection?
pass
elif gtype == ogc_types[gis_fields[model_type]]:
# The geometry type otherwise was expected
pass
else:
raise Exception('Invalid mapping geometry; model has %s, feature has %s' % (model_type, gtype))
## Handling other fields
else:
# Making sure the model field is
if not model_type in field_types:
raise Exception('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 Exception('Given mapping OGR field "%s" not in given OGR layer feature!' % ogr_field)
def check_layer(layer, fields, mapping):
"Checks the OGR layer by incrementing through and checking each feature."
# Incrementing through each feature in the layer.
for feat in layer:
check_feature(feat, fields, mapping)
def check_srs(layer, source_srs):
"Checks the compatibility of the given spatial reference object."
if isinstance(source_srs, SpatialReference):
sr = source_srs
elif isinstance(source_srs, SpatialRefSys):
sr = source_srs.srs
elif isinstance(source_srs, (int, str)):
sr = SpatialReference(source_srs)
else:
sr = layer.srs
if not sr:
raise Exception('No source reference system defined.')
else:
return sr
class LayerMapping:
"A class that maps OGR Layers to Django Models."
def __init__(self, model, data, mapping, layer=0, source_srs=None, encoding=None):
"Takes the Django model, the data source, and the mapping (dictionary)" "Takes the Django model, the data source, and the mapping (dictionary)"
# Getting the field names and types from the model # Getting the field names and types from the model
fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields) self.fields = dict((f.name, self.map_foreign_key(f)) for f in model._meta.fields)
self.field_classes = dict((f.name, f) for f in model._meta.fields)
# Getting the DataSource and its Layer # Getting the DataSource and its Layer
if isinstance(data, basestring): if isinstance(data, basestring):
self.ds = DataSource(data) self.ds = DataSource(data)
@ -254,15 +196,31 @@ class LayerMapping:
self.ds = data self.ds = data
self.layer = self.ds[layer] self.layer = self.ds[layer]
# Checking the layer -- intitialization of the object will fail if # Setting the mapping
# things don't check out before hand.
check_layer(self.layer, fields, mapping)
# Since the layer checked out, setting the fields and the mapping.
self.fields = fields
self.mapping = mapping self.mapping = mapping
# Setting the model, and getting the geometry column associated
# with the model (an exception will be raised if there is no
# geometry column).
self.model = model self.model = model
self.source_srs = check_srs(self.layer, source_srs) self.geo_col = self.geometry_column()
# Checking the source spatial reference system, and getting
# the coordinate transformation object (unless the `transform`
# keyword is set to False)
self.source_srs = self.check_srs(source_srs)
self.transform = transform and self.coord_transform()
# Checking the layer -- intitialization of the object will fail if
# things don't check out before hand. This may be time-consuming,
# and disabled by setting the `check` keyword to False.
if check: self.check_layer()
# The silent, strict, progress, and interval flags.
self.silent = silent
self.strict = strict
self.progress = progress
self.interval = interval
# Setting the encoding for OFTString fields, if specified. # Setting the encoding for OFTString fields, if specified.
if encoding: if encoding:
@ -274,74 +232,129 @@ class LayerMapping:
else: else:
self.encoding = None self.encoding = None
# Either the import will work, or it won't be committed. # Setting the transaction decorator with the function in the
@transaction.commit_on_success # transaction modes dictionary.
def save(self, verbose=False): if transaction_mode in self.TRANSACTION_MODES:
"Runs the layer mapping on the given SHP file, and saves to the database." self.transaction_decorator = self.TRANSACTION_MODES[transaction_mode]
self.transaction_mode = transaction_mode
else:
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
# Getting the GeometryColumn object. def check_feature(self, feat):
"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: try:
db_table = self.model._meta.db_table fi = feat.index(ogr_field)
if SPATIAL_BACKEND == 'oracle': db_table = db_table.upper()
gc_kwargs = {GeometryColumns.table_name_col() : db_table}
geo_col = GeometryColumns.objects.get(**gc_kwargs)
except: except:
raise Exception('Geometry column does not exist. (did you run syncdb?)') raise LayerMapError('Given mapping OGR field "%s" not in given OGR layer feature!' % ogr_field)
# Getting the coordinate system needed for transformation (with CoordTransform) def check_layer(self):
"Checks every feature in this object's layer."
for feat in self.layer:
self.check_feature(feat)
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, SpatialRefSys):
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 coord_transform(self):
"Returns the coordinate transformation object."
try: try:
# Getting the target spatial reference system # Getting the target spatial reference system
target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
# Creating the CoordTransform object # Creating the CoordTransform object
ct = CoordTransform(self.source_srs, target_srs) return CoordTransform(self.source_srs, target_srs)
except Exception, msg: except Exception, msg:
raise Exception('Could not translate between the data source and model geometry: %s' % msg) raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
for feat in self.layer: def feature_kwargs(self, feat):
# The keyword arguments for model construction "Returns the keyword arguments needed for saving a feature."
# The keyword arguments for model construction.
kwargs = {} kwargs = {}
# Incrementing through each model field and the OGR field in the mapping # The all_prepped flagged, will be set to False if there's a
# problem w/a ForeignKey that doesn't exist.
all_prepped = True all_prepped = True
# Incrementing through each model field and OGR field in the
# dictionary mapping.
for model_field, ogr_field in self.mapping.items(): for model_field, ogr_field in self.mapping.items():
is_fk = False is_fk = False
try: try:
model_type = self.fields[model_field] model_type = self.fields[model_field]
except KeyError: #foreign key except KeyError: #foreign key
# The -3 index is b/c foreign keys are appended w/'_id'.
model_type = self.fields[model_field[:-3]] model_type = self.fields[model_field[:-3]]
is_fk = True is_fk = True
if ogr_field in ogc_types: if ogr_field in self.OGC_TYPES:
## Getting the OGR geometry from the field # Verify OGR geometry.
geom = feat.geom val = self.verify_geom(feat.geom, model_type)
if make_multi(geom.geom_name, model_type):
# Constructing a multi-geometry type to contain the single geometry
multi_type = multi_types[geom.geom_name]
g = OGRGeometry(multi_type)
g.add(geom)
else: else:
g = geom # Otherwise, verify OGR Field type.
val = self.verify_field(feat[ogr_field], model_field)
# Transforming the geometry with our Coordinate Transformation object.
g.transform(ct)
# Updating the keyword args with the WKT of the transformed model.
val = g.wkt
else:
## Otherwise, this is an OGR field type
fld = feat[ogr_field]
if isinstance(fld, OFTString) and self.encoding:
# The encoding for OGR data sources may be specified here
# (e.g., 'cp437' for Census Bureau boundary files).
val = unicode(fld.value, self.encoding)
else:
val = fld.value
if is_fk: if is_fk:
# Handling if foreign key.
rel_obj = None rel_obj = None
field_name = model_field[:-3] field_name = model_field[:-3]
try: try:
@ -356,18 +369,133 @@ class LayerMapping:
else: else:
kwargs[model_field] = val kwargs[model_field] = val
return kwargs, all_prepped
def verify_field(self, fld, 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.
"""
field_class = self.field_classes[model_field]
if isinstance(fld, OFTString):
if self.encoding:
# The encoding for OGR data sources may be specified here
# (e.g., 'cp437' for Census Bureau boundary files).
val = unicode(fld.value, self.encoding)
else:
val = fld.value
if len(val) > field_class.max_length:
raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
(model_field, field_class.max_length, len(val)))
elif isinstance(fld, OFTReal):
try:
# Creating an instance of the Decimal value to use.
d = Decimal(str(fld.value))
except:
raise InvalidDecimal('Could not construct decimal from: %s' % fld)
dtup = d.as_tuple()
if len(dtup[1]) > field_class.max_digits:
raise InvalidDecimal('More than the maximum # of digits encountered.')
elif len(dtup[1][dtup[2]:]) > field_class.decimal_places:
raise InvalidDecimal('More than the maximum # of decimal places encountered.')
val = d
else:
val = fld.value
return val
def verify_geom(self, geom, model_type):
"Verifies the geometry."
if self.make_multi(geom.geom_name, model_type):
# Constructing a multi-geometry type to contain the single geometry
multi_type = self.MULTI_TYPES[geom.geom_name]
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
def geometry_column(self):
"Returns the GeometryColumn model associated with the geographic column."
# Getting the GeometryColumn object.
try:
db_table = self.model._meta.db_table
if SPATIAL_BACKEND == 'oracle': db_table = db_table.upper()
gc_kwargs = {GeometryColumns.table_name_col() : db_table}
return GeometryColumns.objects.get(**gc_kwargs)
except Exception, 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):
"Determines whether the geometry should be made into a GeometryCollection."
return (geom_name in self.MULTI_TYPES) and (model_type.startswith('Multi'))
def map_foreign_key(self, django_field):
"Handles fields within foreign keys for the given field."
if not django_field.__class__ is ForeignKey:
# 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):
"Runs the layer mapping on the given SHP file, and saves to the database."
@self.transaction_decorator
def _save():
num_feat = 0
num_saved = 0
for feat in self.layer:
num_feat += 1
# Getting the keyword arguments
try:
kwargs, all_prepped = self.feature_kwargs(feat)
except LayerMapError, msg:
# Something borked the validation
if self.strict: raise
elif not self.silent:
print 'Ignoring Feature ID %s because: %s' % (feat.fid, msg)
else:
# Constructing the model using the constructed keyword args # Constructing the model using the constructed keyword args
if all_prepped: if all_prepped:
m = self.model(**kwargs) m = self.model(**kwargs)
# Saving the model
try: try:
if all_prepped:
m.save() m.save()
if verbose: print 'Saved: %s' % str(m) num_saved += 1
else: if verbose: print 'Saved: %s' % m
print "Skipping %s due to missing relation." % kwargs
except SystemExit: except SystemExit:
raise raise
except Exception, e: except Exception, msg:
print "Failed to save %s\n Continuing" % kwargs if self.transaction_mode == 'autocommit':
# Rolling back the transaction so that other model saves
# will work.
transaction.rollback_unless_managed()
if self.strict:
# Bailing out if the `strict` keyword is set.
if not self.silent:
print 'Failed to save the feature (id: %s) into the model with the keyword arguments:' % feat.fid
print kwargs
raise
elif not self.silent:
print 'Failed to save %s:\n %s\nContinuing' % (kwargs, msg)
else:
print 'Skipping %s due to missing relation.' % kwargs
# Printing progress information, if requested.
if self.progress and num_feat % self.interval == 0:
print 'Processed %d features, saved %d ...' % (num_feat, num_saved)
# Calling our defined function, which will use the specified
# trasaction mode.
_save()

View File

@ -33,12 +33,12 @@ def ogrinfo(data_source, num_features=10):
width = max(*map(len,layer.fields)) width = max(*map(len,layer.fields))
fmt = " %%%ss: %%s" % width fmt = " %%%ss: %%s" % width
for i, feature in enumerate(layer[:num_features]): for j, feature in enumerate(layer[:num_features]):
print "=== Feature %s" % i print "=== Feature %s" % j
for field in layer.fields: for fld_name in layer.fields:
fld_typ = feature[field].__class__.__name__.replace('OFT', '') type_name = feature[fld_name].type_name
output = fmt % (field, fld_typ) output = fmt % (fld_name, type_name)
val = feature.get(field) val = feature.get(fld_name)
if val: if val:
if isinstance(val, str): if isinstance(val, str):
val_fmt = ' ("%s")' val_fmt = ' ("%s")'