1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

gis: added the utils package with the introduction of the LayerMapping class.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5529 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-06-25 03:20:26 +00:00
parent 8648cf78cd
commit b0a56a9919
2 changed files with 267 additions and 0 deletions

View File

@ -0,0 +1,266 @@
# 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.
This grew out of my personal needs, specifically the code repetition
that went into pulling geometries and fields out of an OGR layer,
converting to another coordinate system (e.g. WGS84), and then inserting
into a Geographic Django model.
This utility is still in early stages of development, so its usage
is subject to change -- please report any bugs.
TODO: Unit tests and documentation.
Requirements: OGR C Library (from GDAL) required.
Usage:
lm = LayerMapping(model, source_file, mapping) where,
model -- GeoDjango model (not an instance)
source_file -- OGR-supported data source file (e.g. a shapefile)
mapping -- A python dictionary, keys are strings corresponding
to the GeoDjango model field, and values correspond to
string field names for the OGR feature, or if the model field
is a geographic then it should correspond to the OGR
geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
Example:
1. You need a GDAL-supported data source, like a shapefile.
Assume we're using the test_poly SHP file:
>>> from django.contrib.gis.gdal import DataSource
>>> ds = DataSource('test_poly.shp')
>>> layer = ds[0]
>>> print layer.fields # Exploring the fields in the layer, we only want the 'str' field.
['float', 'int', 'str']
>>> print len(layer) # getting the number of features in the layer (should be 3)
3
>>> print layer.geom_type # Should be 3 (a Polygon)
3
>>> print layer.srs # WGS84
GEOGCS["GCS_WGS_1984",
DATUM["WGS_1984",
SPHEROID["WGS_1984",6378137,298.257223563]],
PRIMEM["Greenwich",0],
UNIT["Degree",0.017453292519943295]]
2. Now we define our corresponding Django model (make sure to use syncdb):
from django.contrib.gis.db import models
class TestGeo(models.Model, models.GeoMixin):
name = models.CharField(maxlength=25) # corresponds to the 'str' field
poly = models.PolygonField(srid=4269) # we want our model in a different SRID
objects = models.GeoManager()
def __str__(self):
return 'Name: %s' % self.name
3. Use LayerMapping to extract all the features and place them in the database:
>>> from django.contrib.gis.utils import LayerMapping
>>> from geoapp.models import TestGeo
>>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field.
'poly' : 'POLYGON', # For geometry fields use OGC name.
} # The mapping is a dictionary
>>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
>>> lm.save(verbose=True) # Save the layermap, imports the data.
>>> lm.save(verbose=True)
Saved: Name: 1
Saved: Name: 2
Saved: Name: 3
LayerMapping just transformed the three geometries from the SHP file from their
source spatial reference system (WGS84) to the spatial reference system of
the GeoDjango model (NAD83). Further, data is selectively imported from
the given
"""
from types import StringType, TupleType
from datetime import datetime
from django.contrib.gis.gdal import \
OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
DataSource, Layer, Feature, OGRException
from django.contrib.gis.gdal.Field import Field, OFTInteger, OFTReal, OFTString, OFTDateTime
from django.contrib.gis.models import GeometryColumns, SpatialRefSys
# 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',
}
# Acceptable 'base' types for a multi-geometry type.
multi_types = {'POINT' : OGRGeomType('MultiPoint'),
'LINESTRING' : OGRGeomType('MultiLineString'),
'POLYGON' : OGRGeomType('MultiPolygon'),
}
# The acceptable Django field types that map to OGR fields.
field_types = {'IntegerField' : OFTInteger,
'FloatField' : OFTReal,
'DateTimeField' : OFTDateTime,
'DecimalField' : OFTReal,
'CharField' : OFTString,
'SmallIntegerField' : OFTInteger,
'PositiveSmallIntegerField' : OFTInteger,
}
def make_multi(geom_name, model_type):
"Determines whether the geometry should be made into a GeometryCollection."
if (geom_name in multi_types) and (model_type.startswith('Multi')):
return True
else:
return False
def check_feature(feat, model_fields, mapping):
"Checks the OGR layer feature."
HAS_GEO = False
# 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 not model_field in model_fields:
raise Exception, 'Given mapping field "%s" not in given Model fields!' % model_field
else:
model_type = 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!'
## 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)
class LayerMapping:
"A class that maps OGR Layers to Django Models."
def __init__(self, model, ogr_file, mapping, layer=0):
"Takes the Django model, the mapping (dictionary), and the SHP file."
# Getting the field names and types from the model
fields = dict((f.name, f.__class__.__name__) for f in model._meta.fields)
# Getting the DataSource and its Layer
self.ds = DataSource(ogr_file)
self.layer = self.ds[layer]
# Checking the layer -- intitialization of the object will fail if
# 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.model = model
def save(self, verbose=False):
"Runs the layer mapping on the given SHP file, and saves to the database."
# Getting the GeometryColumn object.
try:
geo_col = GeometryColumns.objects.get(f_table_name=self.model._meta.db_table)
except:
raise Exception, 'Geometry column "%s" does not exist. (did you run syncdb?)'
# Getting the coordinate system needed for transformation (with CoordTransform)
try:
source_srs = self.layer.srs
target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs
ct = CoordTransform(source_srs, target_srs)
except:
raise Exception, 'Could not translate between the data source and model geometry.'
for feat in self.layer:
# The keyword arguments for model construction
kwargs = {}
# Incrementing through each model field and the OGR field in the mapping
for model_field, ogr_field in self.mapping.items():
model_type = self.fields[model_field]
if ogr_field in ogc_types:
## Getting the OGR geometry from the field
geom = feat.geom
if make_multi(geom.geom_name, model_type):
# Constructing a multi-geometry type to contain the single geometry
multi_type = multi_types[gname]
g = OGRGeometry(multi_type)
g.add(geom)
else:
g = geom
# Transforming the geometry with our Coordinate Transformation object.
g.transform(ct)
# Updating the keyword args with the WKT of the transformed model.
kwargs[model_field] = g.wkt
else:
## Otherwise, this is an OGR field type
fi = feat.index(ogr_field)
val = feat[fi].value
kwargs[model_field] = val
# Constructing the model using the constructed keyword args
m = self.model(**kwargs)
# Saving the model
m.save()
if verbose: print 'Saved: %s' % str(m)

View File

@ -0,0 +1 @@
from LayerMapping import LayerMapping