PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/gis/tests/geo3d/tests.py

https://github.com/insane/django
Python | 278 lines | 161 code | 31 blank | 86 comment | 15 complexity | 0eea548ecff02f15d37e40dbb2e3d606 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. from __future__ import absolute_import, unicode_literals
  2. import os
  3. import re
  4. from unittest import skipUnless
  5. from django.contrib.gis.gdal import HAS_GDAL
  6. from django.contrib.gis.geos import HAS_GEOS
  7. from django.contrib.gis.tests.utils import postgis
  8. from django.test import TestCase
  9. from django.utils._os import upath
  10. if HAS_GEOS:
  11. from django.contrib.gis.db.models import Union, Extent3D
  12. from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon
  13. from .models import (City3D, Interstate2D, Interstate3D, InterstateProj2D,
  14. InterstateProj3D, Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D)
  15. if HAS_GDAL:
  16. from django.contrib.gis.utils import LayerMapping, LayerMapError
  17. data_path = os.path.realpath(os.path.join(os.path.dirname(upath(__file__)), '..', 'data'))
  18. city_file = os.path.join(data_path, 'cities', 'cities.shp')
  19. vrt_file = os.path.join(data_path, 'test_vrt', 'test_vrt.vrt')
  20. # The coordinates of each city, with Z values corresponding to their
  21. # altitude in meters.
  22. city_data = (
  23. ('Houston', (-95.363151, 29.763374, 18)),
  24. ('Dallas', (-96.801611, 32.782057, 147)),
  25. ('Oklahoma City', (-97.521157, 34.464642, 380)),
  26. ('Wellington', (174.783117, -41.315268, 14)),
  27. ('Pueblo', (-104.609252, 38.255001, 1433)),
  28. ('Lawrence', (-95.235060, 38.971823, 251)),
  29. ('Chicago', (-87.650175, 41.850385, 181)),
  30. ('Victoria', (-123.305196, 48.462611, 15)),
  31. )
  32. # Reference mapping of city name to its altitude (Z value).
  33. city_dict = dict((name, coords) for name, coords in city_data)
  34. # 3D freeway data derived from the National Elevation Dataset:
  35. # http://seamless.usgs.gov/products/9arc.php
  36. interstate_data = (
  37. ('I-45',
  38. 'LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)',
  39. ( 11.339, 4.536, 9.762, 12.448, 10.457, 9.418, 14.858,
  40. 15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16 ,
  41. 15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
  42. 15.435),
  43. ),
  44. )
  45. # Bounding box polygon for inner-loop of Houston (in projected coordinate
  46. # system 32140), with elevation values from the National Elevation Dataset
  47. # (see above).
  48. bbox_data = (
  49. 'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,942051.75 4208366.38,941527.97 4225693.20))',
  50. (21.71, 13.21, 9.12, 16.40, 21.71)
  51. )
  52. @skipUnless(HAS_GEOS and HAS_GDAL and postgis, "Geos, GDAL and postgis are required.")
  53. class Geo3DTest(TestCase):
  54. """
  55. Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
  56. tries to test the features that can handle 3D and that are also
  57. available within GeoDjango. For more information, see the PostGIS docs
  58. on the routines that support 3D:
  59. http://postgis.refractions.net/documentation/manual-1.4/ch08.html#PostGIS_3D_Functions
  60. """
  61. def _load_interstate_data(self):
  62. # Interstate (2D / 3D and Geographic/Projected variants)
  63. for name, line, exp_z in interstate_data:
  64. line_3d = GEOSGeometry(line, srid=4269)
  65. line_2d = LineString([l[:2] for l in line_3d.coords], srid=4269)
  66. # Creating a geographic and projected version of the
  67. # interstate in both 2D and 3D.
  68. Interstate3D.objects.create(name=name, line=line_3d)
  69. InterstateProj3D.objects.create(name=name, line=line_3d)
  70. Interstate2D.objects.create(name=name, line=line_2d)
  71. InterstateProj2D.objects.create(name=name, line=line_2d)
  72. def _load_city_data(self):
  73. for name, pnt_data in city_data:
  74. City3D.objects.create(name=name, point=Point(*pnt_data, srid=4326))
  75. def _load_polygon_data(self):
  76. bbox_wkt, bbox_z = bbox_data
  77. bbox_2d = GEOSGeometry(bbox_wkt, srid=32140)
  78. bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140)
  79. Polygon2D.objects.create(name='2D BBox', poly=bbox_2d)
  80. Polygon3D.objects.create(name='3D BBox', poly=bbox_3d)
  81. def test_3d_hasz(self):
  82. """
  83. Make sure data is 3D and has expected Z values -- shouldn't change
  84. because of coordinate system.
  85. """
  86. self._load_interstate_data()
  87. for name, line, exp_z in interstate_data:
  88. interstate = Interstate3D.objects.get(name=name)
  89. interstate_proj = InterstateProj3D.objects.get(name=name)
  90. for i in [interstate, interstate_proj]:
  91. self.assertTrue(i.line.hasz)
  92. self.assertEqual(exp_z, tuple(i.line.z))
  93. self._load_city_data()
  94. for name, pnt_data in city_data:
  95. city = City3D.objects.get(name=name)
  96. z = pnt_data[2]
  97. self.assertTrue(city.point.hasz)
  98. self.assertEqual(z, city.point.z)
  99. def test_3d_polygons(self):
  100. """
  101. Test the creation of polygon 3D models.
  102. """
  103. self._load_polygon_data()
  104. p3d = Polygon3D.objects.get(name='3D BBox')
  105. self.assertTrue(p3d.poly.hasz)
  106. self.assertIsInstance(p3d.poly, Polygon)
  107. self.assertEqual(p3d.poly.srid, 32140)
  108. def test_3d_layermapping(self):
  109. """
  110. Testing LayerMapping on 3D models.
  111. """
  112. point_mapping = {'point' : 'POINT'}
  113. mpoint_mapping = {'mpoint' : 'MULTIPOINT'}
  114. # The VRT is 3D, but should still be able to map sans the Z.
  115. lm = LayerMapping(Point2D, vrt_file, point_mapping, transform=False)
  116. lm.save()
  117. self.assertEqual(3, Point2D.objects.count())
  118. # The city shapefile is 2D, and won't be able to fill the coordinates
  119. # in the 3D model -- thus, a LayerMapError is raised.
  120. self.assertRaises(LayerMapError, LayerMapping,
  121. Point3D, city_file, point_mapping, transform=False)
  122. # 3D model should take 3D data just fine.
  123. lm = LayerMapping(Point3D, vrt_file, point_mapping, transform=False)
  124. lm.save()
  125. self.assertEqual(3, Point3D.objects.count())
  126. # Making sure LayerMapping.make_multi works right, by converting
  127. # a Point25D into a MultiPoint25D.
  128. lm = LayerMapping(MultiPoint3D, vrt_file, mpoint_mapping, transform=False)
  129. lm.save()
  130. self.assertEqual(3, MultiPoint3D.objects.count())
  131. def test_kml(self):
  132. """
  133. Test GeoQuerySet.kml() with Z values.
  134. """
  135. self._load_city_data()
  136. h = City3D.objects.kml(precision=6).get(name='Houston')
  137. # KML should be 3D.
  138. # `SELECT ST_AsKML(point, 6) FROM geo3d_city3d WHERE name = 'Houston';`
  139. ref_kml_regex = re.compile(r'^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$')
  140. self.assertTrue(ref_kml_regex.match(h.kml))
  141. def test_geojson(self):
  142. """
  143. Test GeoQuerySet.geojson() with Z values.
  144. """
  145. self._load_city_data()
  146. h = City3D.objects.geojson(precision=6).get(name='Houston')
  147. # GeoJSON should be 3D
  148. # `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';`
  149. ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
  150. self.assertTrue(ref_json_regex.match(h.geojson))
  151. def test_union(self):
  152. """
  153. Testing the Union aggregate of 3D models.
  154. """
  155. # PostGIS query that returned the reference EWKT for this test:
  156. # `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;`
  157. self._load_city_data()
  158. ref_ewkt = 'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)'
  159. ref_union = GEOSGeometry(ref_ewkt)
  160. union = City3D.objects.aggregate(Union('point'))['point__union']
  161. self.assertTrue(union.hasz)
  162. self.assertEqual(ref_union, union)
  163. def test_extent(self):
  164. """
  165. Testing the Extent3D aggregate for 3D models.
  166. """
  167. self._load_city_data()
  168. # `SELECT ST_Extent3D(point) FROM geo3d_city3d;`
  169. ref_extent3d = (-123.305196, -41.315268, 14,174.783117, 48.462611, 1433)
  170. extent1 = City3D.objects.aggregate(Extent3D('point'))['point__extent3d']
  171. extent2 = City3D.objects.extent3d()
  172. def check_extent3d(extent3d, tol=6):
  173. for ref_val, ext_val in zip(ref_extent3d, extent3d):
  174. self.assertAlmostEqual(ref_val, ext_val, tol)
  175. for e3d in [extent1, extent2]:
  176. check_extent3d(e3d)
  177. def test_perimeter(self):
  178. """
  179. Testing GeoQuerySet.perimeter() on 3D fields.
  180. """
  181. self._load_polygon_data()
  182. # Reference query for values below:
  183. # `SELECT ST_Perimeter3D(poly), ST_Perimeter2D(poly) FROM geo3d_polygon3d;`
  184. ref_perim_3d = 76859.2620451
  185. ref_perim_2d = 76859.2577803
  186. tol = 6
  187. self.assertAlmostEqual(ref_perim_2d,
  188. Polygon2D.objects.perimeter().get(name='2D BBox').perimeter.m,
  189. tol)
  190. self.assertAlmostEqual(ref_perim_3d,
  191. Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
  192. tol)
  193. def test_length(self):
  194. """
  195. Testing GeoQuerySet.length() on 3D fields.
  196. """
  197. # ST_Length_Spheroid Z-aware, and thus does not need to use
  198. # a separate function internally.
  199. # `SELECT ST_Length_Spheroid(line, 'SPHEROID["GRS 1980",6378137,298.257222101]')
  200. # FROM geo3d_interstate[2d|3d];`
  201. self._load_interstate_data()
  202. tol = 3
  203. ref_length_2d = 4368.1721949481
  204. ref_length_3d = 4368.62547052088
  205. self.assertAlmostEqual(ref_length_2d,
  206. Interstate2D.objects.length().get(name='I-45').length.m,
  207. tol)
  208. self.assertAlmostEqual(ref_length_3d,
  209. Interstate3D.objects.length().get(name='I-45').length.m,
  210. tol)
  211. # Making sure `ST_Length3D` is used on for a projected
  212. # and 3D model rather than `ST_Length`.
  213. # `SELECT ST_Length(line) FROM geo3d_interstateproj2d;`
  214. ref_length_2d = 4367.71564892392
  215. # `SELECT ST_Length3D(line) FROM geo3d_interstateproj3d;`
  216. ref_length_3d = 4368.16897234101
  217. self.assertAlmostEqual(ref_length_2d,
  218. InterstateProj2D.objects.length().get(name='I-45').length.m,
  219. tol)
  220. self.assertAlmostEqual(ref_length_3d,
  221. InterstateProj3D.objects.length().get(name='I-45').length.m,
  222. tol)
  223. def test_scale(self):
  224. """
  225. Testing GeoQuerySet.scale() on Z values.
  226. """
  227. self._load_city_data()
  228. # Mapping of City name to reference Z values.
  229. zscales = (-3, 4, 23)
  230. for zscale in zscales:
  231. for city in City3D.objects.scale(1.0, 1.0, zscale):
  232. self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
  233. def test_translate(self):
  234. """
  235. Testing GeoQuerySet.translate() on Z values.
  236. """
  237. self._load_city_data()
  238. ztranslations = (5.23, 23, -17)
  239. for ztrans in ztranslations:
  240. for city in City3D.objects.translate(0, 0, ztrans):
  241. self.assertEqual(city_dict[city.name][2] + ztrans, city.translate.z)