Fixed `LayerMapping` to work with PostGIS geography fields; removed `LayerMapping.geometry_column` and replaced with `LayerMapping.geometry_field` because getting the `geometry_columns` entry was completely unnecessary, and only the geometry field instance is needed; cleaned up and fleshed out the `geogapp` tests.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11983 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2009-12-25 14:37:57 +00:00
parent 7626f851e3
commit ac6273d675
4 changed files with 59 additions and 41 deletions

View File

@ -11,3 +11,10 @@ class Zipcode(models.Model):
poly = models.PolygonField(geography=True) poly = models.PolygonField(geography=True)
objects = models.GeoManager() objects = models.GeoManager()
def __unicode__(self): return self.name def __unicode__(self): return self.name
class County(models.Model):
name = models.CharField(max_length=25)
state = models.CharField(max_length=20)
mpoly = models.MultiPolygonField(geography=True)
objects = models.GeoManager()
def __unicode__(self): return ' County, '.join([self.name, self.state])

View File

@ -1,12 +1,11 @@
""" """
This file demonstrates two different styles of tests (one doctest and one Tests for geography support in PostGIS 1.5+
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
""" """
import os
from django.contrib.gis import gdal
from django.contrib.gis.measure import D from django.contrib.gis.measure import D
from django.test import TestCase from django.test import TestCase
from models import City, Zipcode from models import City, County, Zipcode
class GeographyTest(TestCase): class GeographyTest(TestCase):
@ -15,17 +14,22 @@ class GeographyTest(TestCase):
self.assertEqual(8, City.objects.count()) self.assertEqual(8, City.objects.count())
def test02_distance_lookup(self): def test02_distance_lookup(self):
"Testing GeoQuerySet distance lookup support on non-point geometry fields." "Testing GeoQuerySet distance lookup support on non-point geography fields."
z = Zipcode.objects.get(code='77002') z = Zipcode.objects.get(code='77002')
cities = list(City.objects cities1 = list(City.objects
.filter(point__distance_lte=(z.poly, D(mi=500))) .filter(point__distance_lte=(z.poly, D(mi=500)))
.order_by('name') .order_by('name')
.values_list('name', flat=True)) .values_list('name', flat=True))
self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities) cities2 = list(City.objects
.filter(point__dwithin=(z.poly, D(mi=500)))
.order_by('name')
.values_list('name', flat=True))
for cities in [cities1, cities2]:
self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
def test03_distance_method(self): def test03_distance_method(self):
"Testing GeoQuerySet.distance() support on non-point geometry fields." "Testing GeoQuerySet.distance() support on non-point geography fields."
# Can't do this with geometry fields: # `GeoQuerySet.distance` is not allowed geometry fields.
htown = City.objects.get(name='Houston') htown = City.objects.get(name='Houston')
qs = Zipcode.objects.distance(htown.point) qs = Zipcode.objects.distance(htown.point)
@ -39,3 +43,32 @@ class GeographyTest(TestCase):
self.assertRaises(ValueError, City.objects.filter(point__within=z.poly).count) self.assertRaises(ValueError, City.objects.filter(point__within=z.poly).count)
# `@` operator not available. # `@` operator not available.
self.assertRaises(ValueError, City.objects.filter(point__contained=z.poly).count) self.assertRaises(ValueError, City.objects.filter(point__contained=z.poly).count)
def test05_geography_layermapping(self):
"Testing LayerMapping support on models with geography fields."
# There is a similar test in `layermap` that uses the same data set,
# but the County model here is a bit different.
if not gdal.HAS_GDAL: return
from django.contrib.gis.utils import LayerMapping
# Getting the shapefile and mapping dictionary.
shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
co_mapping = {'name' : 'Name',
'state' : 'State',
'mpoly' : 'MULTIPOLYGON',
}
# Reference county names, number of polygons, and state names.
names = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
num_polys = [1, 2, 1, 19, 1] # Number of polygons for each.
st_names = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269, unique='name')
lm.save(silent=True, strict=True)
for c, name, num_poly, state in zip(County.objects.order_by('name'), names, num_polys, st_names):
self.assertEqual(4326, c.mpoly.srid)
self.assertEqual(num_poly, len(c.mpoly))
self.assertEqual(name, c.name)
self.assertEqual(state, c.state)

View File

@ -1 +0,0 @@
# Create your views here.

View File

@ -97,7 +97,7 @@ class LayerMapping(object):
if self.spatial_backend.mysql: if self.spatial_backend.mysql:
transform = False transform = False
else: else:
self.geo_col = self.geometry_column() self.geo_field = self.geometry_field()
# Checking the source spatial reference system, and getting # Checking the source spatial reference system, and getting
# the coordinate transformation object (unless the `transform` # the coordinate transformation object (unless the `transform`
@ -426,41 +426,20 @@ class LayerMapping(object):
SpatialRefSys = self.spatial_backend.spatial_ref_sys() SpatialRefSys = self.spatial_backend.spatial_ref_sys()
try: try:
# Getting the target spatial reference system # Getting the target spatial reference system
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs target_srs = SpatialRefSys.objects.get(srid=self.geo_field.srid).srs
# Creating the CoordTransform object # Creating the CoordTransform object
return CoordTransform(self.source_srs, target_srs) return CoordTransform(self.source_srs, target_srs)
except Exception, msg: except Exception, msg:
raise LayerMapError('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)
def geometry_column(self): def geometry_field(self):
"Returns the GeometryColumn model associated with the geographic column." "Returns the GeometryField instance associated with the geographic column."
# Use the `get_field_by_name` on the model's options so that we # Use the `get_field_by_name` on the model's options so that we
# get the correct model if there's model inheritance -- otherwise # get the correct field instance if there's model inheritance.
# the returned model is None.
opts = self.model._meta opts = self.model._meta
fld, model, direct, m2m = opts.get_field_by_name(self.geom_field) fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
if model is None: model = self.model return fld
# Trying to get the `GeometryColumns` object that corresponds to the
# the geometry field.
try:
db_table = model._meta.db_table
geo_col = fld.column
if self.spatial_backend.oracle:
# Making upper case for Oracle.
db_table = db_table.upper()
geo_col = geo_col.upper()
GeometryColumns = self.spatial_backend.geometry_columns()
gc_kwargs = { GeometryColumns.table_name_col() : db_table,
GeometryColumns.geom_col_name() : geo_col,
}
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_type, model_field): def make_multi(self, geom_type, model_field):
""" """