django/django · error · ValueError

Only numeric values of degree units are allowed on geographi

Error message

Only numeric values of degree units are allowed on geographic DWithin queries.

What it means

ValueError raised in get_distance (operations.py:140) when a Distance object is supplied to a dwithin lookup on a geodetic (lat/lon) SpatiaLite field. SpatiaLite's PtDistWithin on geographic data needs a plain degree value, not a Distance with metre/foot units.

Source

Thrown at django/contrib/gis/db/backends/spatialite/operations.py:140

    def geo_db_type(self, f):
        """
        Return None because geometry columns are added via the
        `AddGeometryColumn` stored procedure on SpatiaLite.
        """
        return None

    def get_distance(self, f, value, lookup_type):
        """
        Return the distance parameters for the given geometry field,
        lookup value, and lookup type.
        """
        if not value:
            return []
        value = value[0]
        if isinstance(value, Distance):
            if f.geodetic(self.connection):
                if lookup_type == "dwithin":
                    raise ValueError(
                        "Only numeric values of degree units are allowed on "
                        "geographic DWithin queries."
                    )
                dist_param = value.m
            else:
                dist_param = getattr(
                    value, Distance.unit_attname(f.units_name(self.connection))
                )
        else:
            dist_param = value
        return [dist_param]

    def _get_spatialite_func(self, func):
        """
        Helper routine for calling SpatiaLite functions and returning
        their result.
        Any error occurring in this method should be handled by the caller.
        """

View on GitHub (pinned to ae25a40be0)

Solutions

  1. Pass a numeric degree value for geodetic dwithin: filter(geom__dwithin=(point, 0.05)) where 0.05 ≈ degrees.
  2. If you really need metre semantics, use a projected (non-geodetic) SRID for the field so Distance is accepted.
  3. On PostGIS, declare the field with geography=True to keep D(m=...) working.
  4. Convert metres to degrees at the call site (approximate: degrees = metres / 111320 for rough latitude).

Example fix

# before: distance object on geodetic spatialite
qs = Shop.objects.filter(geom__dwithin=(pt, D(km=5)))
# after: plain degree value
qs = Shop.objects.filter(geom__dwithin=(pt, 0.045))  # ~5 km
Defensive patterns

Strategy: type-guard

Validate before calling

from django.contrib.gis.measure import Distance
from django.contrib.gis.geos import GEOSGeometry

def dwithin_value(field, point, dist):
    if field.geodetic and isinstance(dist, Distance):
        raise ValueError('geodetic dwithin needs a numeric degree value, not Distance')
    return (point, dist)

Type guard

from django.contrib.gis.measure import Distance

def is_distance_obj(v) -> bool:
    return isinstance(v, Distance)

Try / catch

try:
    qs = Model.objects.filter(geom__dwithin=(pt, value))
except ValueError:
    # value was a Distance on a geodetic field; fall back to a degree value
    qs = Model.objects.filter(geom__dwithin=(pt, metres_to_degrees(value)))

Prevention

When it happens

Trigger: Calling Model.objects.filter(geom__dwithin=(point, D(km=5))) where geom has srid=4326 (geodetic) on the spatialite backend; the D(...) shortcut wraps the value as a Distance instance.

Common situations: Reusing the same query across PostGIS (geography) and SpatiaLite; copy-pasting a metre-based distance filter from a PostGIS tutorial into a SpatiaLite project; assuming D(m=...) works everywhere.

Related errors


AI-assisted analysis of django/django@ae25a40be0 (2026-08-06). Data as JSON: /api/errors/8b3a19324843381a. Report an issue: GitHub.