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

gis: fixed support for 'isnull' lookup type on geometry columns, and added corresponding tests; added tests for 'left' and 'right' lookup types; added interactive keyword to create_spatial_db().

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5773 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-07-28 18:59:44 +00:00
parent 357ef5d975
commit 68f9a8ee8e
8 changed files with 91 additions and 15 deletions

View File

@ -114,8 +114,9 @@ class GeometryField(Field):
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
"Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value." "Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
if not bool(value): return None
if lookup_type in POSTGIS_TERMS: if lookup_type in POSTGIS_TERMS:
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
if not bool(value): return [None] # If invalid value passed in.
if isinstance(value, GEOSGeometry): if isinstance(value, GEOSGeometry):
# GEOSGeometry instance passed in. # GEOSGeometry instance passed in.
if value.srid != self._srid: if value.srid != self._srid:

View File

@ -50,6 +50,9 @@ POSTGIS_GEOMETRY_FUNCTIONS = {
'relate' : 'Relate', 'relate' : 'Relate',
} }
# Any other lookup types that do not require a mapping.
MISC_TERMS = ['isnull']
# The quotation used for postgis (uses single quotes). # The quotation used for postgis (uses single quotes).
def quotename(value, dbl=False): def quotename(value, dbl=False):
if dbl: return '"%s"' % value if dbl: return '"%s"' % value
@ -58,7 +61,8 @@ def quotename(value, dbl=False):
# These are the PostGIS-customized QUERY_TERMS, combines both the operators # These are the PostGIS-customized QUERY_TERMS, combines both the operators
# and the geometry functions. # and the geometry functions.
POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
POSTGIS_TERMS.extend(list(POSTGIS_GEOMETRY_FUNCTIONS.keys())) # Adding on the Geometry Functions POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
def get_geo_where_clause(lookup_type, table_prefix, field_name, value): def get_geo_where_clause(lookup_type, table_prefix, field_name, value):

View File

@ -9,3 +9,8 @@ class City(models.Model, models.GeoMixin):
name = models.CharField(maxlength=30) name = models.CharField(maxlength=30)
point = models.PointField() point = models.PointField()
objects = models.GeoManager() objects = models.GeoManager()
class State(models.Model, models.GeoMixin):
name = models.CharField(maxlength=30)
poly = models.PolygonField(null=True) # Allowing NULL geometries here.
objects = models.GeoManager()

View File

@ -2,4 +2,7 @@ INSERT INTO geoapp_city ("name", "point") VALUES ('Houston', 'SRID=4326;POINT (-
INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', 'SRID=4326;POINT (-96.801611 32.782057)'); INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', 'SRID=4326;POINT (-96.801611 32.782057)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', 'SRID=4326;POINT (-97.521157 34.464642)'); INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', 'SRID=4326;POINT (-97.521157 34.464642)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', 'SRID=4326;POINT (174.783117 -41.315268)'); INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', 'SRID=4326;POINT (174.783117 -41.315268)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', 'SRID=4326;POINT (-104.609252 38.255001)'); INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', 'SRID=4326;POINT (-104.609252 38.255001)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Lawrence', 'SRID=4326;POINT (-95.235060 38.971823)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Chicago', 'SRID=4326;POINT (-87.650175 41.850385)');
INSERT INTO geoapp_city ("name", "point") VALUES ('Victoria', 'SRID=4326;POINT (-123.305196 48.462611)');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
import unittest import unittest
from models import Country, City from models import Country, City, State
from django.contrib.gis.geos import fromstr, Point from django.contrib.gis.geos import fromstr
class GeoModelTest(unittest.TestCase): class GeoModelTest(unittest.TestCase):
@ -9,8 +9,9 @@ class GeoModelTest(unittest.TestCase):
# Ensuring that data was loaded from initial SQL. # Ensuring that data was loaded from initial SQL.
self.assertEqual(2, Country.objects.count()) self.assertEqual(2, Country.objects.count())
self.assertEqual(5, City.objects.count()) self.assertEqual(8, City.objects.count())
self.assertEqual(3, State.objects.count())
def test002_contains_contained(self): def test002_contains_contained(self):
"Testing the 'contained' and 'contains' lookup types." "Testing the 'contained' and 'contains' lookup types."
@ -22,23 +23,24 @@ class GeoModelTest(unittest.TestCase):
# _bounding box_ of the Geometries. # _bounding box_ of the Geometries.
qs = City.objects.filter(point__contained=texas.mpoly) qs = City.objects.filter(point__contained=texas.mpoly)
self.assertEqual(3, qs.count()) self.assertEqual(3, qs.count())
city_names = [c.name for c in qs] cities = ['Houston', 'Dallas', 'Oklahoma City']
self.assertEqual(True, 'Houston' in city_names) for c in qs: self.assertEqual(True, c.name in cities)
self.assertEqual(True, 'Dallas' in city_names)
self.assertEqual(True, 'Oklahoma City' in city_names)
# Pulling out some cities. # Pulling out some cities.
houston = City.objects.get(name='Houston') houston = City.objects.get(name='Houston')
wellington = City.objects.get(name='Wellington') wellington = City.objects.get(name='Wellington')
pueblo = City.objects.get(name='Pueblo') pueblo = City.objects.get(name='Pueblo')
okcity = City.objects.get(name='Oklahoma City') okcity = City.objects.get(name='Oklahoma City')
lawrence = City.objects.get(name='Lawrence')
# Now testing contains on the countries using the points for # Now testing contains on the countries using the points for
# Houston and Wellington. # Houston and Wellington.
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
ks = State.objects.get(poly__contains=lawrence.point)
self.assertEqual('Texas', tx.name) self.assertEqual('Texas', tx.name)
self.assertEqual('New Zealand', nz.name) self.assertEqual('New Zealand', nz.name)
self.assertEqual('Kansas', ks.name)
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas) # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
# are not contained in Texas or New Zealand. # are not contained in Texas or New Zealand.
@ -48,7 +50,7 @@ class GeoModelTest(unittest.TestCase):
def test003_lookup_insert_transform(self): def test003_lookup_insert_transform(self):
"Testing automatic transform for lookups and inserts." "Testing automatic transform for lookups and inserts."
# San Antonio in WGS84 and NAD83(HARN) / Texas Centric Lambert Conformal # San Antonio in 'WGS84' (SRID 4326) and 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084)
sa_4326 = 'POINT (-98.493183 29.424170)' sa_4326 = 'POINT (-98.493183 29.424170)'
sa_3084 = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform sa_3084 = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform
@ -67,6 +69,64 @@ class GeoModelTest(unittest.TestCase):
self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6) self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6)
self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6) self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
def test004_null_geometries(self):
"Testing NULL geometry support."
# Querying for both NULL and Non-NULL values.
nullqs = State.objects.filter(poly__isnull=True)
validqs = State.objects.filter(poly__isnull=False)
# Puerto Rico should be NULL (it's a commonwealth unincorporated territory)
self.assertEqual(1, len(nullqs))
self.assertEqual('Puerto Rico', nullqs[0].name)
# The valid states should be Colorado & Kansas
self.assertEqual(2, len(validqs))
state_names = [s.name for s in validqs]
self.assertEqual(True, 'Colorado' in state_names)
self.assertEqual(True, 'Kansas' in state_names)
# Saving another commonwealth w/a NULL geometry.
nmi = State(name='Northern Mariana Islands', poly=None)
nmi.save()
def test005_left_right(self):
"Testing the left ('<<') right ('>>') operators."
# Left: A << B => true if xmax(A) < xmin(B)
# Right: A >> B => true if xmin(A) > xmax(B)
# See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
# Getting the borders for Colorado & Kansas
co_border = State.objects.get(name='Colorado').poly
ks_border = State.objects.get(name='Kansas').poly
# Note: Wellington has an 'X' value of 174, so it will not be considered
# to the left of CO.
# These cities should be strictly to the right of the CO border.
cities = ['Houston', 'Dallas', 'San Antonio', 'Oklahoma City',
'Lawrence', 'Chicago', 'Wellington']
qs = City.objects.filter(point__right=co_border)
self.assertEqual(7, len(qs))
for c in qs: self.assertEqual(True, c.name in cities)
# These cities should be strictly to the right of the KS border.
cities = ['Chicago', 'Wellington']
qs = City.objects.filter(point__right=ks_border)
self.assertEqual(2, len(qs))
for c in qs: self.assertEqual(True, c.name in cities)
# Note: Wellington has an 'X' value of 174, so it will not be considered
# to the left of CO.
vic = City.objects.get(point__left=co_border)
self.assertEqual('Victoria', vic.name)
cities = ['Pueblo', 'Victoria']
qs = City.objects.filter(point__left=ks_border)
self.assertEqual(2, len(qs))
for c in qs: self.assertEqual(True, c.name in cities)
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GeoModelTest)) s.addTest(unittest.makeSuite(GeoModelTest))

View File

@ -80,7 +80,7 @@ def _create_with_shell(db_name, verbosity=1, autoclobber=False):
else: else:
raise Exception, 'Unknown error occurred in creating database: %s' % output raise Exception, 'Unknown error occurred in creating database: %s' % output
def create_spatial_db(test=False, verbosity=1, autoclobber=False): def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=False):
"This Python routine creates a spatial database based on settings.py." "This Python routine creates a spatial database based on settings.py."
# Making sure we're using PostgreSQL and psycopg2 # Making sure we're using PostgreSQL and psycopg2
@ -113,7 +113,7 @@ def create_spatial_db(test=False, verbosity=1, autoclobber=False):
settings.DATABASE_NAME = db_name settings.DATABASE_NAME = db_name
# Syncing the database # Syncing the database
syncdb(verbosity, interactive=False) syncdb(verbosity, interactive=interactive)
# Get a cursor (even though we don't need one yet). This has # Get a cursor (even though we don't need one yet). This has
# the side effect of initializing the test database. # the side effect of initializing the test database.