testing functions from djangocontribgisdbmodelsfunctions py areadistancelengthperimeter are tested in distapptests please keep the tests in function s alphabetic order precision argument should only be an integer reference queries and values select stasgeojsongeoappcity point 8 0 from geoappcity where geoappcity name pueblo select stasgeojsongeoappcity point 8 2 from geoappcity where geoappcity name houston this time we want to include the crs by using the crs keyword select stasgeojsongeoappcity point 8 1 from geoappcity where geoappcity name houston this time we include the bounding box by using the bbox keyword select stasgeojsongeoappcity point 5 3 from geoappcity where geoappcity name chicago finally we set every available keyword mariadb doesn t limit the number of decimals in bbox give a second chance with different coords rounding geojson without crs should throw a typeerror when trying to obtain gml from a nongeometry field no precision parameter for oracle should throw a typeerror when trying to obtain kml from a nongeometry field ensuring the kml is as expected select assvggeoappcity point 0 8 from geoappcity where name pueblo even though relative only one point so it s practically the same except for the c letter prefix on the x y values wkb is either xdr or ndr encoded returns the azimuth in radians returns none if the two points are coincident numseg is the number of segments per quarter circle by default numseg48 oracle does something screwy with the texas geometry testing with mixed srid country has default 4326 geom point556597 4 2632018 6 srid3857 spherical mercator qs country objects annotatedifferencefunctions differencempoly geom oracle does something screwy with the texas geometry if connection ops oracle qs qs excludenametexas for c in qs self asserttruec mpoly differencegeom equalsc difference skipunlessdbfeaturehasenvelopefunction def testenvelopeself countries country objects annotateenvelopefunctions envelopempoly for country in countries self asserttruecountry envelope equalscountry mpoly envelope skipunlessdbfeaturehasforcepolygoncwfunction def testforcepolygoncwself rings 0 0 5 0 0 5 0 0 1 1 1 3 3 1 1 1 rhrrings 0 0 0 5 5 0 0 0 1 1 3 1 1 3 1 1 state objects createnamefoo polypolygonrings st state objects annotate forcepolygoncwfunctions forcepolygoncwpoly getnamefoo self assertequalrhrrings st forcepolygoncw coords skipunlessdbfeaturehasfromwkbfunction def testfromwkbself g point56 811078 60 608647 g2 city objects valueslist functions fromwkbvalueg wkb tobytes flattrue 0 self assertisg equalsexactg2 0 00001 true skipunlessdbfeaturehasfromwktfunction def testfromwktself g point56 811078 60 608647 g2 city objects valueslist functions fromwktvalueg wkt flattrue 0 self assertisg equalsexactg2 0 00001 true skipunlessdbfeaturehasgeohashfunction def testgeohashself reference query select stgeohashpoint from geoappcity where name houston select stgeohashpoint 5 from geoappcity where name houston refhash 9vk1mfq8jx0c8e0386z6 h1 city objects annotategeohashfunctions geohashpoint get namehouston h2 city objects annotategeohashfunctions geohashpoint precision5 get namehouston self assertequalrefhash h1 geohash lenrefhash self assertequalrefhash 5 h2 geohash skipunlessdbfeaturehasgeometrydistancefunction def testgeometrydistanceself point point90 40 srid4326 qs city objects annotate distancefunctions geometrydistancepoint point orderbydistance distances 2 99091995527296 5 33507274054713 9 33852187483721 9 91769193646233 11 556465744884 14 713098433352 34 3635252198568 276 987855073372 for city expecteddistance in zipqs distances with self subtestcitycity self assertalmostequalcity distance expecteddistance skipunlessdbfeaturehasintersectionfunction def testintersectionself geom point5 23 srid4326 qs country objects annotateinterfunctions intersectionmpoly geom for c in qs if connection features emptyintersectionreturnsnone self assertisnonec inter else self assertisc inter empty true skipunlessdbfeaturesupportsemptygeometries hasisemptyfunction def testisemptyself empty city objects createnamenowhere pointpointsrid4326 city objects createnamesomewhere pointpoint6 825 47 1 srid4326 self assertsequenceequal city objects annotateisemptyfunctions isemptypoint filter isemptytrue empty self assertsequenceequalcity objects filterpointisemptytrue empty skipunlessdbfeaturehasisvalidfunction def testisvalidself validgeom fromstrpolygon0 0 0 1 1 1 1 0 0 0 invalidgeom fromstrpolygon0 0 0 1 1 1 1 0 1 1 1 0 0 0 state objects createnamevalid polyvalidgeom state objects createnameinvalid polyinvalidgeom valid state objects filternamevalid annotateisvalidfunctions isvalidpoly first invalid state objects filternameinvalid annotateisvalidfunctions isvalidpoly first self assertisvalid isvalid true self assertisinvalid isvalid false skipunlessdbfeaturehasareafunction def testareawithregularaggregateself create projected country objects for this test to work on all backends for c in country objects all countrywebmercator objects create namec name mpolyc mpoly transform3857 clonetrue test in projected coordinate system qs countrywebmercator objects annotateareasumsumfunctions areampoly some backends e g oracle cannot group by multipolygon values so defer such fields in the aggregation query for c in qs defermpoly result c areasum if the result is a measure object get value if isinstanceresult area result result sqm self assertalmostequalresult c mpoly area c mpoly area 0 skipunlessdbfeaturehasareafunction def testarealookupsself create projected countries so the test works on all backends countrywebmercator objects bulkcreate countrywebmercatornamec name mpolyc mpoly transform3857 clonetrue for c in country objects all qs countrywebmercator objects annotateareafunctions areampoly self assertequal qs getarealtareasqkm500000 countrywebmercator objects getnamenew zealand with self assertraisesmessage valueerror areafield only accepts area measurement objects qs getarealt500000 skipunlessdbfeaturehasclosestpointfunction def testclosestpointself qs country objects annotate closestpointfunctions closestpointmpoly functions centroidmpoly for country in qs self assertisinstancecountry closestpoint point self assertequal country mpoly intersectioncountry closestpoint country closestpoint skipunlessdbfeaturehaslinelocatepointfunction def testlinelocatepointself posexpr functions linelocatepoint linestring0 0 0 3 srid4326 point0 1 srid4326 self assertalmostequal state objects annotateposposexpr first pos 0 3333333 skipunlessdbfeaturehasmakevalidfunction def testmakevalidself invalidgeom fromstrpolygon0 0 0 1 1 1 1 0 1 1 1 0 0 0 state objects createnameinvalid polyinvalidgeom invalid state objects filternameinvalid annotaterepairedfunctions makevalidpoly first self assertisinvalid repaired valid true self asserttrue invalid repaired equals fromstrpolygon0 0 0 1 1 1 1 0 0 0 sridinvalid poly srid skipunlessdbfeaturehasmakevalidfunction def testmakevalidmultipolygonself invalidgeom fromstr polygon0 0 0 1 1 1 1 0 0 0 10 0 10 1 11 1 11 0 10 0 state objects createnameinvalid polyinvalidgeom invalid state objects filternameinvalid annotate repairedfunctions makevalidpoly get self assertisinvalid repaired valid true self asserttrue invalid repaired equals fromstr multipolygon 0 0 0 1 1 1 1 0 0 0 10 0 10 1 11 1 11 0 10 0 sridinvalid poly srid self assertequalleninvalid repaired 2 skipunlessdbfeaturehasmakevalidfunction def testmakevalidoutputfieldself outputfield is geometryfield instance because different geometry types can be returned outputfield functions makevalid valuepolygon polygonfieldsrid42 outputfield self assertisoutputfield class geometryfield self assertequaloutputfield srid 42 skipunlessdbfeaturehasmemsizefunction def testmemsizeself ptown city objects annotatesizefunctions memsizepoint get namepueblo exact value depends on database and version self asserttrue20 ptown size 105 skipunlessdbfeaturehasnumgeomfunction def testnumgeomself both countries only have two geometries for c in country objects annotatenumgeomfunctions numgeometriesmpoly self assertequal2 c numgeom qs city objects filterpointisnullfalse annotate numgeomfunctions numgeometriespoint for city in qs the results for the number of geometries on noncollections depends on the database if connection ops mysql or connection ops mariadb self assertisnonecity numgeom else self assertequal1 city numgeom skipunlessdbfeaturehasnumpointfunction def testnumpointsself coords 95 363151 29 763374 95 448601 29 713803 track objects createnamefoo linelinestringcoords qs track objects annotatenumpointsfunctions numpointsline self assertequalqs first numpoints 2 mpolyqs country objects annotatenumpointsfunctions numpointsmpoly if not connection features supportsnumpointspoly for c in mpolyqs self assertisnonec numpoints return for c in mpolyqs self assertequalc mpoly numpoints c numpoints for c in city objects annotatenumpointsfunctions numpointspoint self assertequalc numpoints 1 skipunlessdbfeaturehaspointonsurfacefunction def testpointonsurfaceself qs country objects annotate pointonsurfacefunctions pointonsurfacempoly for country in qs self asserttruecountry mpoly intersectioncountry pointonsurface skipunlessdbfeaturehasreversefunction def testreversegeomself coords 95 363151 29 763374 95 448601 29 713803 track objects createnamefoo linelinestringcoords track track objects annotatereversegeomfunctions reverseline get namefoo coords reverse self assertequaltuplecoords track reversegeom coords skipunlessdbfeaturehasscalefunction def testscaleself xfac yfac 2 3 tol 5 the low precision tolerance is for spatialite qs country objects annotatescaledfunctions scalempoly xfac yfac for country in qs for p1 p2 in zipcountry mpoly country scaled for r1 r2 in zipp1 p2 for c1 c2 in zipr1 coords r2 coords self assertalmostequalc10 xfac c20 tol self assertalmostequalc11 yfac c21 tol test floatdecimal values qs country objects annotate scaledfunctions scalempoly 1 5 decimal2 5 self assertgreaterqs0 scaled area qs0 mpoly area skipunlessdbfeaturehassnaptogridfunction def testsnaptogridself let s try and break snaptogrid with bad combinations of arguments for badargs in range3 range5 with self assertraisesvalueerror country objects annotatesnapfunctions snaptogridmpoly badargs for badargs in 1 0 1 0 none tuplemapstr range4 with self assertraisestypeerror country objects annotatesnapfunctions snaptogridmpoly badargs boundary for san marino courtesy of bjorn sandvik of thematicmapping org from the world borders dataset he provides wkt multipolygon12 41580 43 95795 12 45055 43 97972 12 45389 43 98167 12 46250 43 98472 12 47167 43 98694 12 49278 43 98917 12 50555 43 98861 12 51000 43 98694 12 51028 43 98277 12 51167 43 94333 12 51056 43 93916 12 49639 43 92333 12 49500 43 91472 12 48778 43 90583 12 47444 43 89722 12 46472 43 89555 12 45917 43 89611 12 41639 43 90472 12 41222 43 90610 12 40782 43 91366 12 40389 43 92667 12 40500 43 94833 12 40889 43 95499 12 41580 43 95795 country objects createnamesan marino mpolyfromstrwkt because floatingpoint arithmetic isn t exact we set a tolerance to pass into geos equalsexact tol 0 000000001 select astextstsnaptogridgeoappcountry mpoly 0 1 from geoappcountry where geoappcountry name san marino ref fromstrmultipolygon12 4 44 12 5 44 12 5 43 9 12 4 43 9 12 4 44 self asserttrue ref equalsexact country objects annotatesnapfunctions snaptogridmpoly 0 1 getnamesan marino snap tol select astextstsnaptogridgeoappcountry mpoly 0 05 0 23 from geoappcountry where geoappcountry name san marino ref fromstr multipolygon12 4 43 93 12 45 43 93 12 5 43 93 12 45 43 93 12 4 43 93 self asserttrue ref equalsexact country objects annotatesnapfunctions snaptogridmpoly 0 05 0 23 getnamesan marino snap tol select astextstsnaptogridgeoappcountry mpoly 0 5 0 17 0 05 0 23 from geoappcountry where geoappcountry name san marino ref fromstr multipolygon12 4 43 87 12 45 43 87 12 45 44 1 12 5 44 1 12 5 43 87 12 45 43 87 12 4 43 87 self asserttrue ref equalsexact country objects annotate snapfunctions snaptogridmpoly 0 05 0 23 0 5 0 17 getnamesan marino snap tol skipunlessdbfeaturehassymdifferencefunction def testsymdifferenceself geom point5 23 srid4326 qs country objects annotate symdifferencefunctions symdifferencempoly geom oracle does something screwy with the texas geometry if connection ops oracle qs qs excludenametexas for country in qs self asserttrue country mpoly symdifferencegeom equalscountry symdifference skipunlessdbfeaturehastransformfunction def testtransformself pretransformed points for houston and pueblo ptown fromstrpoint992363 390841912 481455 395105533 srid2774 asserting the result of the transform operation with the values in the pretransformed points h city objects annotateptfunctions transformpoint ptown srid get namepueblo self assertequal2774 h pt srid precision is low due to version variations in proj and gdal self assertlessptown x h pt x 1 self assertlessptown y h pt y 1 skipunlessdbfeaturehastranslatefunction def testtranslateself xfac yfac 5 23 qs country objects annotate translatedfunctions translatempoly xfac yfac for c in qs for p1 p2 in zipc mpoly c translated for r1 r2 in zipp1 p2 for c1 c2 in zipr1 coords r2 coords the low precision is for spatialite self assertalmostequalc10 xfac c20 5 self assertalmostequalc11 yfac c21 5 some combined function tests skipunlessdbfeature hasdifferencefunction hasintersectionfunction hassymdifferencefunction hasunionfunction def testdiffintersectionunionself geom point5 23 srid4326 qs country objects annotate differencefunctions differencempoly geom symdifferencefunctions symdifferencempoly geom unionfunctions unionmpoly geom intersectionfunctions intersectionmpoly geom if connection ops oracle should be able to execute the queries however they won t be the same as geos because oracle doesn t use geos internally like postgis or spatialite return for c in qs self asserttruec mpoly differencegeom equalsc difference if connection features emptyintersectionreturnsnone self assertisnonec intersection else self assertisc intersection empty true self asserttruec mpoly symdifferencegeom equalsc symdifference self asserttruec mpoly uniongeom equalsc union skipunlessdbfeaturehasunionfunction def testunionself the result srid depends on the order of parameters geom point61 42915 55 15402 srid4326 geom3857 geom transform3857 clonetrue tol 0 001 for city in city objects annotateunionfunctions unionpoint geom3857 expected city point geom self asserttruecity union equalsexactexpected tol self assertequalcity union srid 4326 for city in city objects annotateunionfunctions uniongeom3857 point expected geom3857 city point transform3857 clonetrue self asserttrueexpected equalsexactcity union tol self assertequalcity union srid 3857 def testargumentvalidationself with self assertraisesmessage valueerror srid is required for all geometries city objects annotategeofunctions geofuncpoint1 1 msg geofunc function requires a geometryfield in position 1 got charfield with self assertraisesmessagetypeerror msg city objects annotategeofunctions geofuncname msg geofunc function requires a geometric argument in position 1 with self assertraisesmessagetypeerror msg city objects annotateunionfunctions geofunc1 point get namedallas testing functions from django contrib gis db models functions py area distance length perimeter are tested in distapp tests please keep the tests in function s alphabetic order precision argument should only be an integer reference queries and values select st_asgeojson geoapp_city point 8 0 from geoapp_city where geoapp_city name pueblo select st_asgeojson geoapp_city point 8 2 from geoapp_city where geoapp_city name houston this time we want to include the crs by using the crs keyword select st_asgeojson geoapp_city point 8 1 from geoapp_city where geoapp_city name houston this time we include the bounding box by using the bbox keyword select st_asgeojson geoapp_city point 5 3 from geoapp_city where geoapp_city name chicago finally we set every available keyword mariadb doesn t limit the number of decimals in bbox give a second chance with different coords rounding geojson without crs should throw a typeerror when trying to obtain gml from a non geometry field no precision parameter for oracle should throw a typeerror when trying to obtain kml from a non geometry field ensuring the kml is as expected select assvg geoapp_city point 0 8 from geoapp_city where name pueblo even though relative only one point so it s practically the same except for the c letter prefix on the x y values wkb is either xdr or ndr encoded returns the azimuth in radians returns none if the two points are coincident num_seg is the number of segments per quarter circle by default num_seg 48 oracle does something screwy with the texas geometry testing with mixed srid country has default 4326 spherical mercator oracle does something screwy with the texas geometry reference query select st_geohash point from geoapp_city where name houston select st_geohash point 5 from geoapp_city where name houston create projected country objects for this test to work on all backends test in projected coordinate system some backends e g oracle cannot group by multipolygon values so defer such fields in the aggregation query if the result is a measure object get value create projected countries so the test works on all backends output_field is geometryfield instance because different geometry types can be returned exact value depends on database and version both countries only have two geometries the results for the number of geometries on non collections depends on the database the low precision tolerance is for spatialite test float decimal values let s try and break snap_to_grid with bad combinations of arguments boundary for san marino courtesy of bjorn sandvik of thematicmapping org from the world borders dataset he provides because floating point arithmetic isn t exact we set a tolerance to pass into geos equals_exact select astext st_snaptogrid geoapp_country mpoly 0 1 from geoapp_country where geoapp_country name san marino select astext st_snaptogrid geoapp_country mpoly 0 05 0 23 from geoapp_country where geoapp_country name san marino select astext st_snaptogrid geoapp_country mpoly 0 5 0 17 0 05 0 23 from geoapp_country where geoapp_country name san marino oracle does something screwy with the texas geometry pre transformed points for houston and pueblo asserting the result of the transform operation with the values in the pre transformed points precision is low due to version variations in proj and gdal the low precision is for spatialite some combined function tests should be able to execute the queries however they won t be the same as geos because oracle doesn t use geos internally like postgis or spatialite union with all combinations of geometries geometry fields the result srid depends on the order of parameters
import json
import math
import re
from decimal import Decimal

from django.contrib.gis.db.models import GeometryField, PolygonField, functions
from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon, fromstr
from django.contrib.gis.measure import Area
from django.db import NotSupportedError, connection
from django.db.models import IntegerField, Sum, Value
from django.test import TestCase, skipUnlessDBFeature

from ..utils import FuncTestMixin
from .models import City, Country, CountryWebMercator, ManyPointModel, State, Track


class GISFunctionsTests(FuncTestMixin, TestCase):
    

    fixtures = ["initial"]

    def test_asgeojson(self):
        if not connection.features.has_AsGeoJSON_function:
            with self.assertRaises(NotSupportedError):
                list(Country.objects.annotate(json=functions.AsGeoJSON("mpoly")))
            return

        pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
        houston_json = json.loads(
            '{"type":"Point","crs":{"type":"name","properties":'
            '{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
        )
        victoria_json = json.loads(
            '{"type":"Point",'
            '"bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],'
            '"coordinates":[-123.305196,48.462611]}'
        )
        chicago_json = json.loads(
            '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
            '"bbox":[-87.65018,41.85039,-87.65018,41.85039],'
            '"coordinates":[-87.65018,41.85039]}'
        )
        if "crs" in connection.features.unsupported_geojson_options:
            del houston_json["crs"]
            del chicago_json["crs"]
        if "bbox" in connection.features.unsupported_geojson_options:
            del chicago_json["bbox"]
            del victoria_json["bbox"]
        if "precision" in connection.features.unsupported_geojson_options:
            chicago_json["coordinates"] = [-87.650175, 41.850385]

        
        with self.assertRaises(TypeError):
            City.objects.annotate(geojson=functions.AsGeoJSON("point", precision="foo"))

        
        
        
        self.assertJSONEqual(
            pueblo_json,
            City.objects.annotate(geojson=functions.AsGeoJSON("point"))
            .get(name="Pueblo")
            .geojson,
        )

        
        
        
        self.assertJSONEqual(
            City.objects.annotate(json=functions.AsGeoJSON("point", crs=True))
            .get(name="Houston")
            .json,
            houston_json,
        )

        
        
        
        self.assertJSONEqual(
            City.objects.annotate(geojson=functions.AsGeoJSON("point", bbox=True))
            .get(name="Victoria")
            .geojson,
            victoria_json,
        )

        
        
        
        
        if connection.ops.mariadb:
            chicago_json["bbox"] = [-87.650175, 41.850385, -87.650175, 41.850385]
        try:
            self.assertJSONEqual(
                City.objects.annotate(
                    geojson=functions.AsGeoJSON(
                        "point", bbox=True, crs=True, precision=5
                    )
                )
                .get(name="Chicago")
                .geojson,
                chicago_json,
            )
        except AssertionError:
            
            chicago_json["coordinates"][1] = 41.85038
            self.assertJSONEqual(
                City.objects.annotate(
                    geojson=functions.AsGeoJSON(
                        "point", bbox=True, crs=True, precision=5
                    )
                )
                .get(name="Chicago")
                .geojson,
                chicago_json,
            )

    @skipUnlessDBFeature("has_AsGeoJSON_function")
    def test_asgeojson_option_0(self):
        p1 = Point(1, 1, srid=4326)
        p2 = Point(-87.65018, 41.85039, srid=4326)
        obj = ManyPointModel.objects.create(
            point1=p1,
            point2=p2,
            point3=p2.transform(3857, clone=True),
        )
        self.assertJSONEqual(
            ManyPointModel.objects.annotate(geojson=functions.AsGeoJSON("point3"))
            .get(pk=obj.pk)
            .geojson,
            
            json.loads(
                '{"type":"Point","coordinates":[-9757173.40553877, 5138594.87034608]}'
            ),
        )

    @skipUnlessDBFeature("has_AsGML_function")
    def test_asgml(self):
        
        
        qs = City.objects.all()
        with self.assertRaises(TypeError):
            qs.annotate(gml=functions.AsGML("name"))
        ptown = City.objects.annotate(gml=functions.AsGML("point", precision=9)).get(
            name="Pueblo"
        )

        if connection.ops.oracle:
            
            gml_regex = re.compile(
                r'^<gml:Point srsName="EPSG:4326" '
                r'xmlns:gml="http://www.opengis.net/gml">'
                r'<gml:coordinates decimal="\." cs="," ts=" ">'
                r"-104.60925\d+,38.25500\d+ "
                r"</gml:coordinates></gml:Point>"
            )
        else:
            gml_regex = re.compile(
                r'^<gml:Point srsName="(urn:ogc:def:crs:)?EPSG:4326"><gml:coordinates>'
                r"-104\.60925\d+,38\.255001</gml:coordinates></gml:Point>"
            )
        self.assertTrue(gml_regex.match(ptown.gml))
        self.assertIn(
            '<gml:pos srsDimension="2">',
            City.objects.annotate(gml=functions.AsGML("point", version=3))
            .get(name="Pueblo")
            .gml,
        )

    @skipUnlessDBFeature("has_AsKML_function")
    def test_askml(self):
        
        
        with self.assertRaises(TypeError):
            City.objects.annotate(kml=functions.AsKML("name"))

        
        ptown = City.objects.annotate(kml=functions.AsKML("point", precision=9)).get(
            name="Pueblo"
        )
        self.assertEqual(
            "<Point><coordinates>-104.609252,38.255001</coordinates></Point>", ptown.kml
        )

    @skipUnlessDBFeature("has_AsSVG_function")
    def test_assvg(self):
        with self.assertRaises(TypeError):
            City.objects.annotate(svg=functions.AsSVG("point", precision="foo"))
        
        svg1 = 'cx="-104.609252" cy="-38.255001"'
        
        
        svg2 = svg1.replace("c", "")
        self.assertEqual(
            svg1,
            City.objects.annotate(svg=functions.AsSVG("point")).get(name="Pueblo").svg,
        )
        self.assertEqual(
            svg2,
            City.objects.annotate(svg=functions.AsSVG("point", relative=5))
            .get(name="Pueblo")
            .svg,
        )

    @skipUnlessDBFeature("has_AsWKB_function")
    def test_aswkb(self):
        wkb = (
            City.objects.annotate(
                wkb=functions.AsWKB(Point(1, 2, srid=4326)),
            )
            .first()
            .wkb
        )
        
        self.assertIn(
            bytes(wkb),
            (
                b"\x00\x00\x00\x00\x01?\xf0\x00\x00\x00\x00\x00\x00@\x00\x00"
                b"\x00\x00\x00\x00\x00",
                b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00"
                b"\x00\x00\x00\x00\x00@",
            ),
        )

    @skipUnlessDBFeature("has_AsWKT_function")
    def test_aswkt(self):
        wkt = (
            City.objects.annotate(
                wkt=functions.AsWKT(Point(1, 2, srid=4326)),
            )
            .first()
            .wkt
        )
        self.assertEqual(
            wkt, "POINT (1.0 2.0)" if connection.ops.oracle else "POINT(1 2)"
        )

    @skipUnlessDBFeature("has_Azimuth_function")
    def test_azimuth(self):
        
        azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(1, 1, srid=4326))
        self.assertAlmostEqual(
            City.objects.annotate(azimuth=azimuth_expr).first().azimuth,
            math.pi / 4,
            places=2,
        )
        
        azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(0, 0, srid=4326))
        self.assertIsNone(City.objects.annotate(azimuth=azimuth_expr).first().azimuth)

    @skipUnlessDBFeature("has_BoundingCircle_function")
    def test_bounding_circle(self):
        def circle_num_points(num_seg):
            
            return (4 * num_seg) + 1

        expected_areas = (169, 136) if connection.ops.postgis else (171, 126)
        qs = Country.objects.annotate(
            circle=functions.BoundingCircle("mpoly")
        ).order_by("name")
        self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
        self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
        if connection.ops.postgis:
            
            self.assertEqual(qs[0].circle.num_points, circle_num_points(48))
            self.assertEqual(qs[1].circle.num_points, circle_num_points(48))

        tests = [12, Value(12, output_field=IntegerField())]
        for num_seq in tests:
            with self.subTest(num_seq=num_seq):
                qs = Country.objects.annotate(
                    circle=functions.BoundingCircle("mpoly", num_seg=num_seq),
                ).order_by("name")
                if connection.ops.postgis:
                    self.assertGreater(qs[0].circle.area, 168.4, 0)
                    self.assertLess(qs[0].circle.area, 169.5, 0)
                    self.assertAlmostEqual(qs[1].circle.area, 136, 0)
                    self.assertEqual(qs[0].circle.num_points, circle_num_points(12))
                    self.assertEqual(qs[1].circle.num_points, circle_num_points(12))
                else:
                    self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
                    self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)

    @skipUnlessDBFeature("has_Centroid_function")
    def test_centroid(self):
        qs = State.objects.exclude(poly__isnull=True).annotate(
            centroid=functions.Centroid("poly")
        )
        tol = (
            1.8 if connection.ops.mysql else (0.1 if connection.ops.oracle else 0.00001)
        )
        for state in qs:
            self.assertTrue(state.poly.centroid.equals_exact(state.centroid, tol))

        with self.assertRaisesMessage(
            TypeError, "'Centroid' takes exactly 1 argument (2 given)"
        ):
            State.objects.annotate(centroid=functions.Centroid("poly", "poly"))

    @skipUnlessDBFeature("has_Difference_function")
    def test_difference(self):
        geom = Point(5, 23, srid=4326)
        qs = Country.objects.annotate(diff=functions.Difference("mpoly", geom))
        
        if connection.ops.oracle:
            qs = qs.exclude(name="Texas")

        for c in qs:
            self.assertTrue(c.mpoly.difference(geom).equals(c.diff))

    @skipUnlessDBFeature("has_Difference_function", "has_Transform_function")
    def test_difference_mixed_srid(self):
        
        geom = Point(556597.4, 2632018.6, srid=3857)  
        qs = Country.objects.annotate(difference=functions.Difference("mpoly", geom))
        
        if connection.ops.oracle:
            qs = qs.exclude(name="Texas")
        for c in qs:
            self.assertTrue(c.mpoly.difference(geom).equals(c.difference))

    @skipUnlessDBFeature("has_Envelope_function")
    def test_envelope(self):
        countries = Country.objects.annotate(envelope=functions.Envelope("mpoly"))
        for country in countries:
            self.assertTrue(country.envelope.equals(country.mpoly.envelope))

    @skipUnlessDBFeature("has_ForcePolygonCW_function")
    def test_force_polygon_cw(self):
        rings = (
            ((0, 0), (5, 0), (0, 5), (0, 0)),
            ((1, 1), (1, 3), (3, 1), (1, 1)),
        )
        rhr_rings = (
            ((0, 0), (0, 5), (5, 0), (0, 0)),
            ((1, 1), (3, 1), (1, 3), (1, 1)),
        )
        State.objects.create(name="Foo", poly=Polygon(*rings))
        st = State.objects.annotate(
            force_polygon_cw=functions.ForcePolygonCW("poly")
        ).get(name="Foo")
        self.assertEqual(rhr_rings, st.force_polygon_cw.coords)

    @skipUnlessDBFeature("has_FromWKB_function")
    def test_fromwkb(self):
        g = Point(56.811078, 60.608647)
        g2 = City.objects.values_list(
            functions.FromWKB(Value(g.wkb.tobytes())),
            flat=True,
        )[0]
        self.assertIs(g.equals_exact(g2, 0.00001), True)

    @skipUnlessDBFeature("has_FromWKT_function")
    def test_fromwkt(self):
        g = Point(56.811078, 60.608647)
        g2 = City.objects.values_list(
            functions.FromWKT(Value(g.wkt)),
            flat=True,
        )[0]
        self.assertIs(g.equals_exact(g2, 0.00001), True)

    @skipUnlessDBFeature("has_GeoHash_function")
    def test_geohash(self):
        
        
        
        ref_hash = "9vk1mfq8jx0c8e0386z6"
        h1 = City.objects.annotate(geohash=functions.GeoHash("point")).get(
            name="Houston"
        )
        h2 = City.objects.annotate(geohash=functions.GeoHash("point", precision=5)).get(
            name="Houston"
        )
        self.assertEqual(ref_hash, h1.geohash[: len(ref_hash)])
        self.assertEqual(ref_hash[:5], h2.geohash)

    @skipUnlessDBFeature("has_GeometryDistance_function")
    def test_geometry_distance(self):
        point = Point(-90, 40, srid=4326)
        qs = City.objects.annotate(
            distance=functions.GeometryDistance("point", point)
        ).order_by("distance")
        distances = (
            2.99091995527296,
            5.33507274054713,
            9.33852187483721,
            9.91769193646233,
            11.556465744884,
            14.713098433352,
            34.3635252198568,
            276.987855073372,
        )
        for city, expected_distance in zip(qs, distances):
            with self.subTest(city=city):
                self.assertAlmostEqual(city.distance, expected_distance)

    @skipUnlessDBFeature("has_Intersection_function")
    def test_intersection(self):
        geom = Point(5, 23, srid=4326)
        qs = Country.objects.annotate(inter=functions.Intersection("mpoly", geom))
        for c in qs:
            if connection.features.empty_intersection_returns_none:
                self.assertIsNone(c.inter)
            else:
                self.assertIs(c.inter.empty, True)

    @skipUnlessDBFeature("supports_empty_geometries", "has_IsEmpty_function")
    def test_isempty(self):
        empty = City.objects.create(name="Nowhere", point=Point(srid=4326))
        City.objects.create(name="Somewhere", point=Point(6.825, 47.1, srid=4326))
        self.assertSequenceEqual(
            City.objects.annotate(isempty=functions.IsEmpty("point")).filter(
                isempty=True
            ),
            [empty],
        )
        self.assertSequenceEqual(City.objects.filter(point__isempty=True), [empty])

    @skipUnlessDBFeature("has_IsValid_function")
    def test_isvalid(self):
        valid_geom = fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))")
        invalid_geom = fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))")
        State.objects.create(name="valid", poly=valid_geom)
        State.objects.create(name="invalid", poly=invalid_geom)
        valid = (
            State.objects.filter(name="valid")
            .annotate(isvalid=functions.IsValid("poly"))
            .first()
        )
        invalid = (
            State.objects.filter(name="invalid")
            .annotate(isvalid=functions.IsValid("poly"))
            .first()
        )
        self.assertIs(valid.isvalid, True)
        self.assertIs(invalid.isvalid, False)

    @skipUnlessDBFeature("has_Area_function")
    def test_area_with_regular_aggregate(self):
        
        for c in Country.objects.all():
            CountryWebMercator.objects.create(
                name=c.name, mpoly=c.mpoly.transform(3857, clone=True)
            )
        
        qs = CountryWebMercator.objects.annotate(area_sum=Sum(functions.Area("mpoly")))
        
        
        for c in qs.defer("mpoly"):
            result = c.area_sum
            
            if isinstance(result, Area):
                result = result.sq_m
            self.assertAlmostEqual((result - c.mpoly.area) / c.mpoly.area, 0)

    @skipUnlessDBFeature("has_Area_function")
    def test_area_lookups(self):
        
        CountryWebMercator.objects.bulk_create(
            CountryWebMercator(name=c.name, mpoly=c.mpoly.transform(3857, clone=True))
            for c in Country.objects.all()
        )
        qs = CountryWebMercator.objects.annotate(area=functions.Area("mpoly"))
        self.assertEqual(
            qs.get(area__lt=Area(sq_km=500000)),
            CountryWebMercator.objects.get(name="New Zealand"),
        )

        with self.assertRaisesMessage(
            ValueError, "AreaField only accepts Area measurement objects."
        ):
            qs.get(area__lt=500000)

    @skipUnlessDBFeature("has_ClosestPoint_function")
    def test_closest_point(self):
        qs = Country.objects.annotate(
            closest_point=functions.ClosestPoint("mpoly", functions.Centroid("mpoly"))
        )
        for country in qs:
            self.assertIsInstance(country.closest_point, Point)
            self.assertEqual(
                country.mpoly.intersection(country.closest_point),
                country.closest_point,
            )

    @skipUnlessDBFeature("has_LineLocatePoint_function")
    def test_line_locate_point(self):
        pos_expr = functions.LineLocatePoint(
            LineString((0, 0), (0, 3), srid=4326), Point(0, 1, srid=4326)
        )
        self.assertAlmostEqual(
            State.objects.annotate(pos=pos_expr).first().pos, 0.3333333
        )

    @skipUnlessDBFeature("has_MakeValid_function")
    def test_make_valid(self):
        invalid_geom = fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))")
        State.objects.create(name="invalid", poly=invalid_geom)
        invalid = (
            State.objects.filter(name="invalid")
            .annotate(repaired=functions.MakeValid("poly"))
            .first()
        )
        self.assertIs(invalid.repaired.valid, True)
        self.assertTrue(
            invalid.repaired.equals(
                fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", srid=invalid.poly.srid)
            )
        )

    @skipUnlessDBFeature("has_MakeValid_function")
    def test_make_valid_multipolygon(self):
        invalid_geom = fromstr(
            "POLYGON((0 0, 0 1 , 1 1 , 1 0, 0 0), (10 0, 10 1, 11 1, 11 0, 10 0))"
        )
        State.objects.create(name="invalid", poly=invalid_geom)
        invalid = (
            State.objects.filter(name="invalid")
            .annotate(
                repaired=functions.MakeValid("poly"),
            )
            .get()
        )
        self.assertIs(invalid.repaired.valid, True)
        self.assertTrue(
            invalid.repaired.equals(
                fromstr(
                    "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), "
                    "((10 0, 10 1, 11 1, 11 0, 10 0)))",
                    srid=invalid.poly.srid,
                )
            )
        )
        self.assertEqual(len(invalid.repaired), 2)

    @skipUnlessDBFeature("has_MakeValid_function")
    def test_make_valid_output_field(self):
        
        
        output_field = functions.MakeValid(
            Value(Polygon(), PolygonField(srid=42)),
        ).output_field
        self.assertIs(output_field.__class__, GeometryField)
        self.assertEqual(output_field.srid, 42)

    @skipUnlessDBFeature("has_MemSize_function")
    def test_memsize(self):
        ptown = City.objects.annotate(size=functions.MemSize("point")).get(
            name="Pueblo"
        )
        
        self.assertTrue(20 <= ptown.size <= 105)

    @skipUnlessDBFeature("has_NumGeom_function")
    def test_num_geom(self):
        
        for c in Country.objects.annotate(num_geom=functions.NumGeometries("mpoly")):
            self.assertEqual(2, c.num_geom)

        qs = City.objects.filter(point__isnull=False).annotate(
            num_geom=functions.NumGeometries("point")
        )
        for city in qs:
            
            
            if connection.ops.mysql or connection.ops.mariadb:
                self.assertIsNone(city.num_geom)
            else:
                self.assertEqual(1, city.num_geom)

    @skipUnlessDBFeature("has_NumPoint_function")
    def test_num_points(self):
        coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)]
        Track.objects.create(name="Foo", line=LineString(coords))
        qs = Track.objects.annotate(num_points=functions.NumPoints("line"))
        self.assertEqual(qs.first().num_points, 2)
        mpoly_qs = Country.objects.annotate(num_points=functions.NumPoints("mpoly"))
        if not connection.features.supports_num_points_poly:
            for c in mpoly_qs:
                self.assertIsNone(c.num_points)
            return

        for c in mpoly_qs:
            self.assertEqual(c.mpoly.num_points, c.num_points)

        for c in City.objects.annotate(num_points=functions.NumPoints("point")):
            self.assertEqual(c.num_points, 1)

    @skipUnlessDBFeature("has_PointOnSurface_function")
    def test_point_on_surface(self):
        qs = Country.objects.annotate(
            point_on_surface=functions.PointOnSurface("mpoly")
        )
        for country in qs:
            self.assertTrue(country.mpoly.intersection(country.point_on_surface))

    @skipUnlessDBFeature("has_Reverse_function")
    def test_reverse_geom(self):
        coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)]
        Track.objects.create(name="Foo", line=LineString(coords))
        track = Track.objects.annotate(reverse_geom=functions.Reverse("line")).get(
            name="Foo"
        )
        coords.reverse()
        self.assertEqual(tuple(coords), track.reverse_geom.coords)

    @skipUnlessDBFeature("has_Scale_function")
    def test_scale(self):
        xfac, yfac = 2, 3
        tol = 5  
        qs = Country.objects.annotate(scaled=functions.Scale("mpoly", xfac, yfac))
        for country in qs:
            for p1, p2 in zip(country.mpoly, country.scaled):
                for r1, r2 in zip(p1, p2):
                    for c1, c2 in zip(r1.coords, r2.coords):
                        self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
                        self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
        
        qs = Country.objects.annotate(
            scaled=functions.Scale("mpoly", 1.5, Decimal("2.5"))
        )
        self.assertGreater(qs[0].scaled.area, qs[0].mpoly.area)

    @skipUnlessDBFeature("has_SnapToGrid_function")
    def test_snap_to_grid(self):
        
        for bad_args in ((), range(3), range(5)):
            with self.assertRaises(ValueError):
                Country.objects.annotate(snap=functions.SnapToGrid("mpoly", *bad_args))
        for bad_args in (("1.0",), (1.0, None), tuple(map(str, range(4)))):
            with self.assertRaises(TypeError):
                Country.objects.annotate(snap=functions.SnapToGrid("mpoly", *bad_args))

        
        
        wkt = (
            "MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,"
            "12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,"
            "12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,"
            "12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,"
            "12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,"
            "12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,"
            "12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,"
            "12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))"
        )
        Country.objects.create(name="San Marino", mpoly=fromstr(wkt))

        
        
        tol = 0.000000001

        
        
        
        ref = fromstr("MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))")
        self.assertTrue(
            ref.equals_exact(
                Country.objects.annotate(snap=functions.SnapToGrid("mpoly", 0.1))
                .get(name="San Marino")
                .snap,
                tol,
            )
        )

        
        
        
        ref = fromstr(
            "MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))"
        )
        self.assertTrue(
            ref.equals_exact(
                Country.objects.annotate(snap=functions.SnapToGrid("mpoly", 0.05, 0.23))
                .get(name="San Marino")
                .snap,
                tol,
            )
        )

        
        
        
        ref = fromstr(
            "MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,"
            "12.45 43.87,12.4 43.87)))"
        )
        self.assertTrue(
            ref.equals_exact(
                Country.objects.annotate(
                    snap=functions.SnapToGrid("mpoly", 0.05, 0.23, 0.5, 0.17)
                )
                .get(name="San Marino")
                .snap,
                tol,
            )
        )

    @skipUnlessDBFeature("has_SymDifference_function")
    def test_sym_difference(self):
        geom = Point(5, 23, srid=4326)
        qs = Country.objects.annotate(
            sym_difference=functions.SymDifference("mpoly", geom)
        )
        
        if connection.ops.oracle:
            qs = qs.exclude(name="Texas")
        for country in qs:
            self.assertTrue(
                country.mpoly.sym_difference(geom).equals(country.sym_difference)
            )

    @skipUnlessDBFeature("has_Transform_function")
    def test_transform(self):
        
        ptown = fromstr("POINT(992363.390841912 481455.395105533)", srid=2774)

        
        
        h = City.objects.annotate(pt=functions.Transform("point", ptown.srid)).get(
            name="Pueblo"
        )
        self.assertEqual(2774, h.pt.srid)
        
        self.assertLess(ptown.x - h.pt.x, 1)
        self.assertLess(ptown.y - h.pt.y, 1)

    @skipUnlessDBFeature("has_Translate_function")
    def test_translate(self):
        xfac, yfac = 5, -23
        qs = Country.objects.annotate(
            translated=functions.Translate("mpoly", xfac, yfac)
        )
        for c in qs:
            for p1, p2 in zip(c.mpoly, c.translated):
                for r1, r2 in zip(p1, p2):
                    for c1, c2 in zip(r1.coords, r2.coords):
                        
                        self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
                        self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)

    
    @skipUnlessDBFeature(
        "has_Difference_function",
        "has_Intersection_function",
        "has_SymDifference_function",
        "has_Union_function",
    )
    def test_diff_intersection_union(self):
        geom = Point(5, 23, srid=4326)
        qs = Country.objects.annotate(
            difference=functions.Difference("mpoly", geom),
            sym_difference=functions.SymDifference("mpoly", geom),
            union=functions.Union("mpoly", geom),
            intersection=functions.Intersection("mpoly", geom),
        )

        if connection.ops.oracle:
            
            
            
            return
        for c in qs:
            self.assertTrue(c.mpoly.difference(geom).equals(c.difference))
            if connection.features.empty_intersection_returns_none:
                self.assertIsNone(c.intersection)
            else:
                self.assertIs(c.intersection.empty, True)
            self.assertTrue(c.mpoly.sym_difference(geom).equals(c.sym_difference))
            self.assertTrue(c.mpoly.union(geom).equals(c.union))

    @skipUnlessDBFeature("has_Union_function")
    def test_union(self):
        
        geom = Point(-95.363151, 29.763374, srid=4326)

        union = (
            City.objects.annotate(union=functions.Union("point", geom))
            .get(name="Dallas")
            .union
        )
        expected = fromstr(
            "MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)", srid=4326
        )
        self.assertTrue(expected.equals(union))

        union = (
            City.objects.annotate(union=functions.Union(geom, "point"))
            .get(name="Dallas")
            .union
        )
        self.assertTrue(expected.equals(union))

        union = (
            City.objects.annotate(union=functions.Union("point", "point"))
            .get(name="Dallas")
            .union
        )
        expected = GEOSGeometry("POINT(-96.801611 32.782057)", srid=4326)
        self.assertTrue(expected.equals(union))

        union = (
            City.objects.annotate(union=functions.Union(geom, geom))
            .get(name="Dallas")
            .union
        )
        self.assertTrue(geom.equals(union))

    @skipUnlessDBFeature("has_Union_function", "has_Transform_function")
    def test_union_mixed_srid(self):
        
        geom = Point(61.42915, 55.15402, srid=4326)
        geom_3857 = geom.transform(3857, clone=True)
        tol = 0.001

        for city in City.objects.annotate(union=functions.Union("point", geom_3857)):
            expected = city.point | geom
            self.assertTrue(city.union.equals_exact(expected, tol))
            self.assertEqual(city.union.srid, 4326)

        for city in City.objects.annotate(union=functions.Union(geom_3857, "point")):
            expected = geom_3857 | city.point.transform(3857, clone=True)
            self.assertTrue(expected.equals_exact(city.union, tol))
            self.assertEqual(city.union.srid, 3857)

    def test_argument_validation(self):
        with self.assertRaisesMessage(
            ValueError, "SRID is required for all geometries."
        ):
            City.objects.annotate(geo=functions.GeoFunc(Point(1, 1)))

        msg = "GeoFunc function requires a GeometryField in position 1, got CharField."
        with self.assertRaisesMessage(TypeError, msg):
            City.objects.annotate(geo=functions.GeoFunc("name"))

        msg = "GeoFunc function requires a geometric argument in position 1."
        with self.assertRaisesMessage(TypeError, msg):
            City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
                name="Dallas"
            )