Django

Code

Show
Ignore:
Timestamp:
06/15/08 14:48:57 (5 months ago)
Author:
jbronn
Message:

gis: Refactor of the GeoQuerySet; new features include:

(1) Creation of internal API that eases generation of GeoQuerySet methods.
(2) GeoQuerySet.distance now returns Distance objects instead of floats.
(3) Added the new GeoQuerySet methods: area, centroid, difference, envelope, intersection, length, make_line, mem_size, num_geom, num_points, perimeter, point_on_surface, scale, svg, sym_difference, translate, union.
(4) The model_att keyword may be used to customize the attribute that GeoQuerySet methods attach output to.
(5) Geographic distance lookups and GeoQuerySet.distance calls now use ST_distance_sphere by default (performance benefits far outweigh small loss in accuracy); ST_distance_spheroid may still be used by specifying an option.
(6) GeoQuerySet methods may now operate accross ForeignKey? relations specified via the field_name keyword (but this does not work on Oracle).
(7) Area now has the same units of measure as Distance.

Backward Incompatibilites:

  • The aggregate union method is now known as unionagg.
  • The field_name keyword used for GeoQuerySet methods may no longer be specified via positional arguments.
  • Distance objects returned instead of floats from GeoQuerySet.distance.
  • ST_Distance_sphere used by default for geographic distance calculations.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/tests/distapp/data.py

    r7138 r7641  
    1212             ) 
    1313 
    14 stx_cities = (('Downtown Houston', 951640.547328, 4219369.26172), 
    15               ('West University Place', 943547.922328, 4213623.65345), 
    16               ('Southside Place', 944704.643307, 4212768.87617), 
    17               ('Bellaire', 942595.669129, 4212686.72583), 
    18               ('Pearland', 959674.616506, 4197465.6526), 
    19               ('Galveston', 1008151.16007, 4170027.47655), 
    20               ('Sealy', 874859.286808, 4219186.8641), 
    21               ('San Antonio', 649173.910483, 4176413.27786), 
    22               ('Round Rock', 726846.03695, 4297160.99715), 
    23               ('Saint Hedwig', 677644.649952, 4175467.06744), 
     14stx_cities = (('Downtown Houston', -95.363151, 29.763374), 
     15              ('West University Place', -95.448601, 29.713803), 
     16              ('Southside Place', -95.436920, 29.705777), 
     17              ('Bellaire', -95.458732, 29.705614), 
     18              ('Pearland', -95.287303, 29.563568), 
     19              ('Galveston', -94.797489, 29.301336), 
     20              ('Sealy', -96.156952, 29.780918), 
     21              ('San Antonio', -98.493183, 29.424170), 
     22              ('Saint Hedwig', -98.199820, 29.414197), 
    2423              ) 
    2524 
     25# Data from U.S. Census ZCTA cartographic boundary file for Texas (`zt48_d00.shp`). 
     26stx_zips = (('77002', '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))'), 
     27            ('77005', '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))'), 
     28            ('77025', '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))'), 
     29            ('77401', '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))'), 
     30            ) 
    2631 
     32interstates = (('I-25', 'LINESTRING(-104.4780170766108 36.66698791870694, -104.4468522338495 36.79925409393386, -104.46212692626 36.9372149776075, -104.5126119783768 37.08163268820887, -104.5247764602161 37.29300499892048, -104.7084397427668 37.49150259925398, -104.8126599016282 37.69514285621863, -104.8452887035466 37.87613395659479, -104.7160169341003 38.05951763337799, -104.6165437927668 38.30432045855106, -104.6437227858174 38.53979986564737, -104.7596170387259 38.7322907594295, -104.8380078676822 38.89998460604341, -104.8501253693506 39.09980189213358, -104.8791648316464 39.24368776457503, -104.8635041274215 39.3785278162751, -104.8894471170052 39.5929228239605, -104.9721242843344 39.69528482419685, -105.0112104500356 39.7273080432394, -105.0010368577104 39.76677607811571, -104.981835619 39.81466504121967, -104.9858891550477 39.88806911250832, -104.9873548059578 39.98117234571016, -104.9766220487419 40.09796423450692, -104.9818565932953 40.36056530662884, -104.9912746373997 40.74904484447656)'), 
     33               ) 
  • django/branches/gis/django/contrib/gis/tests/distapp/models.py

    r7104 r7641  
    55    name = models.CharField(max_length=30) 
    66    point = models.PointField(srid=32140) 
     7    objects = models.GeoManager() 
     8    def __unicode__(self): return self.name 
     9 
     10class SouthTexasCityFt(models.Model): 
     11    "Same City model as above, but U.S. survey feet are the units." 
     12    name = models.CharField(max_length=30) 
     13    point = models.PointField(srid=2278) 
    714    objects = models.GeoManager() 
    815    def __unicode__(self): return self.name 
     
    1522    def __unicode__(self): return self.name 
    1623 
    17 #class County(models.Model): 
    18 #    name = models.CharField(max_length=30) 
    19 #    mpoly = models.MultiPolygonField(srid=32140) 
    20 #    objects = models.GeoManager() 
     24class CensusZipcode(models.Model): 
     25    "Model for a few South Texas ZIP codes (in original Census NAD83)." 
     26    name = models.CharField(max_length=5) 
     27    poly = models.PolygonField(srid=4269) 
     28    objects = models.GeoManager() 
     29 
     30class SouthTexasZipcode(models.Model): 
     31    "Model for a few South Texas ZIP codes." 
     32    name = models.CharField(max_length=5) 
     33    poly = models.PolygonField(srid=32140) 
     34    objects = models.GeoManager() 
     35    def __unicode__(self): return self.name 
     36 
     37class Interstate(models.Model): 
     38    "Geodetic model for U.S. Interstates." 
     39    name = models.CharField(max_length=10) 
     40    line = models.LineStringField() 
     41    objects = models.GeoManager() 
     42    def __unicode__(self): return self.name 
  • django/branches/gis/django/contrib/gis/tests/distapp/tests.py

    r7464 r7641  
    22from decimal import Decimal 
    33 
     4from django.db.models import Q 
    45from django.contrib.gis.gdal import DataSource 
    56from django.contrib.gis.geos import GEOSGeometry, Point, LineString 
    67from django.contrib.gis.measure import D # alias for Distance 
    78from django.contrib.gis.db.models import GeoQ 
    8 from django.contrib.gis.tests.utils import oracle 
    9  
    10 from models import SouthTexasCity, AustraliaCity 
    11 from data import au_cities, stx_citie
     9from django.contrib.gis.tests.utils import oracle, postgis, no_oracle 
     10 
     11from models import AustraliaCity, Interstate, SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode 
     12from data import au_cities, interstates, stx_cities, stx_zip
    1213 
    1314class DistanceTest(unittest.TestCase): 
     
    2122    au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326) 
    2223 
    23     def get_cities(self, qs): 
     24    def get_names(self, qs): 
    2425        cities = [c.name for c in qs] 
    2526        cities.sort() 
     
    2829    def test01_init(self): 
    2930        "Initialization of distance models." 
    30          
    31         def load_cities(city_model, srid, data_tup): 
     31 
     32        # Loading up the cities. 
     33        def load_cities(city_model, data_tup): 
    3234            for name, x, y in data_tup: 
    33                 c = city_model(name=name, point=Point(x, y, srid=srid)) 
     35                c = city_model(name=name, point=Point(x, y, srid=4326)) 
    3436                c.save() 
    3537         
    36         load_cities(SouthTexasCity, 32140, stx_cities) 
    37         load_cities(AustraliaCity, 4326, au_cities) 
    38  
    39         self.assertEqual(10, SouthTexasCity.objects.count()) 
     38        load_cities(SouthTexasCity, stx_cities) 
     39        load_cities(SouthTexasCityFt, stx_cities) 
     40        load_cities(AustraliaCity, au_cities) 
     41 
     42        self.assertEqual(9, SouthTexasCity.objects.count()) 
     43        self.assertEqual(9, SouthTexasCityFt.objects.count()) 
    4044        self.assertEqual(11, AustraliaCity.objects.count()) 
     45         
     46        # Loading up the South Texas Zip Codes. 
     47        for name, wkt in stx_zips: 
     48            poly = GEOSGeometry(wkt, srid=4269) 
     49            SouthTexasZipcode(name=name, poly=poly).save() 
     50            CensusZipcode(name=name, poly=poly).save() 
     51        self.assertEqual(4, SouthTexasZipcode.objects.count()) 
     52        self.assertEqual(4, CensusZipcode.objects.count()) 
     53 
     54        # Loading up the Interstates. 
     55        for name, wkt in interstates: 
     56            Interstate(name=name, line=GEOSGeometry(wkt, srid=4326)).save() 
     57        self.assertEqual(1, Interstate.objects.count()) 
    4158 
    4259    def test02_dwithin(self): 
     
    4562        # degree/meter pair in au_cities, that's somewhat 
    4663        # approximate). 
    47         tx_dists = [7000, D(km=7), D(mi=4.349)] 
     64        tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)] 
    4865        au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)] 
    4966         
     
    5269        au_cities = ['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong'] 
    5370 
     71        # Performing distance queries on two projected coordinate systems one 
     72        # with units in meters and the other in units of U.S. survey feet. 
    5473        for dist in tx_dists: 
    55             qs = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist)) 
    56             self.assertEqual(tx_cities, self.get_cities(qs)) 
    57  
     74            if isinstance(dist, tuple): dist1, dist2 = dist 
     75            else: dist1 = dist2 = dist 
     76            qs1 = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist1)) 
     77            qs2 = SouthTexasCityFt.objects.filter(point__dwithin=(self.stx_pnt, dist2)) 
     78            for qs in qs1, qs2: 
     79                self.assertEqual(tx_cities, self.get_names(qs)) 
     80 
     81        # Now performing the `dwithin` queries on a geodetic coordinate system. 
    5882        for dist in au_dists: 
    5983            if isinstance(dist, D) and not oracle: type_error = True 
     
    7195                self.assertRaises(TypeError, qs.count) 
    7296            else: 
    73                 self.assertEqual(au_cities, self.get_cities(qs)) 
    74  
    75     def test03_distance_aggregate(self): 
    76         "Testing the `distance` GeoQuerySet method." 
     97                self.assertEqual(au_cities, self.get_names(qs)) 
     98 
     99    def test03a_distance_method(self): 
     100        "Testing the `distance` GeoQuerySet method on projected coordinate systems." 
    77101        # The point for La Grange, TX 
    78102        lagrange = GEOSGeometry('POINT(-96.876369 29.905320)', 4326) 
    79         # Got these from using the raw SQL statement: 
    80         #  SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326),32140)) FROM distapp_southtexascity; 
    81         distances = [147075.069813, 139630.198056, 140888.552826, 
    82                      138809.684197, 158309.246259, 212183.594374, 
    83                      70870.188967, 165337.758878, 102128.654360,  
    84                      139196.085105] 
    85  
    86         # Testing when the field name is explicitly set. 
    87         dist1 = SouthTexasCity.objects.distance('point', lagrange) 
     103        # Reference distances in feet and in meters. Got these values from  
     104        # using the provided raw SQL statements. 
     105        #  SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140)) FROM distapp_southtexascity; 
     106        m_distances = [147075.069813, 139630.198056, 140888.552826, 
     107                       138809.684197, 158309.246259, 212183.594374, 
     108                       70870.188967, 165337.758878, 139196.085105] 
     109        #  SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)) FROM distapp_southtexascityft; 
     110        ft_distances = [482528.79154625, 458103.408123001, 462231.860397575, 
     111                        455411.438904354, 519386.252102563, 696139.009211594, 
     112                        232513.278304279, 542445.630586414, 456679.155883207] 
     113 
     114        # Testing using different variations of parameters and using models 
     115        # with different projected coordinate systems. 
     116        dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point') 
    88117        dist2 = SouthTexasCity.objects.distance(lagrange)  # Using GEOSGeometry parameter 
    89         dist3 = SouthTexasCity.objects.distance(lagrange.ewkt) # Using EWKT string parameter. 
     118        dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter. 
     119        dist4 = SouthTexasCityFt.objects.distance(lagrange) 
    90120 
    91121        # Original query done on PostGIS, have to adjust AlmostEqual tolerance 
     
    95125 
    96126        # Ensuring expected distances are returned for each distance queryset. 
    97         for qs in [dist1, dist2, dist3]: 
     127        for qs in [dist1, dist2, dist3, dist4]: 
    98128            for i, c in enumerate(qs): 
    99                 self.assertAlmostEqual(distances[i], c.distance, tol) 
     129                self.assertAlmostEqual(m_distances[i], c.distance.m, tol) 
     130                self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol) 
     131 
     132    def test03b_distance_method(self): 
     133        "Testing the `distance` GeoQuerySet method on geodetic coordnate systems." 
     134        if oracle: tol = 2 
     135        else: tol = 5 
    100136 
    101137        # Now testing geodetic distance aggregation. 
     
    107143            self.assertRaises(TypeError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1))) 
    108144 
    109         # Got these distances using the raw SQL statement
     145        # Got the reference distances using the raw SQL statements
    110146        #  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)); 
    111         geodetic_distances = [60504.0628825298, 77023.948962654, 49154.8867507115, 90847.435881812, 217402.811862568, 709599.234619957, 640011.483583758, 7772.00667666425, 1047861.7859506, 1165126.55237647] 
    112  
    113         # Ensuring the expected distances are returned. 
    114         qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point) 
     147        spheroid_distances = [60504.0628825298, 77023.948962654, 49154.8867507115, 90847.435881812, 217402.811862568, 709599.234619957, 640011.483583758, 7772.00667666425, 1047861.7859506, 1165126.55237647] 
     148        #  SELECT ST_distance_sphere(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326)) FROM distapp_australiacity WHERE (NOT (id = 11));  st_distance_sphere 
     149        sphere_distances = [60580.7612632291, 77143.7785056615, 49199.2725132184, 90804.4414289463, 217712.63666124, 709131.691061906, 639825.959074112, 7786.80274606706, 1049200.46122281, 1162619.7297006] 
     150 
     151        # Testing with spheroid distances first. 
     152        qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point, spheroid=True) 
    115153        for i, c in enumerate(qs): 
    116             self.assertAlmostEqual(geodetic_distances[i], c.distance, tol) 
     154            self.assertAlmostEqual(spheroid_distances[i], c.distance.m, tol) 
     155        if postgis: 
     156            # PostGIS uses sphere-only distances by default, testing these as well. 
     157            qs =  AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point) 
     158            for i, c in enumerate(qs): 
     159                self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol) 
     160 
     161    @no_oracle # Oracle already handles geographic distance calculation. 
     162    def test03c_distance_method(self): 
     163        "Testing the `distance` GeoQuerySet method used with `transform` on a geographic field." 
     164        # Normally you can't compute distances from a geometry field 
     165        # that is not a PointField (on PostGIS). 
     166        self.assertRaises(TypeError, CensusZipcode.objects.distance, self.stx_pnt) 
     167         
     168        # We'll be using a Polygon (created by buffering the centroid 
     169        # of 77005 to 100m) -- which aren't allowed in geographic distance 
     170        # queries normally, however our field has been transformed to 
     171        # a non-geographic system. 
     172        z = SouthTexasZipcode.objects.get(name='77005') 
     173 
     174        # Reference query: 
     175        # SELECT ST_Distance(ST_Transform("distapp_censuszipcode"."poly", 32140), ST_GeomFromText('<buffer_wkt>', 32140)) FROM "distapp_censuszipcode"; 
     176        dists_m = [3553.30384972258, 1243.18391525602, 2186.15439472242] 
     177 
     178        # Having our buffer in the SRID of the transformation and of the field 
     179        # -- should get the same results. The first buffer has no need for 
     180        # transformation SQL because it is the same SRID as what was given 
     181        # to `transform()`.  The second buffer will need to be transformed, 
     182        # however. 
     183        buf1 = z.poly.centroid.buffer(100) 
     184        buf2 = buf1.transform(4269, clone=True) 
     185        for buf in [buf1, buf2]: 
     186            qs = CensusZipcode.objects.exclude(name='77005').transform(32140).distance(buf) 
     187            self.assertEqual(['77002', '77025', '77401'], self.get_names(qs)) 
     188            for i, z in enumerate(qs): 
     189                self.assertAlmostEqual(z.distance.m, dists_m[i], 5) 
    117190 
    118191    def test04_distance_lookups(self): 
    119192        "Testing the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types." 
    120         # Only two cities (Houston and Southside Place) should be 
    121         # within 7km of the given point. 
    122         dists = [D(km=7), D(mi=4.349), # Distance instances in different units. 
    123                  7000, 7000.0, Decimal(7000), # int, float, Decimal parameters. 
    124                  ] 
    125  
    126         for dist in dists: 
    127             qs = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist)) 
    128             for c in qs: 
    129                 cities = self.get_cities(qs) 
    130                 self.assertEqual(cities, ['Downtown Houston', 'Southside Place']) 
    131  
    132         # Now only retrieving the cities within a 20km 'donut' w/a 7km radius 'hole' 
    133         # (thus, Houston and Southside place will be excluded) 
    134         qs = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20))) 
    135         cities = self.get_cities(qs) 
    136         self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place']) 
    137  
    138     def test05_geodetic_distance(self): 
     193        # Retrieving the cities within a 20km 'donut' w/a 7km radius 'hole' 
     194        # (thus, Houston and Southside place will be excluded as tested in 
     195        # the `test02_dwithin` above). 
     196        qs1 = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20))) 
     197        qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20))) 
     198        for qs in qs1, qs2: 
     199            cities = self.get_names(qs) 
     200            self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place']) 
     201 
     202        # Doing a distance query using Polygons instead of a Point. 
     203        z = SouthTexasZipcode.objects.get(name='77005') 
     204        qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=275))) 
     205        self.assertEqual(['77025', '77401'], self.get_names(qs)) 
     206        # If we add a little more distance 77002 should be included. 
     207        qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300))) 
     208        self.assertEqual(['77002', '77025', '77401'], self.get_names(qs)) 
     209         
     210    def test05_geodetic_distance_lookups(self): 
    139211        "Testing distance lookups on geodetic coordinate systems." 
    140          
    141212        if not oracle: 
    142213            # Oracle doesn't have this limitation -- PostGIS only allows geodetic 
     
    145216            self.assertRaises(TypeError, 
    146217                              AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100)))) 
    147              
     218            # Too many params (4 in this case) should raise a ValueError. 
     219            self.assertRaises(ValueError,  
     220                              AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')).count) 
     221 
     222        # Not enough params should raise a ValueError. 
     223        self.assertRaises(ValueError, 
     224                          AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)).count) 
     225 
     226        # Getting all cities w/in 550 miles of Hobart. 
    148227        hobart = AustraliaCity.objects.get(name='Hobart') 
    149          
    150         # Getting all cities w/in 550 miles of Hobart. 
    151228        qs = AustraliaCity.objects.exclude(name='Hobart').filter(point__distance_lte=(hobart.point, D(mi=550))) 
    152         cities = self.get_cities(qs) 
     229        cities = self.get_names(qs) 
    153230        self.assertEqual(cities, ['Batemans Bay', 'Canberra', 'Melbourne']) 
    154231 
     
    156233        # and using different units of distance. 
    157234        wollongong = AustraliaCity.objects.get(name='Wollongong') 
    158         gq1 = GeoQ(point__distance_lte=(wollongong.point, D(yd=19500))) # Yards (~17km) 
    159         gq2 = GeoQ(point__distance_gte=(wollongong.point, D(nm=400)))   # Nautical Miles 
    160         qs = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2) 
    161         cities = self.get_cities(qs) 
    162         self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul']) 
    163  
     235        d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles. 
     236 
     237        # Normal geodetic distance lookup (uses `distance_sphere` on PostGIS. 
     238        gq1 = GeoQ(point__distance_lte=(wollongong.point, d1)) 
     239        gq2 = GeoQ(point__distance_gte=(wollongong.point, d2)) 
     240        qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2) 
     241 
     242        # Geodetic distance lookup but telling GeoDjango to use `distance_spheroid` 
     243        # instead (we should get the same results b/c accuracy variance won't matter 
     244        # in this test case). Using `Q` instead of `GeoQ` to be different (post-qsrf 
     245        # it doesn't matter). 
     246        if postgis: 
     247            gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid')) 
     248            gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid')) 
     249            qs2 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq3 | gq4) 
     250            querysets = [qs1, qs2] 
     251        else: 
     252            querysets = [qs1] 
     253 
     254        for qs in querysets: 
     255            cities = self.get_names(qs) 
     256            self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul']) 
     257 
     258    def test06_area(self): 
     259        "Testing the `area` GeoQuerySet method." 
     260        # Reference queries: 
     261        # SELECT ST_Area(poly) FROM distapp_southtexaszipcode; 
     262        area_sq_m = [5437908.90234375, 10183031.4389648, 11254471.0073242, 9881708.91772461] 
     263        # Tolerance has to be lower for Oracle and differences 
     264        # with GEOS 3.0.0RC4 
     265        tol = 2 
     266        for i, z in enumerate(SouthTexasZipcode.objects.area()): 
     267            self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol) 
     268 
     269    def test07_length(self): 
     270        "Testing the `length` GeoQuerySet method." 
     271        # Reference query (should use `length_spheroid`). 
     272        # SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]'); 
     273        len_m = 473504.769553813 
     274        qs = Interstate.objects.length() 
     275        if oracle: tol = 2 
     276        else: tol = 7 
     277        self.assertAlmostEqual(len_m, qs[0].length.m, tol) 
     278 
     279    def test08_perimeter(self): 
     280        "Testing the `perimeter` GeoQuerySet method." 
     281        # Reference query: 
     282        # SELECT ST_Perimeter(distapp_southtexaszipcode.poly) FROM distapp_southtexaszipcode; 
     283        perim_m = [18404.3550889361, 15627.2108551001, 20632.5588368978, 17094.5996143697] 
     284        if oracle: tol = 2 
     285        else: tol = 7 
     286        for i, z in enumerate(SouthTexasZipcode.objects.perimeter()): 
     287            self.assertAlmostEqual(perim_m[i], z.perimeter.m, tol) 
     288 
     289        # Running on points; should return 0. 
     290        for i, c in enumerate(SouthTexasCity.objects.perimeter(model_att='perim')): 
     291            self.assertEqual(0, c.perim.m) 
    164292 
    165293def suite():