Added tests for PostGIS geography support; added `proj_version_tuple` to PostGIS spatial backend operations; made `distapp` tests take into account different datums in PROJ.4 4.7; and added svn:ignore properties for recently-added directories.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11971 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2009-12-23 20:45:08 +00:00
parent 18d754d681
commit e1f6b4b82f
8 changed files with 213 additions and 16 deletions

View File

@ -450,6 +450,19 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
return (version, major, minor1, minor2) return (version, major, minor1, minor2)
def proj_version_tuple(self):
"""
Return the version of PROJ.4 used by PostGIS as a tuple of the
major, minor, and subminor release numbers.
"""
proj_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)')
proj_ver_str = self.postgis_proj_version()
m = proj_regex.search(proj_ver_str)
if m:
return tuple(map(int, [m.group(1), m.group(2), m.group(3)]))
else:
raise Exception('Could not determine PROJ.4 version from PostGIS.')
def num_params(self, lookup_type, num_param): def num_params(self, lookup_type, num_param):
""" """
Helper routine that returns a boolean indicating whether the number of Helper routine that returns a boolean indicating whether the number of

View File

@ -1,6 +1,8 @@
import sys, unittest import sys
from django.test.simple import run_tests
from django.utils.importlib import import_module def run_tests(*args, **kwargs):
from django.test.simple import run_tests as base_run_tests
return base_run_tests(*args, **kwargs)
def geo_suite(): def geo_suite():
""" """
@ -14,6 +16,8 @@ def geo_suite():
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.utils import HAS_GEOIP from django.contrib.gis.utils import HAS_GEOIP
from django.contrib.gis.tests.utils import postgis, mysql from django.contrib.gis.tests.utils import postgis, mysql
from django.db import connection
from django.utils.importlib import import_module
gis_tests = [] gis_tests = []
@ -23,13 +27,17 @@ def geo_suite():
# Tests that require use of a spatial database (e.g., creation of models) # Tests that require use of a spatial database (e.g., creation of models)
test_apps = ['geoapp', 'relatedapp'] test_apps = ['geoapp', 'relatedapp']
if postgis and connection.ops.geography:
# Test geography support with PostGIS 1.5+.
test_apps.append('geogapp')
# 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 = [
'test_measure', 'test_measure',
] ]
# Tests applications that require a test spatial db. if HAS_GDAL:
# These tests require GDAL.
if not mysql: if not mysql:
test_apps.append('distapp') test_apps.append('distapp')
@ -37,8 +45,6 @@ def geo_suite():
if postgis and GEOS_PREPARE: if postgis and GEOS_PREPARE:
test_apps.append('geo3d') test_apps.append('geo3d')
if HAS_GDAL:
# These tests require GDAL.
test_suite_names.extend(['test_spatialrefsys', 'test_geoforms']) test_suite_names.extend(['test_spatialrefsys', 'test_geoforms'])
test_apps.append('layermap') test_apps.append('layermap')

View File

@ -1,6 +1,7 @@
import os, unittest import os, unittest
from decimal import Decimal from decimal import Decimal
from django.db import connection
from django.db.models import Q from django.db.models import Q
from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal import DataSource
from django.contrib.gis.geos import GEOSGeometry, Point, LineString from django.contrib.gis.geos import GEOSGeometry, Point, LineString
@ -157,9 +158,28 @@ class DistanceTest(unittest.TestCase):
# Got the reference distances using the raw SQL statements: # Got the reference distances using the raw SQL statements:
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11)); # SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
spheroid_distances = [60504.0628825298, 77023.948962654, 49154.8867507115, 90847.435881812, 217402.811862568, 709599.234619957, 640011.483583758, 7772.00667666425, 1047861.7859506, 1165126.55237647]
# SELECT ST_distance_sphere(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326)) FROM distapp_australiacity WHERE (NOT (id = 11)); st_distance_sphere # SELECT ST_distance_sphere(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326)) FROM distapp_australiacity WHERE (NOT (id = 11)); st_distance_sphere
sphere_distances = [60580.7612632291, 77143.7785056615, 49199.2725132184, 90804.4414289463, 217712.63666124, 709131.691061906, 639825.959074112, 7786.80274606706, 1049200.46122281, 1162619.7297006] if connection.ops.postgis and connection.ops.proj_version_tuple() >= (4, 7, 0):
# PROJ.4 versions 4.7+ have updated datums, and thus different
# distance values.
spheroid_distances = [60504.0628957201, 77023.9489850262, 49154.8867574404,
90847.4358768573, 217402.811919332, 709599.234564757,
640011.483550888, 7772.00667991925, 1047861.78619339,
1165126.55236034]
sphere_distances = [60580.9693849267, 77144.0435286473, 49199.4415344719,
90804.7533823494, 217713.384600405, 709134.127242793,
639828.157159169, 7786.82949717788, 1049204.06569028,
1162623.7238134]
else:
spheroid_distances = [60504.0628825298, 77023.948962654, 49154.8867507115,
90847.435881812, 217402.811862568, 709599.234619957,
640011.483583758, 7772.00667666425, 1047861.7859506,
1165126.55237647]
sphere_distances = [60580.7612632291, 77143.7785056615, 49199.2725132184,
90804.4414289463, 217712.63666124, 709131.691061906,
639825.959074112, 7786.80274606706, 1049200.46122281,
1162619.7297006]
# Testing with spheroid distances first. # Testing with spheroid distances first.
qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point, spheroid=True) qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point, spheroid=True)
@ -230,15 +250,20 @@ class DistanceTest(unittest.TestCase):
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300))) qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs)) self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
@no_spatialite
def test05_geodetic_distance_lookups(self): def test05_geodetic_distance_lookups(self):
"Testing distance lookups on geodetic coordinate systems." "Testing distance lookups on geodetic coordinate systems."
if not oracle: if not oracle:
# Oracle doesn't have this limitation -- PostGIS only allows geodetic # Oracle doesn't have this limitation -- PostGIS only allows geodetic
# distance queries from Points to PointFields. # distance queries from Points to PointFields on geometry columns (geography
# columns don't have that limitation).
mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)') mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
self.assertRaises(ValueError, len, self.assertRaises(ValueError, len,
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100)))) AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
# Ensured that a ValueError was raised, none of the rest of the test is
# support on this backend, so bail now.
if spatialite: return
# Too many params (4 in this case) should raise a ValueError. # Too many params (4 in this case) should raise a ValueError.
self.assertRaises(ValueError, len, self.assertRaises(ValueError, len,
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))) AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))

View File

@ -0,0 +1,98 @@
[
{
"pk": 1,
"model": "geogapp.city",
"fields": {
"name": "Houston",
"point": "POINT (-95.363151 29.763374)"
}
},
{
"pk": 2,
"model": "geogapp.city",
"fields": {
"name": "Dallas",
"point": "POINT (-96.801611 32.782057)"
}
},
{
"pk": 3,
"model": "geogapp.city",
"fields": {
"name": "Oklahoma City",
"point": "POINT (-97.521157 34.464642)"
}
},
{
"pk": 4,
"model": "geogapp.city",
"fields": {
"name": "Wellington",
"point": "POINT (174.783117 -41.315268)"
}
},
{
"pk": 5,
"model": "geogapp.city",
"fields": {
"name": "Pueblo",
"point": "POINT (-104.609252 38.255001)"
}
},
{
"pk": 6,
"model": "geogapp.city",
"fields": {
"name": "Lawrence",
"point": "POINT (-95.235060 38.971823)"
}
},
{
"pk": 7,
"model": "geogapp.city",
"fields": {
"name": "Chicago",
"point": "POINT (-87.650175 41.850385)"
}
},
{
"pk": 8,
"model": "geogapp.city",
"fields": {
"name": "Victoria",
"point": "POINT (-123.305196 48.462611)"
}
},
{
"pk": 1,
"model": "geogapp.zipcode",
"fields" : {
"code" : "77002",
"poly" : "SRID=4269;POLYGON ((-95.365015 29.772327, -95.362415 29.772327, -95.360915 29.771827, -95.354615 29.771827, -95.351515 29.772527, -95.350915 29.765327, -95.351015 29.762436, -95.350115 29.760328, -95.347515 29.758528, -95.352315 29.753928, -95.356415 29.756328, -95.358215 29.754028, -95.360215 29.756328, -95.363415 29.757128, -95.364014 29.75638, -95.363415 29.753928, -95.360015 29.751828, -95.361815 29.749528, -95.362715 29.750028, -95.367516 29.744128, -95.369316 29.745128, -95.373916 29.744128, -95.380116 29.738028, -95.387916 29.727929, -95.388516 29.729629, -95.387916 29.732129, -95.382916 29.737428, -95.376616 29.742228, -95.372616 29.747228, -95.378601 29.750846, -95.378616 29.752028, -95.378616 29.754428, -95.376016 29.754528, -95.374616 29.759828, -95.373616 29.761128, -95.371916 29.763928, -95.372316 29.768727, -95.365884 29.76791, -95.366015 29.767127, -95.358715 29.765327, -95.358615 29.766327, -95.359115 29.767227, -95.360215 29.767027, -95.362783 29.768267, -95.365315 29.770527, -95.365015 29.772327))"
}
},
{
"pk": 2,
"model": "geogapp.zipcode",
"fields" : {
"code" : "77005",
"poly" : "SRID=4269;POLYGON ((-95.447918 29.727275, -95.428017 29.728729, -95.421117 29.729029, -95.418617 29.727629, -95.418517 29.726429, -95.402117 29.726629, -95.402117 29.725729, -95.395316 29.725729, -95.391916 29.726229, -95.389716 29.725829, -95.396517 29.715429, -95.397517 29.715929, -95.400917 29.711429, -95.411417 29.715029, -95.418417 29.714729, -95.418317 29.70623, -95.440818 29.70593, -95.445018 29.70683, -95.446618 29.70763, -95.447418 29.71003, -95.447918 29.727275))"
}
},
{
"pk": 3,
"model": "geogapp.zipcode",
"fields" : {
"code" : "77025",
"poly" : "SRID=4269;POLYGON ((-95.418317 29.70623, -95.414717 29.706129, -95.414617 29.70533, -95.418217 29.70533, -95.419817 29.69533, -95.419484 29.694196, -95.417166 29.690901, -95.414517 29.69433, -95.413317 29.69263, -95.412617 29.68973, -95.412817 29.68753, -95.414087 29.685055, -95.419165 29.685428, -95.421617 29.68513, -95.425717 29.67983, -95.425017 29.67923, -95.424517 29.67763, -95.427418 29.67763, -95.438018 29.664631, -95.436713 29.664411, -95.440118 29.662231, -95.439218 29.661031, -95.437718 29.660131, -95.435718 29.659731, -95.431818 29.660331, -95.441418 29.656631, -95.441318 29.656331, -95.441818 29.656131, -95.441718 29.659031, -95.441118 29.661031, -95.446718 29.656431, -95.446518 29.673431, -95.446918 29.69013, -95.447418 29.71003, -95.446618 29.70763, -95.445018 29.70683, -95.440818 29.70593, -95.418317 29.70623))"
}
},
{
"pk": 4,
"model": "geogapp.zipcode",
"fields" : {
"code" : "77401",
"poly" : "SRID=4269;POLYGON ((-95.447918 29.727275, -95.447418 29.71003, -95.446918 29.69013, -95.454318 29.68893, -95.475819 29.68903, -95.475819 29.69113, -95.484419 29.69103, -95.484519 29.69903, -95.480419 29.70133, -95.480419 29.69833, -95.474119 29.69833, -95.474119 29.70453, -95.472719 29.71283, -95.468019 29.71293, -95.468219 29.720229, -95.464018 29.720229, -95.464118 29.724529, -95.463018 29.725929, -95.459818 29.726129, -95.459918 29.720329, -95.451418 29.720429, -95.451775 29.726303, -95.451318 29.727029, -95.447918 29.727275))"
}
}
]

View File

@ -0,0 +1,13 @@
from django.contrib.gis.db import models
class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(geography=True)
objects = models.GeoManager()
def __unicode__(self): return self.name
class Zipcode(models.Model):
code = models.CharField(max_length=10)
poly = models.PolygonField(geography=True)
objects = models.GeoManager()
def __unicode__(self): return self.name

View File

@ -0,0 +1,41 @@
"""
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.
"""
from django.contrib.gis.measure import D
from django.test import TestCase
from models import City, Zipcode
class GeographyTest(TestCase):
def test01_fixture_load(self):
"Ensure geography features loaded properly."
self.assertEqual(8, City.objects.count())
def test02_distance_lookup(self):
"Testing GeoQuerySet distance lookup support on non-point geometry 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)
def test03_distance_method(self):
"Testing GeoQuerySet.distance() support on non-point geometry fields."
# Can't do this with geometry fields:
htown = City.objects.get(name='Houston')
qs = Zipcode.objects.distance(htown.point)
def test04_invalid_operators_functions(self):
"Ensuring exceptions are raised for operators & functions invalid on geography fields."
# Only a subset of the geometry functions & operator are available
# to PostGIS geography types. For more information, visit:
# http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_GeographyFunctions
z = Zipcode.objects.get(code='77002')
# ST_Within not available.
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)

View File

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