1. from django.contrib.gis.db.models.functions import (
    
  2.     Area,
    
  3.     Distance,
    
  4.     Length,
    
  5.     Perimeter,
    
  6.     Transform,
    
  7.     Union,
    
  8. )
    
  9. from django.contrib.gis.geos import GEOSGeometry, LineString, Point
    
  10. from django.contrib.gis.measure import D  # alias for Distance
    
  11. from django.db import NotSupportedError, connection
    
  12. from django.db.models import (
    
  13.     Case,
    
  14.     Count,
    
  15.     Exists,
    
  16.     F,
    
  17.     IntegerField,
    
  18.     OuterRef,
    
  19.     Q,
    
  20.     Value,
    
  21.     When,
    
  22. )
    
  23. from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
    
  24. 
    
  25. from ..utils import FuncTestMixin
    
  26. from .models import (
    
  27.     AustraliaCity,
    
  28.     CensusZipcode,
    
  29.     Interstate,
    
  30.     SouthTexasCity,
    
  31.     SouthTexasCityFt,
    
  32.     SouthTexasInterstate,
    
  33.     SouthTexasZipcode,
    
  34. )
    
  35. 
    
  36. 
    
  37. class DistanceTest(TestCase):
    
  38.     fixtures = ["initial"]
    
  39. 
    
  40.     def setUp(self):
    
  41.         # A point we are testing distances with -- using a WGS84
    
  42.         # coordinate that'll be implicitly transformed to that to
    
  43.         # the coordinate system of the field, EPSG:32140 (Texas South Central
    
  44.         # w/units in meters)
    
  45.         self.stx_pnt = GEOSGeometry(
    
  46.             "POINT (-95.370401017314293 29.704867409475465)", 4326
    
  47.         )
    
  48.         # Another one for Australia
    
  49.         self.au_pnt = GEOSGeometry("POINT (150.791 -34.4919)", 4326)
    
  50. 
    
  51.     def get_names(self, qs):
    
  52.         cities = [c.name for c in qs]
    
  53.         cities.sort()
    
  54.         return cities
    
  55. 
    
  56.     def test_init(self):
    
  57.         """
    
  58.         Test initialization of distance models.
    
  59.         """
    
  60.         self.assertEqual(9, SouthTexasCity.objects.count())
    
  61.         self.assertEqual(9, SouthTexasCityFt.objects.count())
    
  62.         self.assertEqual(11, AustraliaCity.objects.count())
    
  63.         self.assertEqual(4, SouthTexasZipcode.objects.count())
    
  64.         self.assertEqual(4, CensusZipcode.objects.count())
    
  65.         self.assertEqual(1, Interstate.objects.count())
    
  66.         self.assertEqual(1, SouthTexasInterstate.objects.count())
    
  67. 
    
  68.     @skipUnlessDBFeature("supports_dwithin_lookup")
    
  69.     def test_dwithin(self):
    
  70.         """
    
  71.         Test the `dwithin` lookup type.
    
  72.         """
    
  73.         # Distances -- all should be equal (except for the
    
  74.         # degree/meter pair in au_cities, that's somewhat
    
  75.         # approximate).
    
  76.         tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)]
    
  77.         au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)]
    
  78. 
    
  79.         # Expected cities for Australia and Texas.
    
  80.         tx_cities = ["Downtown Houston", "Southside Place"]
    
  81.         au_cities = ["Mittagong", "Shellharbour", "Thirroul", "Wollongong"]
    
  82. 
    
  83.         # Performing distance queries on two projected coordinate systems one
    
  84.         # with units in meters and the other in units of U.S. survey feet.
    
  85.         for dist in tx_dists:
    
  86.             if isinstance(dist, tuple):
    
  87.                 dist1, dist2 = dist
    
  88.             else:
    
  89.                 dist1 = dist2 = dist
    
  90.             qs1 = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist1))
    
  91.             qs2 = SouthTexasCityFt.objects.filter(point__dwithin=(self.stx_pnt, dist2))
    
  92.             for qs in qs1, qs2:
    
  93.                 with self.subTest(dist=dist, qs=qs):
    
  94.                     self.assertEqual(tx_cities, self.get_names(qs))
    
  95. 
    
  96.         # With a complex geometry expression
    
  97.         self.assertFalse(
    
  98.             SouthTexasCity.objects.exclude(point__dwithin=(Union("point", "point"), 0))
    
  99.         )
    
  100. 
    
  101.         # Now performing the `dwithin` queries on a geodetic coordinate system.
    
  102.         for dist in au_dists:
    
  103.             with self.subTest(dist=dist):
    
  104.                 type_error = isinstance(dist, D) and not connection.ops.oracle
    
  105.                 if isinstance(dist, tuple):
    
  106.                     if connection.ops.oracle or connection.ops.spatialite:
    
  107.                         # Result in meters
    
  108.                         dist = dist[1]
    
  109.                     else:
    
  110.                         # Result in units of the field
    
  111.                         dist = dist[0]
    
  112. 
    
  113.                 # Creating the query set.
    
  114.                 qs = AustraliaCity.objects.order_by("name")
    
  115.                 if type_error:
    
  116.                     # A ValueError should be raised on PostGIS when trying to
    
  117.                     # pass Distance objects into a DWithin query using a
    
  118.                     # geodetic field.
    
  119.                     with self.assertRaises(ValueError):
    
  120.                         AustraliaCity.objects.filter(
    
  121.                             point__dwithin=(self.au_pnt, dist)
    
  122.                         ).count()
    
  123.                 else:
    
  124.                     self.assertEqual(
    
  125.                         au_cities,
    
  126.                         self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))),
    
  127.                     )
    
  128. 
    
  129.     @skipUnlessDBFeature("supports_distances_lookups")
    
  130.     def test_distance_lookups(self):
    
  131.         # Retrieving the cities within a 20km 'donut' w/a 7km radius 'hole'
    
  132.         # (thus, Houston and Southside place will be excluded as tested in
    
  133.         # the `test02_dwithin` above).
    
  134.         for model in [SouthTexasCity, SouthTexasCityFt]:
    
  135.             stx_pnt = self.stx_pnt.transform(
    
  136.                 model._meta.get_field("point").srid, clone=True
    
  137.             )
    
  138.             qs = model.objects.filter(point__distance_gte=(stx_pnt, D(km=7))).filter(
    
  139.                 point__distance_lte=(stx_pnt, D(km=20)),
    
  140.             )
    
  141.             cities = self.get_names(qs)
    
  142.             self.assertEqual(cities, ["Bellaire", "Pearland", "West University Place"])
    
  143. 
    
  144.         # Doing a distance query using Polygons instead of a Point.
    
  145.         z = SouthTexasZipcode.objects.get(name="77005")
    
  146.         qs = SouthTexasZipcode.objects.exclude(name="77005").filter(
    
  147.             poly__distance_lte=(z.poly, D(m=275))
    
  148.         )
    
  149.         self.assertEqual(["77025", "77401"], self.get_names(qs))
    
  150.         # If we add a little more distance 77002 should be included.
    
  151.         qs = SouthTexasZipcode.objects.exclude(name="77005").filter(
    
  152.             poly__distance_lte=(z.poly, D(m=300))
    
  153.         )
    
  154.         self.assertEqual(["77002", "77025", "77401"], self.get_names(qs))
    
  155. 
    
  156.     @skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
    
  157.     def test_geodetic_distance_lookups(self):
    
  158.         """
    
  159.         Test distance lookups on geodetic coordinate systems.
    
  160.         """
    
  161.         # Line is from Canberra to Sydney.  Query is for all other cities within
    
  162.         # a 100km of that line (which should exclude only Hobart & Adelaide).
    
  163.         line = GEOSGeometry("LINESTRING(144.9630 -37.8143,151.2607 -33.8870)", 4326)
    
  164.         dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
    
  165.         expected_cities = [
    
  166.             "Batemans Bay",
    
  167.             "Canberra",
    
  168.             "Hillsdale",
    
  169.             "Melbourne",
    
  170.             "Mittagong",
    
  171.             "Shellharbour",
    
  172.             "Sydney",
    
  173.             "Thirroul",
    
  174.             "Wollongong",
    
  175.         ]
    
  176.         if connection.ops.spatialite:
    
  177.             # SpatiaLite is less accurate and returns 102.8km for Batemans Bay.
    
  178.             expected_cities.pop(0)
    
  179.         self.assertEqual(expected_cities, self.get_names(dist_qs))
    
  180. 
    
  181.         msg = "2, 3, or 4-element tuple required for 'distance_lte' lookup."
    
  182.         with self.assertRaisesMessage(ValueError, msg):  # Too many params.
    
  183.             len(
    
  184.                 AustraliaCity.objects.filter(
    
  185.                     point__distance_lte=(
    
  186.                         "POINT(5 23)",
    
  187.                         D(km=100),
    
  188.                         "spheroid",
    
  189.                         "4",
    
  190.                         None,
    
  191.                     )
    
  192.                 )
    
  193.             )
    
  194. 
    
  195.         with self.assertRaisesMessage(ValueError, msg):  # Too few params.
    
  196.             len(AustraliaCity.objects.filter(point__distance_lte=("POINT(5 23)",)))
    
  197. 
    
  198.         msg = "For 4-element tuples the last argument must be the 'spheroid' directive."
    
  199.         with self.assertRaisesMessage(ValueError, msg):
    
  200.             len(
    
  201.                 AustraliaCity.objects.filter(
    
  202.                     point__distance_lte=("POINT(5 23)", D(km=100), "spheroid", "4")
    
  203.                 )
    
  204.             )
    
  205. 
    
  206.         # Getting all cities w/in 550 miles of Hobart.
    
  207.         hobart = AustraliaCity.objects.get(name="Hobart")
    
  208.         qs = AustraliaCity.objects.exclude(name="Hobart").filter(
    
  209.             point__distance_lte=(hobart.point, D(mi=550))
    
  210.         )
    
  211.         cities = self.get_names(qs)
    
  212.         self.assertEqual(cities, ["Batemans Bay", "Canberra", "Melbourne"])
    
  213. 
    
  214.         # Cities that are either really close or really far from Wollongong --
    
  215.         # and using different units of distance.
    
  216.         wollongong = AustraliaCity.objects.get(name="Wollongong")
    
  217.         d1, d2 = D(yd=19500), D(nm=400)  # Yards (~17km) & Nautical miles.
    
  218. 
    
  219.         # Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
    
  220.         gq1 = Q(point__distance_lte=(wollongong.point, d1))
    
  221.         gq2 = Q(point__distance_gte=(wollongong.point, d2))
    
  222.         qs1 = AustraliaCity.objects.exclude(name="Wollongong").filter(gq1 | gq2)
    
  223. 
    
  224.         # Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
    
  225.         # instead (we should get the same results b/c accuracy variance won't matter
    
  226.         # in this test case).
    
  227.         querysets = [qs1]
    
  228.         if connection.features.has_DistanceSpheroid_function:
    
  229.             gq3 = Q(point__distance_lte=(wollongong.point, d1, "spheroid"))
    
  230.             gq4 = Q(point__distance_gte=(wollongong.point, d2, "spheroid"))
    
  231.             qs2 = AustraliaCity.objects.exclude(name="Wollongong").filter(gq3 | gq4)
    
  232.             querysets.append(qs2)
    
  233. 
    
  234.         for qs in querysets:
    
  235.             cities = self.get_names(qs)
    
  236.             self.assertEqual(cities, ["Adelaide", "Hobart", "Shellharbour", "Thirroul"])
    
  237. 
    
  238.     @skipUnlessDBFeature("supports_distances_lookups")
    
  239.     def test_distance_lookups_with_expression_rhs(self):
    
  240.         stx_pnt = self.stx_pnt.transform(
    
  241.             SouthTexasCity._meta.get_field("point").srid, clone=True
    
  242.         )
    
  243.         qs = SouthTexasCity.objects.filter(
    
  244.             point__distance_lte=(stx_pnt, F("radius")),
    
  245.         ).order_by("name")
    
  246.         self.assertEqual(
    
  247.             self.get_names(qs),
    
  248.             [
    
  249.                 "Bellaire",
    
  250.                 "Downtown Houston",
    
  251.                 "Southside Place",
    
  252.                 "West University Place",
    
  253.             ],
    
  254.         )
    
  255. 
    
  256.         # With a combined expression
    
  257.         qs = SouthTexasCity.objects.filter(
    
  258.             point__distance_lte=(stx_pnt, F("radius") * 2),
    
  259.         ).order_by("name")
    
  260.         self.assertEqual(len(qs), 5)
    
  261.         self.assertIn("Pearland", self.get_names(qs))
    
  262. 
    
  263.         # With spheroid param
    
  264.         if connection.features.supports_distance_geodetic:
    
  265.             hobart = AustraliaCity.objects.get(name="Hobart")
    
  266.             AustraliaCity.objects.update(ref_point=hobart.point)
    
  267.             for ref_point in [hobart.point, F("ref_point")]:
    
  268.                 qs = AustraliaCity.objects.filter(
    
  269.                     point__distance_lte=(ref_point, F("radius") * 70, "spheroid"),
    
  270.                 ).order_by("name")
    
  271.                 self.assertEqual(
    
  272.                     self.get_names(qs), ["Canberra", "Hobart", "Melbourne"]
    
  273.                 )
    
  274. 
    
  275.         # With a complex geometry expression
    
  276.         self.assertFalse(
    
  277.             SouthTexasCity.objects.filter(
    
  278.                 point__distance_gt=(Union("point", "point"), 0)
    
  279.             )
    
  280.         )
    
  281.         self.assertEqual(
    
  282.             SouthTexasCity.objects.filter(
    
  283.                 point__distance_lte=(Union("point", "point"), 0)
    
  284.             ).count(),
    
  285.             SouthTexasCity.objects.count(),
    
  286.         )
    
  287. 
    
  288.     @skipUnlessDBFeature("supports_distances_lookups")
    
  289.     def test_distance_annotation_group_by(self):
    
  290.         stx_pnt = self.stx_pnt.transform(
    
  291.             SouthTexasCity._meta.get_field("point").srid,
    
  292.             clone=True,
    
  293.         )
    
  294.         qs = (
    
  295.             SouthTexasCity.objects.annotate(
    
  296.                 relative_distance=Case(
    
  297.                     When(point__distance_lte=(stx_pnt, D(km=20)), then=Value(20)),
    
  298.                     default=Value(100),
    
  299.                     output_field=IntegerField(),
    
  300.                 ),
    
  301.             )
    
  302.             .values("relative_distance")
    
  303.             .annotate(count=Count("pk"))
    
  304.         )
    
  305.         self.assertCountEqual(
    
  306.             qs,
    
  307.             [
    
  308.                 {"relative_distance": 20, "count": 5},
    
  309.                 {"relative_distance": 100, "count": 4},
    
  310.             ],
    
  311.         )
    
  312. 
    
  313.     def test_mysql_geodetic_distance_error(self):
    
  314.         if not connection.ops.mysql:
    
  315.             self.skipTest("This is a MySQL-specific test.")
    
  316.         msg = (
    
  317.             "Only numeric values of degree units are allowed on geodetic distance "
    
  318.             "queries."
    
  319.         )
    
  320.         with self.assertRaisesMessage(ValueError, msg):
    
  321.             AustraliaCity.objects.filter(
    
  322.                 point__distance_lte=(Point(0, 0), D(m=100))
    
  323.             ).exists()
    
  324. 
    
  325.     @skipUnlessDBFeature("supports_dwithin_lookup")
    
  326.     def test_dwithin_subquery(self):
    
  327.         """dwithin lookup in a subquery using OuterRef as a parameter."""
    
  328.         qs = CensusZipcode.objects.annotate(
    
  329.             annotated_value=Exists(
    
  330.                 SouthTexasCity.objects.filter(
    
  331.                     point__dwithin=(OuterRef("poly"), D(m=10)),
    
  332.                 )
    
  333.             )
    
  334.         ).filter(annotated_value=True)
    
  335.         self.assertEqual(self.get_names(qs), ["77002", "77025", "77401"])
    
  336. 
    
  337.     @skipUnlessDBFeature("supports_dwithin_lookup", "supports_dwithin_distance_expr")
    
  338.     def test_dwithin_with_expression_rhs(self):
    
  339.         # LineString of Wollongong and Adelaide coords.
    
  340.         ls = LineString(((150.902, -34.4245), (138.6, -34.9258)), srid=4326)
    
  341.         qs = AustraliaCity.objects.filter(
    
  342.             point__dwithin=(ls, F("allowed_distance")),
    
  343.         ).order_by("name")
    
  344.         self.assertEqual(
    
  345.             self.get_names(qs),
    
  346.             ["Adelaide", "Mittagong", "Shellharbour", "Thirroul", "Wollongong"],
    
  347.         )
    
  348. 
    
  349.     @skipIfDBFeature("supports_dwithin_distance_expr")
    
  350.     def test_dwithin_with_expression_rhs_not_supported(self):
    
  351.         ls = LineString(((150.902, -34.4245), (138.6, -34.9258)), srid=4326)
    
  352.         msg = (
    
  353.             "This backend does not support expressions for specifying "
    
  354.             "distance in the dwithin lookup."
    
  355.         )
    
  356.         with self.assertRaisesMessage(NotSupportedError, msg):
    
  357.             list(
    
  358.                 AustraliaCity.objects.filter(
    
  359.                     point__dwithin=(ls, F("allowed_distance")),
    
  360.                 )
    
  361.             )
    
  362. 
    
  363. 
    
  364. """
    
  365. =============================
    
  366. Distance functions on PostGIS
    
  367. =============================
    
  368. 
    
  369.                                               | Projected Geometry | Lon/lat Geometry | Geography (4326)
    
  370. 
    
  371. ST_Distance(geom1, geom2)                     |    OK (meters)     |   :-( (degrees)  |    OK (meters)
    
  372. 
    
  373. ST_Distance(geom1, geom2, use_spheroid=False) |    N/A             |   N/A            |    OK (meters), less accurate, quick
    
  374. 
    
  375. Distance_Sphere(geom1, geom2)                 |    N/A             |   OK (meters)    |    N/A
    
  376. 
    
  377. Distance_Spheroid(geom1, geom2, spheroid)     |    N/A             |   OK (meters)    |    N/A
    
  378. 
    
  379. ST_Perimeter(geom1)                           |    OK              |   :-( (degrees)  |    OK
    
  380. 
    
  381. 
    
  382. ================================
    
  383. Distance functions on SpatiaLite
    
  384. ================================
    
  385. 
    
  386.                                                 | Projected Geometry | Lon/lat Geometry
    
  387. 
    
  388. ST_Distance(geom1, geom2)                       |    OK (meters)     |      N/A
    
  389. 
    
  390. ST_Distance(geom1, geom2, use_ellipsoid=True)   |    N/A             |      OK (meters)
    
  391. 
    
  392. ST_Distance(geom1, geom2, use_ellipsoid=False)  |    N/A             |      OK (meters), less accurate, quick
    
  393. 
    
  394. Perimeter(geom1)                                |    OK              |      :-( (degrees)
    
  395. 
    
  396. """  # NOQA
    
  397. 
    
  398. 
    
  399. class DistanceFunctionsTests(FuncTestMixin, TestCase):
    
  400.     fixtures = ["initial"]
    
  401. 
    
  402.     @skipUnlessDBFeature("has_Area_function")
    
  403.     def test_area(self):
    
  404.         # Reference queries:
    
  405.         # SELECT ST_Area(poly) FROM distapp_southtexaszipcode;
    
  406.         area_sq_m = [
    
  407.             5437908.90234375,
    
  408.             10183031.4389648,
    
  409.             11254471.0073242,
    
  410.             9881708.91772461,
    
  411.         ]
    
  412.         # Tolerance has to be lower for Oracle
    
  413.         tol = 2
    
  414.         for i, z in enumerate(
    
  415.             SouthTexasZipcode.objects.annotate(area=Area("poly")).order_by("name")
    
  416.         ):
    
  417.             self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
    
  418. 
    
  419.     @skipUnlessDBFeature("has_Distance_function")
    
  420.     def test_distance_simple(self):
    
  421.         """
    
  422.         Test a simple distance query, with projected coordinates and without
    
  423.         transformation.
    
  424.         """
    
  425.         lagrange = GEOSGeometry("POINT(805066.295722839 4231496.29461335)", 32140)
    
  426.         houston = (
    
  427.             SouthTexasCity.objects.annotate(dist=Distance("point", lagrange))
    
  428.             .order_by("id")
    
  429.             .first()
    
  430.         )
    
  431.         tol = 2 if connection.ops.oracle else 5
    
  432.         self.assertAlmostEqual(houston.dist.m, 147075.069813, tol)
    
  433. 
    
  434.     @skipUnlessDBFeature("has_Distance_function", "has_Transform_function")
    
  435.     def test_distance_projected(self):
    
  436.         """
    
  437.         Test the `Distance` function on projected coordinate systems.
    
  438.         """
    
  439.         # The point for La Grange, TX
    
  440.         lagrange = GEOSGeometry("POINT(-96.876369 29.905320)", 4326)
    
  441.         # Reference distances in feet and in meters. Got these values from
    
  442.         # using the provided raw SQL statements.
    
  443.         #  SELECT ST_Distance(
    
  444.         #      point,
    
  445.         #      ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140)
    
  446.         #  )
    
  447.         #  FROM distapp_southtexascity;
    
  448.         m_distances = [
    
  449.             147075.069813,
    
  450.             139630.198056,
    
  451.             140888.552826,
    
  452.             138809.684197,
    
  453.             158309.246259,
    
  454.             212183.594374,
    
  455.             70870.188967,
    
  456.             165337.758878,
    
  457.             139196.085105,
    
  458.         ]
    
  459.         #  SELECT ST_Distance(
    
  460.         #      point,
    
  461.         #      ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)
    
  462.         #  )
    
  463.         #  FROM distapp_southtexascityft;
    
  464.         ft_distances = [
    
  465.             482528.79154625,
    
  466.             458103.408123001,
    
  467.             462231.860397575,
    
  468.             455411.438904354,
    
  469.             519386.252102563,
    
  470.             696139.009211594,
    
  471.             232513.278304279,
    
  472.             542445.630586414,
    
  473.             456679.155883207,
    
  474.         ]
    
  475. 
    
  476.         # Testing using different variations of parameters and using models
    
  477.         # with different projected coordinate systems.
    
  478.         dist1 = SouthTexasCity.objects.annotate(
    
  479.             distance=Distance("point", lagrange)
    
  480.         ).order_by("id")
    
  481.         dist2 = SouthTexasCityFt.objects.annotate(
    
  482.             distance=Distance("point", lagrange)
    
  483.         ).order_by("id")
    
  484.         dist_qs = [dist1, dist2]
    
  485. 
    
  486.         # Ensuring expected distances are returned for each distance queryset.
    
  487.         for qs in dist_qs:
    
  488.             for i, c in enumerate(qs):
    
  489.                 with self.subTest(c=c):
    
  490.                     self.assertAlmostEqual(m_distances[i], c.distance.m, -1)
    
  491.                     self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, -1)
    
  492. 
    
  493.     @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
    
  494.     def test_distance_geodetic(self):
    
  495.         """
    
  496.         Test the `Distance` function on geodetic coordinate systems.
    
  497.         """
    
  498.         # Testing geodetic distance calculation with a non-point geometry
    
  499.         # (a LineString of Wollongong and Shellharbour coords).
    
  500.         ls = LineString(((150.902, -34.4245), (150.87, -34.5789)), srid=4326)
    
  501. 
    
  502.         # Reference query:
    
  503.         #  SELECT ST_distance_sphere(
    
  504.         #      point,
    
  505.         #      ST_GeomFromText('LINESTRING(150.9020 -34.4245,150.8700 -34.5789)', 4326)
    
  506.         #  )
    
  507.         #  FROM distapp_australiacity ORDER BY name;
    
  508.         distances = [
    
  509.             1120954.92533513,
    
  510.             140575.720018241,
    
  511.             640396.662906304,
    
  512.             60580.9693849269,
    
  513.             972807.955955075,
    
  514.             568451.8357838,
    
  515.             40435.4335201384,
    
  516.             0,
    
  517.             68272.3896586844,
    
  518.             12375.0643697706,
    
  519.             0,
    
  520.         ]
    
  521.         qs = AustraliaCity.objects.annotate(distance=Distance("point", ls)).order_by(
    
  522.             "name"
    
  523.         )
    
  524.         for city, distance in zip(qs, distances):
    
  525.             with self.subTest(city=city, distance=distance):
    
  526.                 # Testing equivalence to within a meter (kilometer on SpatiaLite).
    
  527.                 tol = -3 if connection.ops.spatialite else 0
    
  528.                 self.assertAlmostEqual(distance, city.distance.m, tol)
    
  529. 
    
  530.     @skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
    
  531.     def test_distance_geodetic_spheroid(self):
    
  532.         tol = 2 if connection.ops.oracle else 4
    
  533. 
    
  534.         # Got the reference distances using the raw SQL statements:
    
  535.         #  SELECT ST_distance_spheroid(
    
  536.         #      point,
    
  537.         #      ST_GeomFromText('POINT(151.231341 -33.952685)', 4326),
    
  538.         #      'SPHEROID["WGS 84",6378137.0,298.257223563]'
    
  539.         #  )
    
  540.         #  FROM distapp_australiacity WHERE (NOT (id = 11));
    
  541.         #  SELECT ST_distance_sphere(
    
  542.         #      point,
    
  543.         #      ST_GeomFromText('POINT(151.231341 -33.952685)', 4326)
    
  544.         #  )
    
  545.         #  FROM distapp_australiacity WHERE (NOT (id = 11));  st_distance_sphere
    
  546.         spheroid_distances = [
    
  547.             60504.0628957201,
    
  548.             77023.9489850262,
    
  549.             49154.8867574404,
    
  550.             90847.4358768573,
    
  551.             217402.811919332,
    
  552.             709599.234564757,
    
  553.             640011.483550888,
    
  554.             7772.00667991925,
    
  555.             1047861.78619339,
    
  556.             1165126.55236034,
    
  557.         ]
    
  558.         sphere_distances = [
    
  559.             60580.9693849267,
    
  560.             77144.0435286473,
    
  561.             49199.4415344719,
    
  562.             90804.7533823494,
    
  563.             217713.384600405,
    
  564.             709134.127242793,
    
  565.             639828.157159169,
    
  566.             7786.82949717788,
    
  567.             1049204.06569028,
    
  568.             1162623.7238134,
    
  569.         ]
    
  570.         # Testing with spheroid distances first.
    
  571.         hillsdale = AustraliaCity.objects.get(name="Hillsdale")
    
  572.         qs = (
    
  573.             AustraliaCity.objects.exclude(id=hillsdale.id)
    
  574.             .annotate(distance=Distance("point", hillsdale.point, spheroid=True))
    
  575.             .order_by("id")
    
  576.         )
    
  577.         for i, c in enumerate(qs):
    
  578.             with self.subTest(c=c):
    
  579.                 self.assertAlmostEqual(spheroid_distances[i], c.distance.m, tol)
    
  580.         if connection.ops.postgis or connection.ops.spatialite:
    
  581.             # PostGIS uses sphere-only distances by default, testing these as well.
    
  582.             qs = (
    
  583.                 AustraliaCity.objects.exclude(id=hillsdale.id)
    
  584.                 .annotate(distance=Distance("point", hillsdale.point))
    
  585.                 .order_by("id")
    
  586.             )
    
  587.             for i, c in enumerate(qs):
    
  588.                 with self.subTest(c=c):
    
  589.                     self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol)
    
  590. 
    
  591.     @skipIfDBFeature("supports_distance_geodetic")
    
  592.     @skipUnlessDBFeature("has_Distance_function")
    
  593.     def test_distance_function_raw_result(self):
    
  594.         distance = (
    
  595.             Interstate.objects.annotate(
    
  596.                 d=Distance(Point(0, 0, srid=4326), Point(0, 1, srid=4326)),
    
  597.             )
    
  598.             .first()
    
  599.             .d
    
  600.         )
    
  601.         self.assertEqual(distance, 1)
    
  602. 
    
  603.     @skipUnlessDBFeature("has_Distance_function")
    
  604.     def test_distance_function_d_lookup(self):
    
  605.         qs = Interstate.objects.annotate(
    
  606.             d=Distance(Point(0, 0, srid=3857), Point(0, 1, srid=3857)),
    
  607.         ).filter(d=D(m=1))
    
  608.         self.assertTrue(qs.exists())
    
  609. 
    
  610.     @skipUnlessDBFeature("supports_tolerance_parameter")
    
  611.     def test_distance_function_tolerance_escaping(self):
    
  612.         qs = (
    
  613.             Interstate.objects.annotate(
    
  614.                 d=Distance(
    
  615.                     Point(500, 500, srid=3857),
    
  616.                     Point(0, 0, srid=3857),
    
  617.                     tolerance="0.05) = 1 OR 1=1 OR (1+1",
    
  618.                 ),
    
  619.             )
    
  620.             .filter(d=D(m=1))
    
  621.             .values("pk")
    
  622.         )
    
  623.         msg = "The tolerance parameter has the wrong type"
    
  624.         with self.assertRaisesMessage(TypeError, msg):
    
  625.             qs.exists()
    
  626. 
    
  627.     @skipUnlessDBFeature("supports_tolerance_parameter")
    
  628.     def test_distance_function_tolerance(self):
    
  629.         # Tolerance is greater than distance.
    
  630.         qs = (
    
  631.             Interstate.objects.annotate(
    
  632.                 d=Distance(
    
  633.                     Point(0, 0, srid=3857),
    
  634.                     Point(1, 1, srid=3857),
    
  635.                     tolerance=1.5,
    
  636.                 ),
    
  637.             )
    
  638.             .filter(d=0)
    
  639.             .values("pk")
    
  640.         )
    
  641.         self.assertIs(qs.exists(), True)
    
  642. 
    
  643.     @skipIfDBFeature("supports_distance_geodetic")
    
  644.     @skipUnlessDBFeature("has_Distance_function")
    
  645.     def test_distance_function_raw_result_d_lookup(self):
    
  646.         qs = Interstate.objects.annotate(
    
  647.             d=Distance(Point(0, 0, srid=4326), Point(0, 1, srid=4326)),
    
  648.         ).filter(d=D(m=1))
    
  649.         msg = "Distance measure is supplied, but units are unknown for result."
    
  650.         with self.assertRaisesMessage(ValueError, msg):
    
  651.             list(qs)
    
  652. 
    
  653.     @skipUnlessDBFeature("has_Distance_function", "has_Transform_function")
    
  654.     def test_distance_transform(self):
    
  655.         """
    
  656.         Test the `Distance` function used with `Transform` on a geographic field.
    
  657.         """
    
  658.         # We'll be using a Polygon (created by buffering the centroid
    
  659.         # of 77005 to 100m) -- which aren't allowed in geographic distance
    
  660.         # queries normally, however our field has been transformed to
    
  661.         # a non-geographic system.
    
  662.         z = SouthTexasZipcode.objects.get(name="77005")
    
  663. 
    
  664.         # Reference query:
    
  665.         # SELECT ST_Distance(ST_Transform("distapp_censuszipcode"."poly", 32140),
    
  666.         #   ST_GeomFromText('<buffer_wkt>', 32140))
    
  667.         # FROM "distapp_censuszipcode";
    
  668.         dists_m = [3553.30384972258, 1243.18391525602, 2186.15439472242]
    
  669. 
    
  670.         # Having our buffer in the SRID of the transformation and of the field
    
  671.         # -- should get the same results. The first buffer has no need for
    
  672.         # transformation SQL because it is the same SRID as what was given
    
  673.         # to `transform()`.  The second buffer will need to be transformed,
    
  674.         # however.
    
  675.         buf1 = z.poly.centroid.buffer(100)
    
  676.         buf2 = buf1.transform(4269, clone=True)
    
  677.         ref_zips = ["77002", "77025", "77401"]
    
  678. 
    
  679.         for buf in [buf1, buf2]:
    
  680.             qs = (
    
  681.                 CensusZipcode.objects.exclude(name="77005")
    
  682.                 .annotate(distance=Distance(Transform("poly", 32140), buf))
    
  683.                 .order_by("name")
    
  684.             )
    
  685.             self.assertEqual(ref_zips, sorted(c.name for c in qs))
    
  686.             for i, z in enumerate(qs):
    
  687.                 self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
    
  688. 
    
  689.     @skipUnlessDBFeature("has_Distance_function")
    
  690.     def test_distance_order_by(self):
    
  691.         qs = (
    
  692.             SouthTexasCity.objects.annotate(
    
  693.                 distance=Distance("point", Point(3, 3, srid=32140))
    
  694.             )
    
  695.             .order_by("distance")
    
  696.             .values_list("name", flat=True)
    
  697.             .filter(name__in=("San Antonio", "Pearland"))
    
  698.         )
    
  699.         self.assertSequenceEqual(qs, ["San Antonio", "Pearland"])
    
  700. 
    
  701.     @skipUnlessDBFeature("has_Length_function")
    
  702.     def test_length(self):
    
  703.         """
    
  704.         Test the `Length` function.
    
  705.         """
    
  706.         # Reference query (should use `length_spheroid`).
    
  707.         #  SELECT ST_length_spheroid(
    
  708.         #      ST_GeomFromText('<wkt>', 4326)
    
  709.         #      'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]'
    
  710.         #  );
    
  711.         len_m1 = 473504.769553813
    
  712.         len_m2 = 4617.668
    
  713. 
    
  714.         if connection.features.supports_length_geodetic:
    
  715.             qs = Interstate.objects.annotate(length=Length("path"))
    
  716.             tol = 2 if connection.ops.oracle else 3
    
  717.             self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
    
  718.             # TODO: test with spheroid argument (True and False)
    
  719.         else:
    
  720.             # Does not support geodetic coordinate systems.
    
  721.             with self.assertRaises(NotSupportedError):
    
  722.                 list(Interstate.objects.annotate(length=Length("path")))
    
  723. 
    
  724.         # Now doing length on a projected coordinate system.
    
  725.         i10 = SouthTexasInterstate.objects.annotate(length=Length("path")).get(
    
  726.             name="I-10"
    
  727.         )
    
  728.         self.assertAlmostEqual(len_m2, i10.length.m, 2)
    
  729.         self.assertTrue(
    
  730.             SouthTexasInterstate.objects.annotate(length=Length("path"))
    
  731.             .filter(length__gt=4000)
    
  732.             .exists()
    
  733.         )
    
  734.         # Length with an explicit geometry value.
    
  735.         qs = Interstate.objects.annotate(length=Length(i10.path))
    
  736.         self.assertAlmostEqual(qs.first().length.m, len_m2, 2)
    
  737. 
    
  738.     @skipUnlessDBFeature("has_Perimeter_function")
    
  739.     def test_perimeter(self):
    
  740.         """
    
  741.         Test the `Perimeter` function.
    
  742.         """
    
  743.         # Reference query:
    
  744.         #  SELECT ST_Perimeter(distapp_southtexaszipcode.poly)
    
  745.         #  FROM distapp_southtexaszipcode;
    
  746.         perim_m = [
    
  747.             18404.3550889361,
    
  748.             15627.2108551001,
    
  749.             20632.5588368978,
    
  750.             17094.5996143697,
    
  751.         ]
    
  752.         tol = 2 if connection.ops.oracle else 7
    
  753.         qs = SouthTexasZipcode.objects.annotate(perimeter=Perimeter("poly")).order_by(
    
  754.             "name"
    
  755.         )
    
  756.         for i, z in enumerate(qs):
    
  757.             self.assertAlmostEqual(perim_m[i], z.perimeter.m, tol)
    
  758. 
    
  759.         # Running on points; should return 0.
    
  760.         qs = SouthTexasCity.objects.annotate(perim=Perimeter("point"))
    
  761.         for city in qs:
    
  762.             self.assertEqual(0, city.perim.m)
    
  763. 
    
  764.     @skipUnlessDBFeature("has_Perimeter_function")
    
  765.     def test_perimeter_geodetic(self):
    
  766.         # Currently only Oracle supports calculating the perimeter on geodetic
    
  767.         # geometries (without being transformed).
    
  768.         qs1 = CensusZipcode.objects.annotate(perim=Perimeter("poly"))
    
  769.         if connection.features.supports_perimeter_geodetic:
    
  770.             self.assertAlmostEqual(qs1[0].perim.m, 18406.3818954314, 3)
    
  771.         else:
    
  772.             with self.assertRaises(NotSupportedError):
    
  773.                 list(qs1)
    
  774.         # But should work fine when transformed to projected coordinates
    
  775.         qs2 = CensusZipcode.objects.annotate(
    
  776.             perim=Perimeter(Transform("poly", 32140))
    
  777.         ).filter(name="77002")
    
  778.         self.assertAlmostEqual(qs2[0].perim.m, 18404.355, 3)
    
  779. 
    
  780.     @skipUnlessDBFeature(
    
  781.         "supports_null_geometries", "has_Area_function", "has_Distance_function"
    
  782.     )
    
  783.     def test_measurement_null_fields(self):
    
  784.         """
    
  785.         Test the measurement functions on fields with NULL values.
    
  786.         """
    
  787.         # Creating SouthTexasZipcode w/NULL value.
    
  788.         SouthTexasZipcode.objects.create(name="78212")
    
  789.         # Performing distance/area queries against the NULL PolygonField,
    
  790.         # and ensuring the result of the operations is None.
    
  791.         htown = SouthTexasCity.objects.get(name="Downtown Houston")
    
  792.         z = SouthTexasZipcode.objects.annotate(
    
  793.             distance=Distance("poly", htown.point), area=Area("poly")
    
  794.         ).get(name="78212")
    
  795.         self.assertIsNone(z.distance)
    
  796.         self.assertIsNone(z.area)