From ac6273d6750be112dcb909d2201104be5885e806 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Fri, 25 Dec 2009 14:37:57 +0000 Subject: [PATCH] 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 --- django/contrib/gis/tests/geogapp/models.py | 7 +++ django/contrib/gis/tests/geogapp/tests.py | 59 +++++++++++++++++----- django/contrib/gis/tests/geogapp/views.py | 1 - django/contrib/gis/utils/layermapping.py | 33 +++--------- 4 files changed, 59 insertions(+), 41 deletions(-) delete mode 100644 django/contrib/gis/tests/geogapp/views.py diff --git a/django/contrib/gis/tests/geogapp/models.py b/django/contrib/gis/tests/geogapp/models.py index effba700916..3198fbd36d7 100644 --- a/django/contrib/gis/tests/geogapp/models.py +++ b/django/contrib/gis/tests/geogapp/models.py @@ -11,3 +11,10 @@ class Zipcode(models.Model): poly = models.PolygonField(geography=True) objects = models.GeoManager() 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]) diff --git a/django/contrib/gis/tests/geogapp/tests.py b/django/contrib/gis/tests/geogapp/tests.py index 5fe4c290aca..7be51931031 100644 --- a/django/contrib/gis/tests/geogapp/tests.py +++ b/django/contrib/gis/tests/geogapp/tests.py @@ -1,12 +1,11 @@ """ -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. +Tests for geography support in PostGIS 1.5+ """ +import os +from django.contrib.gis import gdal from django.contrib.gis.measure import D from django.test import TestCase -from models import City, Zipcode +from models import City, County, Zipcode class GeographyTest(TestCase): @@ -15,17 +14,22 @@ class GeographyTest(TestCase): self.assertEqual(8, City.objects.count()) 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') - cities = list(City.objects - .filter(point__distance_lte=(z.poly, D(mi=500))) - .order_by('name') - .values_list('name', flat=True)) - self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities) + cities1 = list(City.objects + .filter(point__distance_lte=(z.poly, D(mi=500))) + .order_by('name') + .values_list('name', flat=True)) + 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): - "Testing GeoQuerySet.distance() support on non-point geometry fields." - # Can't do this with geometry fields: + "Testing GeoQuerySet.distance() support on non-point geography fields." + # `GeoQuerySet.distance` is not allowed geometry fields. htown = City.objects.get(name='Houston') qs = Zipcode.objects.distance(htown.point) @@ -39,3 +43,32 @@ class GeographyTest(TestCase): self.assertRaises(ValueError, City.objects.filter(point__within=z.poly).count) # `@` operator not available. 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) diff --git a/django/contrib/gis/tests/geogapp/views.py b/django/contrib/gis/tests/geogapp/views.py deleted file mode 100644 index 60f00ef0ef3..00000000000 --- a/django/contrib/gis/tests/geogapp/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 9db743aea41..6b125d747e0 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -97,7 +97,7 @@ class LayerMapping(object): if self.spatial_backend.mysql: transform = False else: - self.geo_col = self.geometry_column() + self.geo_field = self.geometry_field() # Checking the source spatial reference system, and getting # the coordinate transformation object (unless the `transform` @@ -426,41 +426,20 @@ class LayerMapping(object): SpatialRefSys = self.spatial_backend.spatial_ref_sys() try: # 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 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): - "Returns the GeometryColumn model associated with the geographic column." + def geometry_field(self): + "Returns the GeometryField instance associated with the geographic column." # Use the `get_field_by_name` on the model's options so that we - # get the correct model if there's model inheritance -- otherwise - # the returned model is None. + # get the correct field instance if there's model inheritance. opts = self.model._meta fld, model, direct, m2m = opts.get_field_by_name(self.geom_field) - if model is None: model = self.model - - # 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) + return fld def make_multi(self, geom_type, model_field): """