- """
- Tests for geography support in PostGIS
- """
- import os 
- from django.contrib.gis.db import models 
- from django.contrib.gis.db.models.functions import Area, Distance 
- from django.contrib.gis.measure import D 
- from django.db import NotSupportedError, connection 
- from django.db.models.functions import Cast 
- from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature 
- from ..utils import FuncTestMixin 
- from .models import City, County, Zipcode 
- class GeographyTest(TestCase): 
- fixtures = ["initial"] 
- def test01_fixture_load(self): 
- "Ensure geography features loaded properly." 
- self.assertEqual(8, City.objects.count()) 
- @skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic") 
- def test02_distance_lookup(self): 
- "Testing distance lookup support on non-point geography fields." 
- z = Zipcode.objects.get(code="77002") 
- 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 test04_invalid_operators_functions(self): 
- """ 
- Exceptions are raised for operators & functions invalid on geography
- fields.
- """
- if not connection.ops.postgis: 
- self.skipTest("This is a PostGIS-specific test.") 
- # 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. 
- with self.assertRaises(ValueError): 
- City.objects.filter(point__within=z.poly).count() 
- # `@` operator not available. 
- with self.assertRaises(ValueError): 
- City.objects.filter(point__contained=z.poly).count() 
- # Regression test for #14060, `~=` was never really implemented for PostGIS. 
- htown = City.objects.get(name="Houston") 
- with self.assertRaises(ValueError): 
- City.objects.get(point__exact=htown.point) 
- 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. 
- 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) 
- class GeographyFunctionTests(FuncTestMixin, TestCase): 
- fixtures = ["initial"] 
- @skipUnlessDBFeature("supports_extent_aggr") 
- def test_cast_aggregate(self): 
- """ 
- Cast a geography to a geometry field for an aggregate function that
- expects a geometry input.
- """
- if not connection.features.supports_geography: 
- self.skipTest("This test needs geography support") 
- expected = (
- -96.8016128540039, 
- 29.7633724212646, 
- -95.3631439208984, 
- 32.782058715820, 
- )
- res = City.objects.filter(name__in=("Houston", "Dallas")).aggregate( 
- extent=models.Extent(Cast("point", models.PointField())) 
- )
- for val, exp in zip(res["extent"], expected): 
- self.assertAlmostEqual(exp, val, 4) 
- @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic") 
- def test_distance_function(self): 
- """ 
- Testing Distance() support on non-point geography fields.
- """
- if connection.ops.oracle: 
- ref_dists = [0, 4899.68, 8081.30, 9115.15] 
- elif connection.ops.spatialite: 
- if connection.ops.spatial_version < (5,): 
- # SpatiaLite < 5 returns non-zero distance for polygons and points 
- # covered by that polygon. 
- ref_dists = [326.61, 4899.68, 8081.30, 9115.15] 
- else: 
- ref_dists = [0, 4899.68, 8081.30, 9115.15] 
- else: 
- ref_dists = [0, 4891.20, 8071.64, 9123.95] 
- htown = City.objects.get(name="Houston") 
- qs = Zipcode.objects.annotate( 
- distance=Distance("poly", htown.point), 
- distance2=Distance(htown.point, "poly"), 
- )
- for z, ref in zip(qs, ref_dists): 
- self.assertAlmostEqual(z.distance.m, ref, 2) 
- if connection.ops.postgis: 
- # PostGIS casts geography to geometry when distance2 is calculated. 
- ref_dists = [0, 4899.68, 8081.30, 9115.15] 
- for z, ref in zip(qs, ref_dists): 
- self.assertAlmostEqual(z.distance2.m, ref, 2) 
- if not connection.ops.spatialite: 
- # Distance function combined with a lookup. 
- hzip = Zipcode.objects.get(code="77002") 
- self.assertEqual(qs.get(distance__lte=0), hzip) 
- @skipUnlessDBFeature("has_Area_function", "supports_area_geodetic") 
- def test_geography_area(self): 
- """ 
- Testing that Area calculations work on geography columns.
- """
- # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002'; 
- z = Zipcode.objects.annotate(area=Area("poly")).get(code="77002") 
- # Round to the nearest thousand as possible values (depending on 
- # the database and geolib) include 5439084, 5439100, 5439101. 
- rounded_value = z.area.sq_m
- rounded_value -= z.area.sq_m % 1000 
- self.assertEqual(rounded_value, 5439000) 
- @skipUnlessDBFeature("has_Area_function") 
- @skipIfDBFeature("supports_area_geodetic") 
- def test_geodetic_area_raises_if_not_supported(self): 
- with self.assertRaisesMessage( 
- NotSupportedError, "Area on geodetic coordinate systems not supported." 
- ):
- Zipcode.objects.annotate(area=Area("poly")).get(code="77002")