tests.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. """
  2. Tests for geography support in PostGIS
  3. """
  4. from __future__ import unicode_literals
  5. import os
  6. from unittest import skipUnless
  7. from django.contrib.gis.db import models
  8. from django.contrib.gis.db.models.functions import Area, Distance
  9. from django.contrib.gis.gdal import HAS_GDAL
  10. from django.contrib.gis.measure import D
  11. from django.db import connection
  12. from django.db.models.functions import Cast
  13. from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
  14. from django.utils._os import upath
  15. from django.utils.deprecation import RemovedInDjango20Warning
  16. from ..utils import oracle, postgis
  17. from .models import City, County, Zipcode
  18. @skipUnlessDBFeature("gis_enabled")
  19. class GeographyTest(TestCase):
  20. fixtures = ['initial']
  21. def test01_fixture_load(self):
  22. "Ensure geography features loaded properly."
  23. self.assertEqual(8, City.objects.count())
  24. @skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
  25. def test02_distance_lookup(self):
  26. "Testing GeoQuerySet distance lookup support on non-point geography fields."
  27. z = Zipcode.objects.get(code='77002')
  28. cities1 = list(City.objects
  29. .filter(point__distance_lte=(z.poly, D(mi=500)))
  30. .order_by('name')
  31. .values_list('name', flat=True))
  32. cities2 = list(City.objects
  33. .filter(point__dwithin=(z.poly, D(mi=500)))
  34. .order_by('name')
  35. .values_list('name', flat=True))
  36. for cities in [cities1, cities2]:
  37. self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
  38. @skipUnlessDBFeature("has_distance_method", "supports_distance_geodetic")
  39. @ignore_warnings(category=RemovedInDjango20Warning)
  40. def test03_distance_method(self):
  41. "Testing GeoQuerySet.distance() support on non-point geography fields."
  42. # `GeoQuerySet.distance` is not allowed geometry fields.
  43. htown = City.objects.get(name='Houston')
  44. Zipcode.objects.distance(htown.point)
  45. @skipUnless(postgis, "This is a PostGIS-specific test")
  46. def test04_invalid_operators_functions(self):
  47. "Ensuring exceptions are raised for operators & functions invalid on geography fields."
  48. # Only a subset of the geometry functions & operator are available
  49. # to PostGIS geography types. For more information, visit:
  50. # http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_GeographyFunctions
  51. z = Zipcode.objects.get(code='77002')
  52. # ST_Within not available.
  53. with self.assertRaises(ValueError):
  54. City.objects.filter(point__within=z.poly).count()
  55. # `@` operator not available.
  56. with self.assertRaises(ValueError):
  57. City.objects.filter(point__contained=z.poly).count()
  58. # Regression test for #14060, `~=` was never really implemented for PostGIS.
  59. htown = City.objects.get(name='Houston')
  60. with self.assertRaises(ValueError):
  61. City.objects.get(point__exact=htown.point)
  62. @skipUnless(HAS_GDAL, "GDAL is required.")
  63. def test05_geography_layermapping(self):
  64. "Testing LayerMapping support on models with geography fields."
  65. # There is a similar test in `layermap` that uses the same data set,
  66. # but the County model here is a bit different.
  67. from django.contrib.gis.utils import LayerMapping
  68. # Getting the shapefile and mapping dictionary.
  69. shp_path = os.path.realpath(os.path.join(os.path.dirname(upath(__file__)), '..', 'data'))
  70. co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
  71. co_mapping = {'name': 'Name',
  72. 'state': 'State',
  73. 'mpoly': 'MULTIPOLYGON',
  74. }
  75. # Reference county names, number of polygons, and state names.
  76. names = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
  77. num_polys = [1, 2, 1, 19, 1] # Number of polygons for each.
  78. st_names = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
  79. lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269, unique='name')
  80. lm.save(silent=True, strict=True)
  81. for c, name, num_poly, state in zip(County.objects.order_by('name'), names, num_polys, st_names):
  82. self.assertEqual(4326, c.mpoly.srid)
  83. self.assertEqual(num_poly, len(c.mpoly))
  84. self.assertEqual(name, c.name)
  85. self.assertEqual(state, c.state)
  86. @skipUnlessDBFeature("has_area_method", "supports_distance_geodetic")
  87. @ignore_warnings(category=RemovedInDjango20Warning)
  88. def test06_geography_area(self):
  89. "Testing that Area calculations work on geography columns."
  90. # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002';
  91. z = Zipcode.objects.area().get(code='77002')
  92. # Round to the nearest thousand as possible values (depending on
  93. # the database and geolib) include 5439084, 5439100, 5439101.
  94. rounded_value = z.area.sq_m
  95. rounded_value -= z.area.sq_m % 1000
  96. self.assertEqual(rounded_value, 5439000)
  97. @skipUnlessDBFeature("gis_enabled")
  98. class GeographyFunctionTests(TestCase):
  99. fixtures = ['initial']
  100. @skipUnlessDBFeature("supports_extent_aggr")
  101. def test_cast_aggregate(self):
  102. """
  103. Cast a geography to a geometry field for an aggregate function that
  104. expects a geometry input.
  105. """
  106. if not connection.ops.geography:
  107. self.skipTest("This test needs geography support")
  108. expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
  109. res = City.objects.filter(
  110. name__in=('Houston', 'Dallas')
  111. ).aggregate(extent=models.Extent(Cast('point', models.PointField())))
  112. for val, exp in zip(res['extent'], expected):
  113. self.assertAlmostEqual(exp, val, 4)
  114. @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
  115. def test_distance_function(self):
  116. """
  117. Testing Distance() support on non-point geography fields.
  118. """
  119. if oracle:
  120. ref_dists = [0, 4899.68, 8081.30, 9115.15]
  121. else:
  122. ref_dists = [0, 4891.20, 8071.64, 9123.95]
  123. htown = City.objects.get(name='Houston')
  124. qs = Zipcode.objects.annotate(distance=Distance('poly', htown.point))
  125. for z, ref in zip(qs, ref_dists):
  126. self.assertAlmostEqual(z.distance.m, ref, 2)
  127. @skipUnlessDBFeature("has_Area_function", "supports_distance_geodetic")
  128. def test_geography_area(self):
  129. """
  130. Testing that Area calculations work on geography columns.
  131. """
  132. # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002';
  133. z = Zipcode.objects.annotate(area=Area('poly')).get(code='77002')
  134. # Round to the nearest thousand as possible values (depending on
  135. # the database and geolib) include 5439084, 5439100, 5439101.
  136. rounded_value = z.area.sq_m
  137. rounded_value -= z.area.sq_m % 1000
  138. self.assertEqual(rounded_value, 5439000)