{"record":{"id":"7a3b889c33c4f00b","repo":"django/django","slug":"st-perimeter-cannot-use-a-non-projected-non-geogra","errorCode":null,"errorMessage":"ST_Perimeter cannot use a non-projected non-geography field.","messagePattern":"ST_Perimeter cannot use a non-projected non-geography field\\.","errorType":"exception","errorClass":"NotSupportedError","httpStatus":null,"severity":"error","filePath":"django/contrib/gis/db/models/functions.py","lineNumber":547,"sourceCode":"\n\nclass NumGeometries(GeoFunc):\n    output_field = IntegerField()\n    arity = 1\n\n\nclass NumPoints(GeoFunc):\n    output_field = IntegerField()\n    arity = 1\n\n\nclass Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc):\n    arity = 1\n\n    def as_postgresql(self, compiler, connection, **extra_context):\n        function = None\n        if self.geo_field.geodetic(connection) and not self.source_is_geography():\n            raise NotSupportedError(\n                \"ST_Perimeter cannot use a non-projected non-geography field.\"\n            )\n        dim = min(f.dim for f in self.get_source_fields())\n        if dim > 2:\n            function = connection.ops.perimeter3d\n        return super().as_sql(compiler, connection, function=function, **extra_context)\n\n    def as_sqlite(self, compiler, connection, **extra_context):\n        if self.geo_field.geodetic(connection):\n            raise NotSupportedError(\"Perimeter cannot use a non-projected field.\")\n        return super().as_sql(compiler, connection, **extra_context)\n\n\nclass PointOnSurface(OracleToleranceMixin, GeomOutputGeoFunc):\n    arity = 1\n\n\nclass Reverse(GeoFunc):","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/django/django/blob/ae25a40be07e8a749edf526df37c93e59d4a22c9/django/contrib/gis/db/models/functions.py#L529-L565","documentation":"NotSupportedError raised in Perimeter.as_postgresql (functions.py:547) on PostGIS when the field is geodetic AND not geography. ST_Perimeter on a lon/lat geometry column is meaningless in metres; you must use the geography type so PostGIS applies geodesic perimeter.","triggerScenarios":"Model.objects.annotate(p=Perimeter('geom')) on PostGIS where geom is a GeometryField with srid=4326 and geography=False.","commonSituations":"Default GeometryField(srid=4326) and then trying to compute perimeter; converting a PostGIS project from a projected CRS to WGS84 without setting geography=True.","solutions":["Declare the field with geography=True: geom = PolygonField(srid=4326, geography=True).","Use a projected SRID (e.g. 3857, UTM) so Perimeter is planar in metres.","Apply Transform to a projected CRS in the query: Perimeter(Transform('geom', 3857)).","Run a migration to switch the column to geography if switching the model field."],"exampleFix":"# before\ngeom = models.PolygonField(srid=4326)  # geography=False\nqs = Plot.objects.annotate(p=Perimeter('geom'))  # raises\n# after\ngeom = models.PolygonField(srid=4326, geography=True)\n# or in-query\nqs = Plot.objects.annotate(p=Perimeter(Transform('geom', 3857)))","handlingStrategy":"validation","validationCode":"from django.db import connection\n\ndef perimeter_ok_on_postgis(field):\n    return (not field.geodetic(connection)) or field.geography\n\nif field.geodetic(connection) and not field.geography:\n    raise ValueError('Use geography=True or a projected SRID for Perimeter on PostGIS')","typeGuard":"def geography_or_projected(field, conn) -> bool:\n    return (not field.geodetic(conn)) or bool(getattr(field, 'geography', False))","tryCatchPattern":"from django.contrib.gis.db.models.functions import Perimeter, Transform\ntry:\n    qs = Model.objects.annotate(p=Perimeter('geom'))\nexcept NotImplementedError:\n    qs = Model.objects.annotate(p=Perimeter(Transform('geom', 3857)))","preventionTips":["Default geometry fields to geography=True on PostGIS when you compute geodesic measures.","Use a projected SRID if you need planar perimeters.","Add migrations to switch existing columns to geography when needed."],"tags":["gis","perimeter","postgis","geodetic","geography","notsupported"],"analyzedSha":"ae25a40be07e8a749edf526df37c93e59d4a22c9","analyzedAt":"2026-08-06T21:46:51.801Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}